From 8d1bb025d4f3f814487a9a69a769c0f08cbb668a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 17 Sep 2026 12:43:26 +0200 Subject: [PATCH 1/4] Write the CultCache document variants target Operator rulings: variants are a CultCache feature, stored as a delta from a base and resolved by the cache. Ends, invariants and open forks for the Imagination pass. Co-Authored-By: Claude Opus 5 --- docs/document-variants-target.md | 108 +++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 docs/document-variants-target.md diff --git a/docs/document-variants-target.md b/docs/document-variants-target.md new file mode 100644 index 00000000..fe3d2644 --- /dev/null +++ b/docs/document-variants-target.md @@ -0,0 +1,108 @@ +# CultCache Document Variants: Target + +Date: 2026-09-17 + +Status: target, before any substrate map or cut map. This document owns the ends. +A cut map, written after an Imagination pass over the Body, will own the means. +Branch: `codex/document-variants`. + +## Why + +A CultCache record today is always complete. To make a bigger laser from a +laser, an author copies the record and re-specifies every stat. For a ship hull +with several hardpoint layouts, every layout is a full copy of the hull. When +the base changes, each copy has to be found and edited by hand, and copies drift +apart silently. + +The first consumer is Aetheria's catalog (`F:\Projects\Aetheria\GameData\Aetheria.cc`), +edited in CultCache Studio: + +- **Hull variants:** one hull with several hardpoint layout options. A variant + replaces the whole `HullData.Hardpoints` member. +- **Item variants:** a bigger laser. A variant changes stats nested inside the + design's behavior list, for example one behavior's `PerformanceStat.Max`. + +## Rulings (operator, 2026-09-17) + +- **This is a CultCache feature, not an Aetheria mechanism.** Operator: "Yes, this + is absolutely a CultCache feature, write it up in CultLib". +- **A variant is stored as a delta from its base and resolved by the cache.** A + record stores a reference to its base plus a sparse set of overrides. The + cache resolves it; readers see complete documents. The operator chose this + over two alternatives. Authoring-only variants in Studio over flattened stored + copies leave a derived cache pretending to be data. Per-type variant records + in each consumer rebuild the feature bespoke for every type. + +## Ends + +- **Readers see complete documents.** `Get`, `GetAll`, lookups by name and by + index, watches and snapshots return resolved documents. No consumer outside + the cache and its authoring tools handles a delta. +- **A base change reaches its variants.** Editing a base changes every variant + that does not override the edited value. Watches, indexes and name lookups + observe that change on the variants too, in the same commit that changed the + base. +- **A variant is a record.** It has its own key and name, and references to it + behave like references to any record. In Aetheria a lot's design and a + product's design can name a variant directly. +- **Studio edits the delta.** It shows which values a variant inherits and which + it overrides, and it can clear an override so the base value applies again. +- **Loading refuses a broken variant loudly and names the records.** Cases: + - a cycle of bases; + - a base that does not exist; + - a base whose type the variant's type cannot inherit from; + - an override naming a member that does not exist on the type. + + Deleting a base that still has variants is refused. + +## Invariants that must survive + +- **Wire parity** for the persisted format (`src/GameCult.Caching/Contracts/cultcache-persistence-format.md`, + `docs/persisted-record-encoding.md`). Every runtime that reads a store holding + variants resolves them identically, or refuses the store loudly. Which + runtimes resolve is decided in the map, against + `docs/runtime-parity-scope.md`; none may misread silently. +- **One home store per type**, and atomic single-store commit + (`cultcache-store-composition.md`). A base change and the resulting variant + changes are one commit, not a repair that runs afterwards. +- **Schema compatibility** (`cultcache-schema-compatibility.md`): overrides are + subject to the same semantic identity, soft drift and hard rejection rules as + full records. +- **Compare-exchange** (`Expect`, `ExpectUnchanged`) keeps a single meaning for + variants. The map settles whether it tests the stored delta or the resolved + document; it must not mean both. + +## Open forks, for the Imagination pass to map and the operator to rule + +1. **Override granularity.** + - Top-level members are enough for hardpoint layouts. + - A bigger laser needs overrides inside list elements. Addressing list + elements by index is fragile: an insertion into the base silently retargets + every variant's overrides. + - Candidates: stable element identity (for example a behavior's type within its + design); replacing whole list elements only; or addressing paths. + - CultNet's selection work already resolves member paths per leaf type, + inherited members included (`docs/cultnet-selection-cut.md`). A variant's + delta addressing should reuse that grammar, not invent a second one. +2. **Where resolution lives:** at load in the cache core, lazily on read, or as a + materialized view the cache maintains. This affects memory, watch cost, and + the compare-exchange meaning. +3. **Base and variant across stores.** Can a variant in one store name a base in + another (a run-store variant of a catalog record)? This interacts with + one-home-store routing and read-only stores. +4. **CultNet.** Do variants travel as deltas, or resolved? A subscriber that + receives only resolved documents needs no resolver; one that edits needs + deltas. +5. **Runtime scope.** Which of C#, TypeScript, Python, Rust and Kotlin resolve, + and which refuse a store containing variants. +6. **Type change.** May a variant's type be a subtype of its base's type, + adding members, or must the two types match? +7. **Clearing and reparenting.** What clearing an override writes, and whether + a variant can change its base. + +## Not in scope + +- Scaling or formula variants ("every stat times 1.5"). An override states a + value. Generated authoring can compute values. +- Any Aetheria catalog content. Aetheria adopts the feature after a release, as + its own cut. From c901d58824e08d8da722da40fe510221b7878520 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 17 Sep 2026 12:52:48 +0200 Subject: [PATCH 2/4] Record the operator's rulings on variant forks 1, 3, 4, 5 and 7 Co-Authored-By: Claude Opus 5 --- docs/document-variants-target.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/document-variants-target.md b/docs/document-variants-target.md index fe3d2644..1e438242 100644 --- a/docs/document-variants-target.md +++ b/docs/document-variants-target.md @@ -72,6 +72,25 @@ edited in CultCache Studio: variants. The map settles whether it tests the stored delta or the resolved document; it must not mean both. +## Fork rulings (operator, 2026-09-17) + +- **1, granularity: agreed.** Index addressing into lists is out, and addressing reuses + CultNet's member-path grammar. The Imagination pass still chooses between stable element + identity and whole-element replacement, and that choice comes back as a fork. +- **2, where resolution lives: still open.** Here, resolution means computing a + variant's complete document from its base chain and its overrides. The operator asked + what the fork meant, and it has been explained. +- **3, cross-store bases: no.** A variant and its base live in the same store. +- **4, CultNet: deltas.** +- **5, runtimes: C# first.** TypeScript, Python, Rust and Kotlin refuse a store + or snapshot containing variants, loudly, until each implements resolution. +- **6, type change: open.** The operator called it "a good question". +- **7, clearing and rebasing.** + - Clearing an override removes that member from the variant's stored delta. A member + that is not overridden is not stored. + - Rebasing (changing a variant's base) is supported. Operator: "rebasing is a nice + tool to have, things can get annoying otherwise". + ## Open forks, for the Imagination pass to map and the operator to rule 1. **Override granularity.** From 1cf44e630a60ee84a0534625c1c61a3ce1e2bfae Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 17 Sep 2026 12:57:08 +0200 Subject: [PATCH 3/4] Record variant forks 2 and 6: resolve at load, same concrete type Co-Authored-By: Claude Opus 5 --- docs/document-variants-target.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/document-variants-target.md b/docs/document-variants-target.md index 1e438242..80528a6e 100644 --- a/docs/document-variants-target.md +++ b/docs/document-variants-target.md @@ -77,14 +77,22 @@ edited in CultCache Studio: - **1, granularity: agreed.** Index addressing into lists is out, and addressing reuses CultNet's member-path grammar. The Imagination pass still chooses between stable element identity and whole-element replacement, and that choice comes back as a fork. -- **2, where resolution lives: still open.** Here, resolution means computing a - variant's complete document from its base chain and its overrides. The operator asked - what the fork meant, and it has been explained. +- **2, where resolution lives: at load, kept in memory** (operator: "agreed"). Resolution + computes a variant's complete document from its base chain and its overrides. + - The cache holds both the stored delta and the resolved document. + - A base edit re-resolves its variants inside the same commit, so watches and indexes + observe the change atomically. + - Compare-exchange (`Expect`) tests the stored delta. The resolved document is derived. - **3, cross-store bases: no.** A variant and its base live in the same store. - **4, CultNet: deltas.** - **5, runtimes: C# first.** TypeScript, Python, Rust and Kotlin refuse a store or snapshot containing variants, loudly, until each implements resolution. -- **6, type change: open.** The operator called it "a good question". +- **6, type change: the same concrete type only** (operator: "agreed"). + - An override can only name members the base type has, and rebasing requires the same + type. + - A variant whose type differs from its base's is refused loudly. + - Subtype variants are parked until real content needs a template base. A record + flagged as a template, not a real item, may be the smaller feature. - **7, clearing and rebasing.** - Clearing an override removes that member from the variant's stored delta. A member that is not overridden is not stored. From 979fa42fb2c67cbe9e9441a49f74f6dfafbd8c7d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 17 Sep 2026 13:02:11 +0200 Subject: [PATCH 4/4] Record variant fork 1: stable element ids everywhere Co-Authored-By: Claude Opus 5 --- docs/document-variants-target.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/document-variants-target.md b/docs/document-variants-target.md index 80528a6e..1adbbe50 100644 --- a/docs/document-variants-target.md +++ b/docs/document-variants-target.md @@ -74,9 +74,20 @@ edited in CultCache Studio: ## Fork rulings (operator, 2026-09-17) -- **1, granularity: agreed.** Index addressing into lists is out, and addressing reuses - CultNet's member-path grammar. The Imagination pass still chooses between stable element - identity and whole-element replacement, and that choice comes back as a fork. +- **1, granularity: stable element identity, ids everywhere** (operator, 2026-09-17: "ids + everywhere"). + - Every object element in a list carries a stable id, minted when the element is created. + This is the approach of sequence CRDTs (RGA, YATA/Yjs, Automerge, Fugue). + - Matching algorithms (Myers, diff3, GumTree) are heuristics. They fail on look-alike + elements such as two `EnergyDraw` behaviors, which is why identity is minted, not + inferred. + - An override addresses an element id plus a member path in CultNet's grammar. An + insertion is a new element anchored after an existing id; a removal names the + removed id. Index addressing is out. + - Rebasing is deterministic. An override whose element id exists in the new base + re-applies. One whose element is missing is an explicit conflict that Studio shows, + to keep, retarget or clear; it is never silently retargeted. + - Existing stores get ids minted once, by a rewrite. - **2, where resolution lives: at load, kept in memory** (operator: "agreed"). Resolution computes a variant's complete document from its base chain and its overrides. - The cache holds both the stored delta and the resolved document.