From a9b955660e9eefdda568bdbc575d2afe705b3931 Mon Sep 17 00:00:00 2001 From: Libby Date: Wed, 11 Nov 2026 00:49:52 +0000 Subject: [PATCH] Docs: [CultIndex] is unique, [CultRole] names a selection role Co-Authored-By: Claude Sonnet 5.5 --- docs/studio-grouping-cut.md | 6 +++--- .../Contracts/cultcache-store-composition.md | 4 +++- src/GameCult.Caching/README.md | 12 +++++++++--- src/GameCult.Mesh/docs/public-api.md | 13 +++++++------ 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/studio-grouping-cut.md b/docs/studio-grouping-cut.md index 770d0698..7bc8418c 100644 --- a/docs/studio-grouping-cut.md +++ b/docs/studio-grouping-cut.md @@ -33,9 +33,9 @@ Recovered from the legacy "Database Tools" window (Aetheria `d3db1730`, **The attribute is not `[CultIndex]`.** `[CultIndex]` means unique (operator, 2026-09-30; `CultDocumentContracts.cs:34`). Grouping is `CultInspectorGroupByAttribute`, in `CultInspectorAttributes.cs`, read only by `CultInspectorModel`. It changes no schema id, -catalog entry, index, or store byte. The index cut's deletion of grouping-only `[CultIndex]` -declarations (CultMesh, Geometry) adds no `[CultInspectorGroupBy]` in their place: those are -service stores, not Studio-authored catalogs. +catalog entry, index, or store byte. The index cut converts the grouping and selection `[CultIndex]` +declarations (CultMesh, Geometry) to `[CultRole]` rather than deleting them, and adds no +`[CultInspectorGroupBy]` in their place: those are service stores, not Studio-authored catalogs. ### Invariants diff --git a/src/GameCult.Caching/Contracts/cultcache-store-composition.md b/src/GameCult.Caching/Contracts/cultcache-store-composition.md index 1a03d4b1..6e240628 100644 --- a/src/GameCult.Caching/Contracts/cultcache-store-composition.md +++ b/src/GameCult.Caching/Contracts/cultcache-store-composition.md @@ -110,7 +110,9 @@ which route wrote it. already agree when publication starts. - An admission runs a record's `[CultName]` and `[CultIndex]` getters once per landing record, before the store is written and before any `Sequence` is - minted. A conditional commit on a clean file store is judged a second time + minted. An admission is refused when it would leave two records of one type + holding one `[CultIndex]` value (blank is no value), and a load of a store + holding such a pair refuses naming both keys and the index. A conditional commit on a clean file store is judged a second time against the file it merges onto whenever the cache holds variants in any store, the batch lands a variant, or the file holds one. That second read runs the getters again for the records the batch lands, for any variant it re-resolves, diff --git a/src/GameCult.Caching/README.md b/src/GameCult.Caching/README.md index fc5cd308..c0eb2a8e 100644 --- a/src/GameCult.Caching/README.md +++ b/src/GameCult.Caching/README.md @@ -22,9 +22,12 @@ public sealed class ItemData [Key(1)] [CultIndex] - public string Category = string.Empty; + public string Sku = string.Empty; [Key(2)] + public string Category = string.Empty; + + [Key(3)] public int Value; } ``` @@ -33,7 +36,8 @@ Available cache attributes: - `CultDocument(schemaName, schemaVersion)` - `CultName` -- `CultIndex(alias?)` +- `CultIndex(alias?)`: a unique lookup. Within one document type, a non-blank value names one record; a commit that would leave two records holding one is refused, and a store holding such a pair refuses to load. +- `CultRole(alias?)`: a selection and reference role. Many records may share a value; it is never a lookup. - `CultGlobal` - `CultReference(targetType?, many: false)` @@ -53,6 +57,7 @@ var cache = new CultCache(); var handle = await cache.AddAsync(new ItemData { Name = "Potion", + Sku = "POT-1", Category = "Consumable", Value = 50 }); @@ -66,7 +71,7 @@ Name and index lookups are driven from attributes, not manual registration. ```csharp var byName = cache.GetByName("Potion"); -var byIndex = cache.GetByIndex("Category", "Consumable"); +var byIndex = cache.GetByIndex("Sku", "POT-1"); ``` ### Explicit references @@ -130,6 +135,7 @@ var cache = await CultCacheMessagePack.OpenAsync("Data.msgpack"); var handle = await cache.UpsertAsync(new ItemData { Name = "Potion", + Sku = "POT-1", Category = "Consumable", Value = 50 }); diff --git a/src/GameCult.Mesh/docs/public-api.md b/src/GameCult.Mesh/docs/public-api.md index 072e6c73..31737e9c 100644 --- a/src/GameCult.Mesh/docs/public-api.md +++ b/src/GameCult.Mesh/docs/public-api.md @@ -321,15 +321,16 @@ var bySchema = documents.DocumentBySchema("gamecult.aetheria.station_refit.v1"); Typed collections use the same one-call style: ```csharp -var allies = CultMesh.CollectionByIndex( +// A [CultIndex] value is unique within its type, so the handle holds at most one record. +var flagship = CultMesh.CollectionByIndex( node, - "Faction", - "au"); + "CallSign", + "au-1"); -var currentAllies = await allies.LatestAsync(); -using var changes = allies.WatchChanges(change => UpdateRoster(change)); +var currentFlagship = await flagship.LatestAsync(); +using var changes = flagship.WatchChanges(change => UpdateRoster(change)); -var collections = CultMesh.Collections(allies); +var collections = CultMesh.Collections(flagship); var roster = await collections.LatestAsync(); using var rosterChanges = collections.WatchChanges(RenderRoster); ```