30 KiB
Studio Record Grouping And Reference Drag-Drop Cut
First mapped 2026-09-17 (Imagination) against CultLib 122c217. Refreshed 2026-09-29
(Imagination) against CultLib main feeced8 and Aetheria codex/fire-control-12 4b594e11.
Every file:line below is against those revisions unless marked. Hands re-reads an anchor
before editing it; main keeps moving.
Rulings. Q1 B (a class attribute on the model; argument order is nesting order;
2026-09-17). Q2 A (occupied values only). Q3 A (list only, not the ref picker).
Q4 A (no record search). Q5 overtaken: one release from main, together with
document-variants C3 (docs/document-variants-cut.md), taking whatever native-plugin drift
main carries. Q6 A (flat row by resolved value, C3's variant marker; 2026-09-30). Q7 B (legacy parity
minus manufacturer, plus FactionProductData by Manufacturer then Design, plus
WeaponItemData by WeaponType; 2026-09-30).
Target
Ends
- Declared record grouping.
[CultInspectorGroupBy(nameof(A), nameof(B))]on a document class makes Studio's record list for that type a foldout tree, nested in argument order. A declaration on a base type groups every derived type listed under it. - Create in a group. Every group node offers
Create. The new record gets every grouped member on the path to that node set to that node's value, so it lands in that node. - Reference drag and drop. A record row dragged from Studio's list onto any
CultRecordRef<T>value the inspector draws (member, list element, dictionary key or value) is accepted exactly when the ref's picker would offer that record.
Recovered from the legacy "Database Tools" window (Aetheria d3db1730,
Assets/Scripts/CultCache/Editor/DatabaseView.cs, AetheriaDatabaseView.cs,
Inspectors/DatabaseLinkInspector.cs). Selector lambdas are rejected (operator, 2026-09-17).
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.
Invariants
- I1. The model owns grouping.
CultInspectorModelalone decides which members group a listed type and in what order, which declarations are invalid (and the notice), the tree's partition, node identity, order and labels, and the object create-in-group makes. Studio lowers the tree; it never reads the attribute, callsSetValuefor a preset, or computes a group label. - I2. The leaves partition the candidates. Across all leaves the tree holds exactly
RecordCandidates(CultRecordRef<listed>, records), each record once, in candidate order inside a leaf. Grouping never hides a record, including one whose grouped value is unset, dangling, or an undefined enum value. - I3. The listed type owns the tree's shape. Grouping comes from the type selected in the type pane, never from each record's runtime type.
- I4. Node identity is value identity, not the label. A ref groups by key, an enum by its underlying value, other kinds by the value's invariant string. Foldout state keys on node id.
- I5. Create-in-group round-trips. Upsert what
CreateInGroup(listed, node)returns, regroup: the new record lies in a node with the same id. - I6. One candidate rule.
IsRecordCandidate(refType, record)is the only test for "this record may be this ref's value".RecordCandidatesfilters through it; drop acceptance calls it. - I7. One ref label. The model owns
NoneandMissing <key>; the picker and group labels both use it. - I8. Invalid declarations never break the window. They give a flat list plus the model's
notice, never a throw in
OnGUI. - I9. A drop honours the picker's gates. Refused in a disabled (read-only) scope, when the
payload came from another model, and when the key equals the current key. A dictionary-key
drop still passes
ReplaceKey(CultCacheStudioDrawers.cs:303-309). - I10 (new). Grouping reads what readers read. A variant record groups, labels, and is a
drop candidate by its resolved
Document(CultCache.cs:369-371), never its delta.
Non-goals
No sibling-runtime port (TS cult-cache-inspector.ts is a byte dumper; Rust and Python have
no inspector). No runtime CultUI panel. No search (Q4), no picker grouping (Q3), no
drop-to-append on list headers, no legacy New <Type> naming.
Body findings
Kept from 2026-09-17, re-anchored:
- F3. Legacy grouping matched the exact type; grouping derived records under a base declaration is new.
- F4. Legacy create-in-group was dead (
DatabaseView.cs:70-75throws); legacy foldouts keyed on the label hash. - F5.
member.GetCustomAttributes(true)ignoresinheritfor aPropertyInfo, so a base[M] abstract Pis not seen on the override. Sites:CultInspectorModel.cs:36(now the only metadata site:MetadataOfcaches it per member,:341-351) and:209(CultInspectorDrawerClaims.Resolve). Fix:Attribute.GetCustomAttributes(member, true). - F6. Studio
Addcreates only the listed type (CultCacheStudioWindow.cs:340-346,CreateElement(type, type)), disabled when the model cannot create it (:141). So create-in-group never makes a derived type, and an abstract listed type offers noCreate. - F7. Studio search filters the type pane only (
:112-120). - F8. Foldouts are in-memory (
CultCacheStudioDrawers.cs:33);OnDisablecloses the store (:48-51), domain reload included. The onlyEditorPrefskey isLastPathKey(:13). - F9. No drawer claims
CultRecordRef<>, so every ref reachesDrawRecordRef(CultCacheStudioDrawers.cs:254-280). - F10. The Studio editor sources compile outside Unity (netstandard2.1, C# 9, against
Unity 6000.3.24f1
Editor\Data\Managed\UnityEngine\*.dllwithout theUnityEditor.dllfacade, plus the builtGameCult.Caching*and trackedMessagePack*.dll). The 09-17 scratch project is gone; Hands rebuilds it in its own scratch from this recipe. - F11. The record row reads its click in
GUILayout.Toggle(:184); a drag start must be handled on a reserved rect before the toggle draws (IMGUI convention, not probed).
New since the map:
- F12. Variants (C1,
6ffe17a).CultInspectorModel(registry, CultCodec)(CultInspectorModel.cs:302-306); Studio still callsCultCacheMessagePack.CreateInspectorModel(registry)(CultCacheMessagePack.cs:33-34, unchanged).CultStoredDocument.Variantis the delta (CultCache.cs:374);Documentis the resolved view. A top-level override is aSetwhose path is one step at the member's slot (CultVariants.cs:72-73), so "is this grouped member overridden on this variant" is computable from the member'sSlot. - F13. Every Studio commit to a variant key is refused until C3.
CultInspectorEdit.Commitis a plainUpsertAsync(CultInspectorModel.cs:266-282); Q1a refuses a plain write at a variant key (CultCache.cs:2494-2496). A drop into a variant's inspector therefore reports that refusal until C3 lands. The release waits for C3, so no shipped Studio shows it, but the operator click-through must run after C3. - F14. Studio
Duplicateof a variant makes a flattened plain copy (CultCacheStudioWindow.cs:349-353clones the resolvedDocument). Not this campaign's defect; C3'sCreateVariantsits beside it. - F15. Element ids (C2a,
hands/variants-c2a, WIPdf74f8c). No grouping interaction: grouped members are top-level scalars and refs; ids live on list elements. Drops write a ref into the edit document; ids are untouched and minted on write for new elements; C3's diff keys overrides on them. Two consequences:- After C2a, registration refuses a document type whose object-list element lacks a
[CultElementId](CultCache.csdescriptor build, WIP). Cut 1's new fixtures carry no object lists. - C2a does not hide or lock the id member, so Studio shows it as an editable string.
Hand-editing one retargets overrides. That belongs to C3 (make
[CultElementId]members read-only in the model), not here; flagged for Self.
- After C2a, registration refuses a document type whose object-list element lacks a
- F16. The 1.0.60/1.4.0 release shipped (
0155ba8, tags on45c2f40) andcodex/asset-guid-attributeis merged. It already carried the 302,080-byte native bridge, so the 09-17 Q5 drift is spent. Native source has moved since (git diff --stat 45c2f40 main -- native/: 5 files, +840/-118), so this release rebuilds the bridge again. The v1 exports the managed side imports (src/GameCult.Mesh.Quic.Native/*.cs:263-276) remain atcultmesh_quic_native.cpp:1405-1465. - F17. Aetheria's schema moved.
ItemData.Manufactureris gone (slot 3 retired,ItemData.cs:281-282). The manufacturer relation now lives onFactionProductData(FactionProduct.cs:27,CultRecordRef<Faction> Manufacturer;:24,CultRecordRef<CraftedItemData> Design). Aetheria pinscultlib-unity-v1.0.60/caching-unity-v1.4.0/cultmath-unity-v0.2.4(Packages/manifest.json:54-56),CultLibRevision45c2f40and a separateCultMathRevision6d5e209(Directory.Build.props:6,10). - F18. After C2a, Aetheria cannot take the new CultLib without its element-id sweep
(
List<ItemRole>,List<BehaviorData>,List<AudioStat>,List<HardpointData>,List<ProductRole>,List<LoadoutSlot>, among others). So the Aetheria re-pin belongs to variants C4, and this campaign's Cut 4 shrinks to annotations and tests on top of it.
Identity and authority
| Thing | Owner | Lifecycle | Persisted |
|---|---|---|---|
[CultInspectorGroupBy] |
consumer source | compile time; nearest declaration wins; no arguments opts out | source only |
CultInspectorGrouping (resolved) |
model | cached per listed type like _shapes (CultInspectorModel.cs:296) |
no |
CultInspectorRecordGroup tree, node ids, labels |
model | rebuilt each OnGUI, as RecordCandidates is (CultCacheStudioWindow.cs:167) |
no |
| Group foldouts (node id → bool) | window | window memory; cleared by CloseStore (:318-327) |
no |
Drag payload { Model, Key } under "GameCult.CultCacheStudio.Record" |
Studio list (source) | one drag | no |
| Drop acceptance | IsRecordCandidate plus I9 gates |
per DragUpdated/DragPerform |
no |
| Created-in-group object | model makes; CultCache admits via UpsertAsync, as Add |
new plain record | store, on Save |
Forks
Q1-Q5 are ruled (header). Two remain.
Q6. RULED A (2026-09-30). How does the grouped list show a variant? (blocks nothing in Cuts 1-2; shapes C3's row lowering)
Mechanical defaults, not asked: a variant groups by its resolved values (I10), so a variant
that inherits its base's HullType sits in the base's node and moves when the base changes;
create-in-group always makes a plain record; C3's CreateVariant needs no preset because the
variant inherits its base's group; a variant is a valid drag payload.
- A. Flat by resolved value. A variant is an ordinary row in its resolved group. C3 adds a row marker ("variant of Longinus"). Which of its grouped values are overridden shows in the inspector through C3's per-member marker, not in the tree.
- B. Nested under the base. Inside a leaf, a variant row sits indented under its base's row when both share the leaf; otherwise it stands alone with the marker.
- C. A's rows plus an inherited/overridden glyph on the row for the grouped members.
- Recommendation: A. I2 stays one rule, the tree has one shape, and the base link already lives in C3's inspector. B makes the tree's shape depend on two relations, and splits whenever a variant overrides a grouped value. C duplicates C3's inspector marker.
Q7. RULED B (2026-09-30). Which Aetheria types group, and by what?
Candidates on codex/fire-control-12 (enums unless noted; [Flags] enums excluded):
| Type | Member | Anchor | Note |
|---|---|---|---|
SimpleCommodityData |
Category (SimpleCommodityCategory) |
ItemData.cs:310 |
legacy parity |
CompoundCommodityData |
Category (CompoundCommodityCategory) |
ItemData.cs:338 |
legacy parity |
GearData |
Hardpoint (HardpointType) |
ItemData.cs:454 |
legacy parity; WeaponItemData inherits when listed under GearData |
WeaponItemData |
WeaponType; or WeaponCaliber, WeaponRange |
ItemData.cs:485, :482, :479 |
re-declaration, applies when listing weapons |
HullData |
HullType |
ItemData.cs:519 |
legacy parity |
FactionProductData |
Manufacturer (ref Faction), then Design (ref CraftedItemData) |
FactionProduct.cs:27, :24 |
where "by manufacturer" now lives |
Loadout |
Hull (ref HullData) |
Loadout.cs:17 |
optional |
ConsumableItemData, CargoBayData, DockingBayData, Faction, PersonalityAttribute
have no groupable member worth a level.
- A. Legacy parity minus manufacturer: commodities by
Category,GearDatabyHardpoint,HullDatabyHullType. - B. A, plus
FactionProductDatabyManufacturerthenDesign, plusWeaponItemDatabyWeaponType. - C. B with
FactionProductDatabyDesignthenManufacturer(the market-segment view: who makes this design). - D. B plus
LoadoutbyHull. - Recommendation: B. It recovers everything the legacy window grouped, puts the manufacturer view where the manufacturer now is, and gives the weapon list its own axis. C is the better view if the operator authors products per design rather than per faction. Product record counts are unprobed; Cut 4's Hands reports them.
Defaults (recorded, not asked)
- Labels: enum
Enum.GetName, else the number; refRecordLabelof the candidate with that key (forFaction,Name, not legacyShortName); unset refNone; danglingMissing <key>; string its value or(empty); integer invariant string; boolFalse/True. - Order within a level: enums by underlying value; refs
Nonefirst, then label (OrdinalIgnoreCase, then key),Missinglast; stringsOrdinalIgnoreCasethen ordinal; integers numeric;falsebeforetrue. - Groupable kinds: String, Integer, Bool, Enum (not
[Flags]), RecordRef. Refused with a notice and a flat list: any other kind (Float included), a[Flags]enum,Hidden,IsReadOnly, a name that is not an inspector member of the listed type, a name given twice. - Global types are never grouped.
- Foldouts: window memory, collapsed by default, cleared by
CloseStore. Create-in-group,AddandDuplicateexpand the path to the new selection. NoEditorPrefs/SessionState. - Create into a
Missing <key>node presets the dangling key (keeps I5). - A drop of the current key is rejected (no no-op commit).
- Variants: see Q6's defaults and I10.
Sequence with document variants
| Cut | Files | Overlaps grouping? |
|---|---|---|
| Variants C2a (element ids, in flight) | CultCache.cs, new CultElementIds.cs, Mesh/Geometry documents, ElementIdTests.cs |
no |
Index cut ([CultIndex] means unique; queued behind C2a) |
CultCache.cs, Mesh/Geometry |
no |
| Variants C2b (nested paths; inferred from the brief, not yet written in the variants map) | CultCache.cs, CultVariants.cs |
no |
| Grouping Cuts 1-2 | CultInspectorModel.cs, CultInspectorAttributes.cs, CultInspectorModelTests.cs, CultCacheStudioWindow.cs, CultCacheStudioDrawers.cs, Studio README |
— |
| Variants C3 (Studio edits the delta) | CultInspectorModel.cs (edit, commit, diff, CreateVariant, Rebase), window (variant controls beside Duplicate, row marker), drawers (per-member marker and revert), CultInspectorModelTests.cs |
yes: all five grouping files |
Order: grouping Cuts 1-2 now, in parallel with C2a, the index cut and C2b (no shared
files). Grouping merges to main before C3's Hands starts; C3 needs C2b anyway. C3 then
rebases onto grouping and lowers its row marker inside the grouped row drawing that Cut 2
writes. Then one release (Cut 3), then one Aetheria pass (variants C4 plus Cut 4).
Where they touch: DrawRecords rows (CultCacheStudioWindow.cs:180-185; grouping rewrites,
C3 marks); the toolbar (:142-157; C3 adds variant buttons beside Duplicate, grouping
leaves it alone); DrawRecordRef (grouping adds the drop target; C3 marks at the member
level in DrawValue, not inside it); CultInspectorModel.cs (disjoint methods);
CultInspectorModelTests.cs (disjoint fixtures; name grouping fixtures Group*).
One release commit: Cut 3 below is the single release for both campaigns. The variants
map's C3 line "Then tag caching-unity-v1.5.0" should point here instead of owning a second
release step (proposal for Self).
Cuts
Hands for Cuts 1-2: branch hands/studio-grouping in a fresh worktree off origin/main
(Self picks the path, e.g. C:/wsNN-grouping). F:\Projects\CultLib-studio-grouping holds the
superseded docs-only codex/studio-grouping; do not reuse it. Do not touch the main checkout.
Mutation testing is Stryker.NET on the cut's diff; there are no hand-applied mutations.
Cut 1. CultLib model: declaration, tree, candidate and label authority (~150k tokens)
- Delete and collapse first:
CultInspectorModel.cs:519: the inlinetarget.IsInstanceOfType(record.Document) && record.Key.Value.Length > 0inRecordCandidates(:514-522) moves intoIsRecordCandidate;RecordCandidatesfilters through it (I6).:36and:209:member.GetCustomAttributes(true)→Attribute.GetCustomAttributes(member, true)(F5).
- Add in
CultInspectorAttributes.cs, beforeCultInspectorDrawerAttribute(:81-86):CultInspectorGroupByAttribute(params string[] members),AttributeUsage(Class, Inherited = true, AllowMultiple = false), propertyMembers, with a comment in the file's style stating nesting, nearest-wins and opt-out. - Add in
CultInspectorModel.cs(or a siblingCultInspectorGrouping.cs, same namespace; one file while it stays under ~800 lines; it is 645 now):CultInspectorGrouping { Type ListedType; IReadOnlyList<CultInspectorMember> Members; string? Notice }(members empty whenNoticeis set).CultInspectorRecordGroup { string Id; string Label; int Depth; IReadOnlyList<object?> Values; IReadOnlyList<CultInspectorRecordGroup> Children; IReadOnlyList<CultStoredDocument> Records; int Count }.GroupingOf(Type listed), cached likeShapeOf(:324-333): names resolve againstMembersOf(listed)(:335-339); validation per Defaults.GroupRecords(Type listed, IEnumerable<CultStoredDocument> records): partitionsRecordCandidates(CultRecordRef<listed>)by value identity, reading each record'sDocument(resolved, I10).CreateInGroup(Type listed, CultInspectorRecordGroup group, out string? notice):CreateElement(listed, listed, out notice)(:387-398), thenSetValueeach level; refs viaCreateRecordRef(:524-529).ArgumentExceptionfor a node not oflisted's current grouping.IsRecordCandidate(Type refType, CultStoredDocument record).RecordRefLabel(Type refType, object? value, IEnumerable<CultStoredDocument> records):None,RecordLabel(:315-319), orMissing <key>.
- Tests in
tests/GameCult.Caching.Tests/CultInspectorModelTests.cs(476 lines; fixtures:299-476). New fixturesGroupBase(abstract) /GroupLeaf : GroupBase[CultDocument], a re-declaring type and an opting-out type; members: enum declared out of alphabetical order,CultRecordRef<InspectOther>, string, float,[Flags]enum, hidden int, read-only int. No object lists (F15). Behaviours, one test each:- levels nest in declared order, not slot order;
- a
GroupLeaflisted underGroupBasegroups by the base declaration (I3); - nearest declaration wins; an empty declaration opts out;
- leaves partition the candidates over unset refs, dangling refs and undefined enum values (I2);
- two refs sharing one
[CultName]give two nodes;Nonefirst;Missing klast (I4, I7); - enum nodes order by value;
- create at depth 2, upsert into a temp single-file
CultCache, regroup: same node id (I5); - each invalid declaration (unknown, float,
[Flags], hidden, read-only, duplicate) gives a notice and a flat tree, no throw (I8); IsRecordCandidateaccepts a subtype, refuses a sibling and an empty key, andRecordCandidatesequals the records it accepts (I6);- a base abstract property's
[CultInspectorLabel]reaches the override's metadata (F5); - a variant groups by its resolved value: base with
Enum=A, variant overriding it toBviaUpsertVariantAsyncandOverride, regroup: the variant is inB; change the base toCwith the variant not overriding it, regroup: it follows toC(I10).
- Verify (Yggdrasil):
ygg-verify.sh /f/Projects/CultLib <rev> dotnet 'dotnet test tests/GameCult.Caching.Tests -c Release', thenygg-verify.sh /f/Projects/CultLib <rev> dotnet 'dotnet tool install -g dotnet-stryker && export PATH="$PATH:$HOME/.dotnet/tools" && cd tests/GameCult.Caching.Tests && dotnet stryker --project GameCult.Caching.csproj --since:<base-sha> > /tmp/s.log 2>&1; rc=$?; tail -60 /tmp/s.log; exit $rc'. Survivors triaged by name in the report. - Ledger: model ~+170, attributes ~+15, tests ~+230; one predicate collapsed.
Cut 2. CultLib Studio: lower the tree, create in group, drag source, drop target (~150k tokens)
- Delete first:
CultCacheStudioDrawers.cs:266inline"Missing " + key : "None"→Model.RecordRefLabel(type, value, Records)fornames[0]whenindex == 0.CultCacheStudioWindow.cs:180-185flatforeach→ tree drawing.
- Window (
CultCacheStudioWindow.cs):- fields (
:13-30):Dictionary<string,bool> _groupFoldouts(ordinal),const string RecordDragKey;CloseStore(:318-327) clears them. DrawRecords(:135-189):GroupingOf(_selectedType); aNoticeshows a warningHelpBox. DrawGroupRecords(_selectedType, _records)recursively: foldoutLabel (Count)indented byDepth; an expanded node draws children, leaf rows, then aCreatemini button underAdd's gate (:141-146). The root is not a foldout;Addstays root create.- row: reserve the rect with
GUILayoutUtility.GetRect, handleMouseDragthere first (PrepareStartDrag,SetGenericData(RecordDragKey, new CultCacheStudioRecordDrag(_model, key)), emptyobjectReferences,StartDrag(label),Use()), thenGUI.Toggle(rect, ...)(F11). Keep the existing<Type>suffix logic (:182-183) in one place for C3 to extend. AddInGroup(node)mirrorsAdd(:340-346) throughRun(:369); selects the key and expands the node's id prefixes.Add/Duplicatereveal the new record's path (a_revealKeyresolved next frame is acceptable).
- fields (
- Drawers (
CultCacheStudioDrawers.cs):internal sealed class CultCacheStudioRecordDrag { Model; Key }. InDrawRecordRef(:254-280), capture theHorizontalScoperect; onDragUpdated/DragPerforminside it with our payload:CopywhenGUI.enabled, sameModel, key differs, record inRecords, andIsRecordCandidate; elseRejected. Leave foreign drags alone. On perform:AcceptDrag,next = key,GUI.changed = true,Use(); return through the existing:279. - README (
src/GameCult.Unity/Assets/Caching/README.md): grouping, create in group and drag-to-reference after the paragraph at:30-34; the grouping API,IsRecordCandidate,RecordRefLabelunder## Inspection Model(:39). - Verify:
- Cut 1's Yggdrasil test command still passes.
- Negative greps (each empty):
rg -n "IsInstanceOfType" src/GameCult.Unity/Assets/Caching/Editor | rg -v "assetType.IsInstanceOfType"(I6;:215is the asset-GUID sub-asset check, not candidacy);rg -n "\"Missing \"" src/GameCult.Unity/Assets/Caching/Editor(I7; the union drawer's"None"at:393is not a ref label, so grepMissingonly);rg -n "SetValue|GetCustomAttribute|CultInspectorGroupBy" src/GameCult.Unity/Assets/Caching/Editor/CultCacheStudioWindow.cs(I1);rg -n "EditorPrefs" src/GameCult.Unity/Assets/Caching/Editorshows onlyLastPathKey. - Starfire, one job, editor closed: the F10 compile gate,
dotnet buildof a scratch netstandard2.1 project over the worktree'sAssets/Caching/Editor/*.cs. 0 errors.
- Ledger: window ~+80, drawers ~+30, README ~+20; two inline decisions deleted.
Cut 3. Joint release: cultlib Unity 1.0.61, Studio 1.5.0 (after grouping and variants C3 merge)
- Base:
mainwith grouping Cuts 1-2 and variants C1-C3 merged; a clean worktree. - Files:
unity/org.gamecult.cultlib/package.json:41.0.60→1.0.61;src/GameCult.Unity/Assets/Caching/package.json:41.4.0→1.5.0and:13dependency1.0.61;CHANGELOG.mdentries in both packages (grouping, create in group, drag to reference; variant editing from C3); plugins viapowershell -File scripts\build-unity-package.ps1 -UpdateTemplate(Windows: MSVC and the pinned MsQuic digests; its version check is at:168-170). - Tags:
cultlib-unity-v1.0.61,caching-unity-v1.5.0; nocultmath-unitytag. - Verify (Starfire, one job at a time):
- byte check: rerun the script;
git status --porcelain unity/is empty; - expected diff:
GameCult.Caching.dll/.pdbchanges, andgamecult_mesh_quic_native.dllchanges (F16);msquic.dllshould not (same pinned digest). Anything else stops the release; - API present: reflect over the tracked
GameCult.Caching.dllforCultInspectorGroupByAttribute,GroupRecords,CreateInGroup,IsRecordCandidate,RecordRefLabel, and C3'sCreateVariant; - native ABI:
dumpbin /exportslistscultmesh_quic_open,_state,_poll,_error,_close; git ls-remote --tags originlists both tags.
- byte check: rerun the script;
Cut 4. Aetheria: grouping annotations (inside variants C4's pass; blocked on Q7)
- Where: the Aetheria branch and worktree Self opens for variants C4. Never
F:\Projects\Aetheria's working tree. C4 owns the pin bump (Directory.Build.props:6, andPackages/manifest.json:54-55to#caching-unity-v1.5.0/#cultlib-unity-v1.0.61;:56cultmath unchanged;packages-lock.jsonfollows) and the element-id sweep (F18). This cut adds only:- Q7's
[CultInspectorGroupBy(...)]class attributes (recommendation B: five declarations); tests/Aetheria.Shared.Tests/StudioGroupingTests.cs(xUnit):GroupingOfoverCultCacheMessagePack.CreateInspectorModel(registry)names exactly the declared members per annotated type with no notice;GroupingOf(typeof(WeaponItemData))follows Q7; every registered document type'sGroupingOfhas a null notice.
- Q7's
- Verify:
- headless (Starfire, one job; the revision check needs
CultLibRootclean at the release SHA andCultMathRootclean at6d5e209):dotnet test tests\Aetheria.Shared.Tests -p:CultLibRoot=<clean worktree at release SHA> -p:CultMathRoot=<clean worktree at 6d5e209>; - Unity 6000.3.24f1 batchmode compile, editor closed:
-batchmode -nographics -quit -projectPath <Aetheria worktree> -logFile <scratch>; 0 errors, both new tags resolved.
- headless (Starfire, one job; the revision check needs
- Operator only, in Aetheria's Studio against a copy of
GameData/Aetheria.cc:- each annotated type shows the declared levels;
GearDataincludes<WeaponItemData>rows; - create in a node: the record lands there and is selected;
- drag a
Factiononto aFactionProductData.Manufacturer: accepted; drag a gear record onto it: rejected; - drag a
PersonalityAttributeonto aDemandProfilekey that already exists: refused through the notice; - read-only open: every drop rejected;
- a variant (once C4 authors one, e.g.
LonginusX) sits in its resolvedHullTypenode, and a drop into its inspector writes an override (C3), not a refusal; - close without edits: store bytes unchanged.
- each annotated type shows the declared levels;
Subtraction ledger
Added: one attribute, two model types, five model methods, ~11 CultLib and ~3 Aetheria tests,
~110 Studio lines, five annotations. Collapsed: the candidate predicate (now one method, reused
by drop), the ref label strings, the property-inheritance miss (F5). Not added: selectors,
registries, EditorPrefs keys, runtime ports, packages, targets, daemons, a second release.
Build footprint: GameCult.Caching and its tests (Yggdrasil); the F10 compile gate, the
package script (all tracked plugins including the native bridge) and Aetheria's headless and
batchmode checks (Starfire; Windows is the target for the editor plugin and win-x64 bridge).
What the 2026-09-17 map had wrong as of feeced8
- Cut 4 grouped
GearDatabyManufacturer;ItemData.Manufacturerno longer exists (F17). - Q5's native-plugin drift was spent by the 1.0.60 release (F16); the Cut 3 "rebase onto
codex/asset-guid-attribute" note is obsolete (merged). - Cut 4 re-pinned Aetheria on its own; after C2a it cannot, without the id sweep (F18).
- Aetheria branch
codex/cultcache-cutover, props:5, manifest:53-55, cultmathv0.2.3: nowcodex/fire-control-12,:6,:54-56,v0.2.4with a separate CultMath pin. - Negative greps for
IsInstanceOfTypeand"None"would fail on unrelated lines (:215,:393). - Hand-applied mutations per test are retired; Stryker.NET on the diff replaces them.
- Moved anchors:
RecordCandidates:512-520→:514-522;_shapes:298→:296;RecordLabel:313-317→:315-319;CreateElement:385-396→:387-398; drawer attribute:80→:81-86;DrawRecordRef:195-221→:254-280, label:207→:266;ReplaceKeyrouting:243-251→:303-309; test fixtures:380-459→:299-476; README:36-39→:30-39; build-script version check:150-155→:168-170. - Worktree
F:\Projects\CultLib-studio-groupingis now the superseded docs branch; Hands uses a fresh one.