12 KiB
Modern runtime J3.4 — canonical object-table ownership
Status: COMPLETE
Parent: 2026-07-25-modern-runtime-slice-j3.md
Required production base: e937cc36df39cf6ea1eaba2f9c0243d1929da702
Prior closeout: docs/research/2026-07-25-slice-j3-3-exact-projection-store.md
Production commit: 5ef8b5371d8990f0380acd939e71cd711289d429
Closeout: docs/research/2026-07-26-slice-j3-4-canonical-object-lifetime.md
Objective
Make one presentation-independent Runtime lifetime-group root own both:
- the canonical
RuntimeEntityDirectory; and - the existing canonical
ClientObjectTable.
App, retained UI, interaction controllers, and the graphical projection host must borrow those exact same instances. There must be no copied inventory model, second object table, asynchronous delivery seam, extra input frame, or change to retail event order.
This slice moves ownership and transaction boundaries, not the
ClientObjectTable implementation out of AcDream.Core. The type is already
presentation-free and has extensive Core conformance coverage.
Current ownership inventory
At the required base:
LiveEntityRuntimeconstructs its ownRuntimeEntityDirectory.GameWindowconstructs a public readonlyClientObjectTable Objects.- App composition records pass that table to retained UI, selection, interaction, magic, combat, paperdoll, appraisal, cooldown, and diagnostics.
- Runtime
LiveSessionEventRouteralready applies property, stack, inventory, player-description, container, and appraisal traffic directly to the borrowed table throughObjectTableWiring. - App
LiveEntityHydrationControllerapplies CreateObject to the table only after canonical Runtime create acceptance and revalidatesCreateIntegrationVersion. - App
LiveEntityDeletionControllerapplies logical DeleteObject before the exact App projection teardown suffix. - App
LiveSessionResetManifestclears the object table before clearing live graphical projections. - Optimistic moves, confirmations, rollback, stack updates, container replacement, and every table event are synchronous on the current thread.
Retail behavior and citations already live in:
src/AcDream.Core/Items/ClientObjectTable.cs;src/AcDream.Core.Net/ObjectTableWiring.cs;docs/research/2026-07-13-retail-give-item-pseudocode.md;- the named-retail symbols cited beside
ACCObjectMaint::CreateObject,ACCObjectMaint::DeleteObject, andACCWeenieObject::ServerSaysMoveItem.
J3.4 does not reinterpret those algorithms.
Fixed target
Add a presentation-free Runtime owner equivalent to:
public sealed class RuntimeEntityObjectLifetime
{
public RuntimeEntityDirectory Entities { get; }
public ClientObjectTable Objects { get; }
}
The concrete name may change, but these invariants do not:
- The owner constructs each canonical instance exactly once.
LiveEntityRuntimereceivesEntities; it may not construct a directory.- Runtime session routing receives
Objectsfrom the same owner. - App projection, UI, and interaction composition receive borrowed references only.
- Accepted CreateObject/DeleteObject object-table mutation is implemented by the Runtime group and revalidates the exact canonical record/version.
- Reset preserves the current order: object-table clear before graphical live projection teardown, with completed stages never replayed.
- Studio/sample-data object tables remain independent fixture owners and are not part of the live session.
Execution
1. Pin parity traces and construction identity
- Capture normalized traces for:
- initial player description and equipment manifest;
- item CreateObject add/update;
- same-incarnation refresh;
- new-generation replacement;
- property Int/Int64/String/Float/DataID updates;
- stack-size update;
- optimistic move, authoritative confirm, and rejection rollback;
- wield/unwield;
- ViewContents replacement and close;
- logical delete, GUID reuse, and session clear.
- Add a construction-identity test proving the router, App projection host, retained UI source, and interaction source all receive one table instance.
- Preserve current synchronous event order in the golden trace.
2. Introduce the Runtime lifetime-group root
- Add the owner under
src/AcDream.Runtime/Entities/. - Construct
RuntimeEntityDirectoryandClientObjectTablethere. - Expose borrowed properties only; no collection mirror or event repeater.
- Add Runtime-only tests that instantiate it without loading App, UI, Silk, OpenAL, Arch, or ImGui assemblies.
- Keep mutable state instance-scoped so two roots can hold conflicting GUIDs and inventory contents without interference.
3. Inject canonical identity into the App projection host
- Change
LiveEntityRuntimeto require the exactRuntimeEntityDirectoryinstance from the group. - Remove its internal
new RuntimeEntityDirectory(...). - Preserve the existing local-ID start override for focused wraparound tests through an explicit fixture/group construction seam, not a second production allocator.
- Assert that App cannot replace the directory after projection construction.
- Preserve J3.3
RuntimeEntityKeyidentity and teardown behavior unchanged.
4. Replace the GameWindow object-table owner
- Construct the Runtime group before retained UI and live presentation composition.
- Delete
GameWindow's public readonlyClientObjectTable Objectsfield. - Pass
runtimeGroup.Objectsthrough the existing typed composition records. - Keep App controllers as direct synchronous borrowers; do not wrap every read in a copied DTO or event bus.
- Add source guards rejecting a production App
new ClientObjectTable()and a second Runtime entity directory. Exempt Studio/sample-data and tests.
5. Move accepted object mutation to the Runtime group
- Move the generation/version-sensitive
ObjectTableWiring.ApplyEntitySpawn/Deletetransaction boundary out of App. - Runtime must apply a CreateObject only after the canonical directory accepts that exact incarnation and while its create-integration version remains current.
- New-generation replacement must remove the old object generation and publish the same add/update/removal sequence as today.
- Logical delete must mutate the table before App graphical teardown, exactly
as the current
beforeTeardowncallback does. - Pickup/parent leave-world must not delete the object-table entry.
- Dormant liveness pruning must retain the existing object-table semantics; only an authoritative logical delete removes the retained object generation.
- Keep all callbacks synchronous and revalidate after every callback that can replace, delete, or clear the incarnation.
6. Route session and reset ownership
- Bind J2
LiveSessionEventRouterdirectly from the Runtime group rather than an App-owned table field. - Keep the current pre-Connect subscription order.
- Keep local-player property projection to
LocalPlayerStateand the same table instance until J4 moves the coherent player/gameplay state group. - Replace the App reset field with a Runtime-group object-table reset acknowledgement at the same manifest position.
- Failure injection must retain the unfinished reset suffix and never clear a replacement session's table.
7. Delete superseded ownership seams
- Remove public ownership comments and aliases that describe the table as a GameWindow state owner.
- Remove any App construction helper whose only purpose was to allocate the live table.
- Keep direct table parameters on UI/controllers for now: they are explicit borrowed dependencies and J4 will move higher-level inventory transactions.
- Update architecture, plans, durable memory, and exact rollback in the same closeout commit.
Adversarial cases
- Reentrant
ObjectAddeddeletes or replaces the same GUID. - Reentrant
ObjectMovedstarts another optimistic move. - Delete arrives during CreateObject callbacks.
- Same GUID with a new
INSTANCE_TSwhile old projection teardown is pending. - Unknown-object authoritative move publishes the same nullable item event.
- Equal, stale, and wrapped object/physics generations.
- Multiple in-flight optimistic moves followed by confirm/reject in either order.
- Container replacement while item events mutate the same container.
- Reset during object-table event dispatch.
- Failed graphical teardown after canonical object removal.
- Two Runtime roots use identical GUIDs with different contents.
- No-window root executes the full entity/object lifecycle without loading App or backend assemblies.
Mandatory gates
Run in this order:
- Runtime entity/object lifetime tests.
- Core
ClientObjectTable*andObjectTableWiring*tests. - Focused App hydration, deletion, reset, inventory, interaction, paperdoll, magic, cooldown, and runtime-adapter tests.
- All Runtime tests.
- All App tests.
dotnet build AcDream.slnx -c Release.dotnet test AcDream.slnx -c Release --no-build.- Exact-binary connected lifecycle/reconnect gate.
- At every connected checkpoint: zero render-shadow mismatches, zero pending deltas, graceful process exit, and object/projection teardown convergence.
A user visual pause is not required unless an automated or connected gate exposes an inventory, paperdoll, selection, or interaction symptom.
Acceptance
- One Runtime lifetime-group root owns the exact entity directory and object table.
GameWindowowns neither instance directly.- App and Runtime routing borrow the same object table.
- Runtime remains the only GUID/incarnation/local-ID authority.
- Create/delete/property/container ordering matches the pinned trace.
- Optimistic move/rollback and press-time UI remain synchronous.
- No App/UI/backend dependency enters Runtime.
- No mirror, queue, frame delay, or steady-state traversal allocation is added.
- Runtime-only entity/object lifecycle and multiple-instance isolation pass.
- Complete Release and exact connected gates pass on one committed binary.
Rollback
The exact J3.4 rollback is:
git revert 5ef8b5371d8990f0380acd939e71cd711289d429
J3.3 remains independently reversible; do not use its rollback for a J3.4 failure.
Result
RuntimeEntityObjectLifetime now constructs and owns the one
RuntimeEntityDirectory and one live ClientObjectTable. GameWindow,
session routing, retained UI, interaction, and the graphical projection host
borrow those exact instances.
Accepted CreateObject mutation revalidates the exact canonical record and
CreateIntegrationVersion before, during, and after synchronous object-table
callbacks. Authoritative DeleteObject acceptance uses an exact, single-use
Runtime token: the active identity is retired first and retained-object
removal then commits before App resource teardown. Dormant/offscreen pruning
does not remove retained object information.
Final gates:
- Runtime: 118 passed;
- App: 3,762 passed / 3 skipped;
- complete Release solution: 8,453 passed / 5 skipped;
- exact-binary connected lifecycle/reconnect:
logs/connected-world-gate-20260726-055713/report.json; - two sessions, seven checkpoints, graceful zero-code exits, zero failures, zero render-shadow mismatches, zero pending deltas, and zero pending live teardown/publication/retirement work.
Final J3 goal
J3.4 is not the end of J3. J3.5 must publish one ordered per-session entity/object delta stream and make graphical/direct hosts consume the same committed facts without a second queue. J3.6 then failure-injects and proves the complete no-window entity/object lifecycle, zero leaked records, tombstones, projections, container entries, queued deltas, or subscriptions, plus Release and connected parity. That J3.6 proof is the clean boundary before J4 moves inventory transactions, vitals, enchantments, magic, and other gameplay-state services.