Retires the GL framing from the documents that described a two-backend,
two-UI-stack client, and files what the deletion left behind.
Divergence register:
* AD-46 (anisotropic tap pattern in dense alpha scenery) is REFRAMED rather
than retired. Its substance survives -- distant foliage may read denser
than retail's -- but it was measured GL-vs-Vulkan, and with GL gone it is
a Vulkan-vs-retail question against the D3D oracle it already cited. The
measurement is kept as the evidence that the residual is a driver tap
pattern; the row now records that it is no longer falsifiable by
self-differential, which is a real loss the deletion causes.
* AD-47 and AD-48 are NEW, and the campaign's own risk register scheduled
them here: MSAA sample positions (measured at 8.83% of the frame at 4x,
which is why every strict gate runs MSAA off -- and therefore why a
regression confined to the multisample path would not be caught) and
present pacing (#235 is the live instance).
* AD-17's justification moves from a GL clip-plane citation to Vulkan's
maxClipDistances floor, which is the same 8, so the divergence is
unchanged and only its authority moves.
* AP-92 keeps IUiViewportRenderer.TextureIsBottomUp rather than folding it
flat, because it is what let the origin question be answered by data.
Architecture and code structure: the layer diagram, the frame order, the
residency vocabulary and the reference table all said OpenGL. The UI section
said two stacks. Rule 3's rationale is rewritten around what actually
happened -- ImGui was deleted and not one panel, ViewModel or command had to
change, because none of them had ever imported ImGuiNET. That is the rule
paying for itself, so it is recorded as evidence rather than removed as
obsolete.
Issues: #258 files the dev-panel host as a decision rather than an accident,
and #255 is REOPENED. Its TaskCreationOptions.LongRunning fix asks the
scheduler for a thread but does not promise two callbacks overlap; under nine
concurrent test assemblies it still failed 2 of 5 whole-suite runs. The
earlier evidence tested a narrower pool, not a contended one. The fix it
needs is a rendezvous inside the read stub -- not a weakened assertion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
853 lines
56 KiB
Markdown
853 lines
56 KiB
Markdown
# acdream — code structure & extraction sequence
|
||
|
||
**Status:** Living document. Created 2026-05-16; implementation reconciliation
|
||
completed 2026-07-21; Slices 1–8 and their automated closeout landed by
|
||
2026-07-22. The connected visual matrix passed 2026-07-23; the campaign is
|
||
complete and new M4 subsystems may enter through the extracted owners.
|
||
**Purpose:** Describe the desired structural state of the App layer,
|
||
explain the rules we've adopted, and lay out the safe extraction
|
||
sequence from today's reality (one 15,723-line `GameWindow.cs` at the
|
||
2026-07-21 audit) to the
|
||
target (thin `GameWindow`, small focused collaborators).
|
||
**Companion to:** [`acdream-architecture.md`](acdream-architecture.md)
|
||
(the layered architecture) and
|
||
[`worldbuilder-inventory.md`](worldbuilder-inventory.md) (what we take
|
||
from WB vs port ourselves).
|
||
|
||
---
|
||
|
||
## 1. The structural problem we're solving
|
||
|
||
The layered architecture works: `AcDream.Core` is backend-free, the network
|
||
layer is wire-compatible, the UI has a stable contract, plugins load.
|
||
The structural debt is concentrated in **one file**:
|
||
|
||
```
|
||
baseline cf50ee3d 15,723 lines / 278 fields / 205 methods
|
||
after Slice 1 14,912 lines / 278 fields / 191 methods
|
||
after Slice 2 14,546 lines / 277 fields / 190 methods
|
||
after Slice 3 14,310 lines / 274 fields / 190 methods
|
||
after Slice 4 10,301 lines / 267 fields / 163 methods
|
||
after Slice 5 closeout 8,811 lines / 247 fields / 153 methods
|
||
after Slice 6 closeout 7,026 lines / 241 fields / 108 methods
|
||
after Slice 7 draw-frame cutover 4,666 lines / 196 fields / 70 methods
|
||
after Slice 8 checkpoint D 4,330 lines / 192 fields / 67 methods
|
||
after Slice 8 checkpoint E 4,266 lines / 194 fields / 65 methods
|
||
after Slice 8 checkpoint F 4,057 lines / 198 fields / 54 methods
|
||
after Slice 8 checkpoint G 3,663 lines / 162 fields / 37 methods
|
||
after Slice 8 checkpoint I 1,945 lines / startup composition shell
|
||
after Slice 8 checkpoint J 1,625 lines / focused lifetime shell
|
||
after Slice 8 checkpoint K 1,622 lines / canonical soak shell
|
||
```
|
||
|
||
At the campaign baseline, `GameWindow` was the single object that:
|
||
|
||
- Owns the Vulkan device, the window, input, and shaders.
|
||
- Reads ~40 different environment variables across its lifetime.
|
||
- Composes the shipped `LiveSessionController`/host/router boundary; it no
|
||
longer owns a parallel session, command bus, subscription lifetime, connect,
|
||
character-entry, reconnect, or graceful-close body.
|
||
- Owns the adapters that hydrate canonical Runtime entity records into
|
||
exact-key App projection sidecars and their animation, collision, rendering,
|
||
and DAT-backed appearance resources.
|
||
- Composes the shipped `WorldSelectionQuery` and
|
||
`SelectionInteractionController`; it no longer owns world-picking,
|
||
selection intent, Use/PickUp, or auto-walk deferral algorithms.
|
||
- Drives per-frame render orchestration (sky → terrain → opaque mesh →
|
||
transparent mesh → particles → debug lines → UI).
|
||
- Builds and applies streamed landblock presentation (DAT decode, scenery,
|
||
EnvCells, mesh publication, collision, and retirement) around the shipped
|
||
`StreamingController` and `GpuWorldState` owners.
|
||
- Wires up every plugin hook sink, every diagnostic, every panel.
|
||
|
||
The completed slices moved those bodies into focused, tested owners. The rule
|
||
that governed the campaign still applies: a collaborator is a completed
|
||
extraction only when it owns the state and behavior body; wrapping a
|
||
`GameWindow` method in a delegate preserves ordering but remains a partial
|
||
extraction.
|
||
|
||
The fix is **not** "rewrite `GameWindow` in one pass" — that is a
|
||
high-risk change. The fix is to **extract one
|
||
collaborator at a time**, verify behavior is unchanged, ship, and
|
||
move on. This document defines that sequence.
|
||
|
||
---
|
||
|
||
## 2. Code Structure Rules — the discipline
|
||
|
||
Recap of the rules from `CLAUDE.md` with the rationale:
|
||
|
||
### Rule 1: No new substantial feature bodies in `GameWindow.cs`
|
||
|
||
**Why:** New feature bodies in the native shell would reverse the ownership
|
||
campaign. Runtime work belongs in focused App owners, with only construction
|
||
and narrow callback handoffs retained here.
|
||
|
||
**How to apply:** A new feature gets its own class under
|
||
`src/AcDream.App/<Subsystem>/` (or deeper in `AcDream.Core` if it's pure
|
||
logic). `GameWindow` owns a field and a wiring call, nothing more. If
|
||
you find yourself adding a 200-line method to `GameWindow`, stop and
|
||
extract.
|
||
|
||
**Exemption:** Trivial wiring that *must* stay in `GameWindow` because
|
||
it touches device state during `OnLoad` is acceptable, but should still
|
||
delegate to a collaborator for the substance.
|
||
|
||
### Rule 2: `AcDream.Core` must not depend on window / backend projects
|
||
|
||
**Why:** Core is the backend-free, testable layer. The moment Core imports
|
||
a graphics or windowing namespace, we've lost the ability to test it without
|
||
a device, and the layer split becomes fiction.
|
||
|
||
**How to apply:** Phase O removed both external WorldBuilder/backend project
|
||
references. The only currently allowed seams are the backend-free helpers owned in
|
||
our tree under `src/AcDream.Core/Rendering/Wb/`: `TerrainUtils`,
|
||
`TerrainEntry`, `RegionInfo`, `SceneryHelpers`, and `TextureHelpers`.
|
||
`ObjectMeshManager` and every GPU resource owner remain in App. If Core needs a
|
||
new capability, define a narrow Core interface and implement it in App; adding
|
||
a new project reference requires an inventory-doc update explaining why.
|
||
|
||
### Rule 3: UI panels target `AcDream.UI.Abstractions` only
|
||
|
||
**Why:** The rule was written to keep D.2b (the retail-look backend)
|
||
viable while ImGui was still the developer stack, and **it paid off**: when
|
||
Campaign V slice V11 deleted ImGui on 2026-07-29, not one panel contract,
|
||
ViewModel or command had to change, because none of them had ever imported
|
||
`ImGuiNET`. `AcDream.UI.Abstractions` survived a backend deletion intact.
|
||
|
||
**How to apply:** A panel's `using` block must mention
|
||
`AcDream.UI.Abstractions.*` and nothing from any backend assembly. The panel
|
||
writes against `IPanelRenderer`; a renderer implementation translates those
|
||
calls at runtime. Plugin-facing UI follows the same rule.
|
||
|
||
**Status:** there is currently no `IPanelRenderer` implementation in the tree —
|
||
the ImGui one went with V11 and the replacement is issue **#258**. The contract
|
||
is kept rather than deleted precisely because this rule proved its worth; a new
|
||
host binds to it without touching a single panel.
|
||
|
||
### Rule 4: Startup env vars enter through `RuntimeOptions`
|
||
|
||
**Why:** Environment variables are global mutable state. Reading them
|
||
at random call sites means (a) duplicated `Environment.GetEnvironmentVariable`
|
||
boilerplate, (b) no single place to see "what flags does the client
|
||
respond to?", (c) impossible to unit-test parsing.
|
||
|
||
**How to apply:** `src/AcDream.App/RuntimeOptions.cs` is the typed
|
||
options object. `Program.cs` builds it once from args + env and passes
|
||
it to `GameWindow`. New startup flags add a field to `RuntimeOptions`
|
||
and a parser in `RuntimeOptions.FromEnvironment`. They don't add
|
||
`Environment.GetEnvironmentVariable` reads.
|
||
|
||
**Scope:** `RuntimeOptions` is for **startup-time** configuration —
|
||
things that don't change once the window is up. Runtime diagnostic
|
||
toggles are Rule 5's domain.
|
||
|
||
### Rule 5: Runtime diagnostic toggles live in diagnostic owner classes
|
||
|
||
**Why:** Diagnostic flags (`ACDREAM_DUMP_MOTION`, `ACDREAM_PROBE_*`,
|
||
etc.) need to be both env-readable at startup *and* runtime-toggleable
|
||
from the DebugPanel. Per-call-site env reads can't be runtime-toggled.
|
||
|
||
**How to apply:** Today's template is
|
||
`src/AcDream.Core/Physics/PhysicsDiagnostics.cs` — one static class with
|
||
typed `Probe*` properties read from env vars once at startup, plus
|
||
runtime setters that the DebugPanel binds. New diagnostic flags follow
|
||
this shape, not the per-call-site pattern that dominates `GameWindow.cs`.
|
||
|
||
**Cleanup direction:** The dozens of existing `ACDREAM_DUMP_*` reads
|
||
inside `GameWindow.cs` are tech debt. We do NOT bulk-migrate them as
|
||
part of this refactor — they're working, they're scattered, and
|
||
moving them carries risk without a current acceptor. We migrate them
|
||
opportunistically: when a `GameWindow` extraction lands and a diagnostic
|
||
moves with it, route it through the new owner's diagnostic class.
|
||
|
||
### Rule 6: Tests live in the project matching the layer
|
||
|
||
**Why:** Test discoverability + dependency hygiene. A test for a Core
|
||
class belongs next to other Core tests; a test for an App class belongs
|
||
in an App test project. Co-locating tests across layers makes the
|
||
dependency graph dishonest.
|
||
|
||
**How to apply:** One test project per source project that has tests.
|
||
Today:
|
||
|
||
- `tests/AcDream.Core.Tests/` ← `src/AcDream.Core/`
|
||
- `tests/AcDream.Core.Net.Tests/` ← `src/AcDream.Core.Net/`
|
||
- `tests/AcDream.Runtime.Tests/` ← `src/AcDream.Runtime/`
|
||
- `tests/AcDream.UI.Abstractions.Tests/` ← `src/AcDream.UI.Abstractions/`
|
||
- `tests/AcDream.App.Tests/` ← `src/AcDream.App/`
|
||
|
||
`tests/AcDream.App.Tests/` now exists and owns App-layer controller, streaming,
|
||
render-resource lifetime, retained-UI, and `RuntimeOptions` tests. New App tests
|
||
belong there; do not place backend-free Core behavior in that project merely because
|
||
App currently wires it.
|
||
|
||
---
|
||
|
||
### Render-frame ownership versus recursive reachability
|
||
|
||
The typed `RenderFrameOrchestrator` graph owns frame sequencing and borrows
|
||
long-lived runtime/presentation owners. The enforceable ownership invariant is
|
||
that the orchestrator and its immediate phase owners retain neither
|
||
`GameWindow`, an anonymous callback bag, nor direct window delegates. It is
|
||
**not** an invariant that recursively walking every borrowed canonical owner
|
||
can never reach an event subscriber or callback that was composed by
|
||
`GameWindow`; that claim would be false and would confuse object-graph
|
||
reachability with frame ownership.
|
||
|
||
Known recursive callback-bearing paths include (this is evidence and design
|
||
documentation, not an exhaustive allowlist):
|
||
|
||
- `RetainedGameplayUiFrame -> RetailUiRuntime -> RetailUiRuntimeBindings` for
|
||
retained gameplay UI state and commands.
|
||
- `PrivatePresentationRenderer -> PaperdollFramePresenter ->
|
||
RetailPaperdollFrameView/PaperdollInventoryVisibility -> UiElement tree` for
|
||
private paperdoll visibility and texture publication. This is a second,
|
||
independent retained-UI entry because `UiElement.Parent` links back to the
|
||
shared root and sibling controls.
|
||
- `DevToolsFramePresenter -> DevToolsPanelSet -> panel/ViewModel bindings` for
|
||
the optional developer UI.
|
||
- `WorldRenderFrameBuilder -> RuntimeWorldFrameSettingsPreview ->
|
||
IRuntimeSettingsPreviewSource -> RuntimeSettingsController -> optional
|
||
SettingsVM` for the live settings draft preview applied before world drawing.
|
||
- `LocalPlayerPortalViewport -> LocalPlayerTeleportController ->
|
||
GameplayInputFrameController -> InputDispatcher.Fired -> GameWindow` for the
|
||
canonical portal/input lifetime and the host's input-action subscription.
|
||
|
||
These are presentation state/command seams, not alternative owners of the
|
||
window or frame transaction. The screenshot path is deliberately not on this
|
||
list: viewport width/height now travel in `RenderFrameInput`, so
|
||
`FrameScreenshotController` no longer stores a window-size callback. New
|
||
immediate phase owners must still satisfy the direct ownership invariant;
|
||
recursive shared-owner paths are reviewed against their canonical subsystem
|
||
lifetime rather than an impossible global no-callback assertion.
|
||
|
||
---
|
||
|
||
## 3. Target structure of the App layer
|
||
|
||
The end state — not what we're shipping in one pass, but the shape
|
||
we're aiming at.
|
||
|
||
```
|
||
src/AcDream.App/
|
||
├── Program.cs # parse args + env → RuntimeOptions, build GameWindow
|
||
├── RuntimeOptions.cs # typed startup options (Rule 4)
|
||
├── Rendering/
|
||
│ ├── GameWindow.cs # thin: device/window lifecycle + delegates per-frame to RenderFrameOrchestrator
|
||
│ ├── RenderFrameOrchestrator.cs # GPU-flight boundary + typed world/private/UI render phases
|
||
│ ├── LiveEntityAnimationScheduler.cs # shipped: ordinary live-object update workset
|
||
│ ├── LiveEntityAnimationPresenter.cs # final part-pose/mesh/effect composition after scheduler output
|
||
│ ├── RetailStaticAnimatingObjectScheduler.cs # shipped: separate static-animation workset
|
||
│ ├── StaticLiveRootCommitter.cs # live static root → pose + collision boundary
|
||
│ ├── TerrainModernRenderer.cs # (already exists)
|
||
│ ├── TextureCache.cs # (already exists)
|
||
│ ├── ParticleRenderer.cs # (already exists)
|
||
│ ├── Sky/ # (already exists)
|
||
│ ├── Wb/ # WB seam + EnvCellLandblockBuild transaction
|
||
│ └── Vfx/ # (already exists)
|
||
├── Net/
|
||
│ ├── LiveSessionController.cs # owns complete WorldSession connect/enter/logout/reconnect lifecycle
|
||
│ ├── LiveSessionHost.cs # App reset/selection/entry/route-factory composition over the canonical controller
|
||
│ ├── LiveSessionLifecycleHost.cs # exact borrowed-session attachment guard
|
||
│ ├── RetailSkillFormula.cs # named-retail unsigned skill-credit calculation + DAT lookup
|
||
│ └── LiveSessionEventRouter.cs # typed subscriptions → focused domain handlers
|
||
├── Physics/
|
||
│ ├── ProjectileController.cs # canonical live-record projectile orchestration
|
||
│ ├── RemotePhysicsUpdater.cs # ordinary/Hidden remote narrow-tick integration
|
||
│ ├── LiveEntityOrdinaryPhysicsUpdater.cs # manager-less canonical body Transition path
|
||
│ ├── LiveEntityNetworkUpdateController.cs # Position/Vector/State/Motion App integration
|
||
│ ├── LiveEntityInboundAuthorityGate.cs # exact per-channel timestamp/version admission
|
||
│ ├── LiveEntityMotionRuntimeController.cs # shared host/setup/MoveTo/Sticky runtime policy
|
||
│ ├── DeferredLiveEntityMotionRuntimeBindings.cs # fail-fast construction-order bridge
|
||
│ ├── LiveEntityShadowPublisher.cs # authoritative exact-owner/residency collision gate
|
||
│ ├── RemoteInboundMotionDispatcher.cs # shared animated/headless UpdateMotion funnel
|
||
│ ├── RemoteTeleportController.cs # loaded/pending teleport placement ownership
|
||
│ ├── RemoteTeleportHook.cs # ordered retail teleport teardown actions
|
||
│ └── RemoteTeleportPlacement.cs # collision-seated SetPosition transition commit
|
||
├── World/
|
||
│ ├── InboundPhysicsStateController.cs # timestamps + accepted spawn snapshots
|
||
│ ├── LiveEntityRuntime.cs # shipped: logical lifetime + ServerGuid↔entity.Id translation
|
||
│ ├── LiveEntityHydrationController.cs # spawn/appearance/parent/delete resource integration
|
||
│ ├── RetailInboundEventDispatcher.cs # update-thread packet/frame FIFO barrier
|
||
│ ├── RetailLiveFrameCoordinator.cs # shipped: object/network/command/reconcile phase order
|
||
│ ├── LiveEntityPresentationController.cs # ordered Hidden/NoDraw/effect/collision side effects
|
||
│ ├── WorldEnvironmentController.cs # one-shot clock/DAT-sky/day/weather/AdminEnvirons owner
|
||
│ ├── LiveEntityTeardown.cs # failure-isolated multi-owner lifecycle drain
|
||
│ └── ParentAttachmentState.cs # parent generations + pending ParentEvent relations
|
||
├── Interaction/
|
||
│ ├── WorldSelectionQuery.cs # read-only picking/classification/description queries
|
||
│ └── SelectionInteractionController.cs # owns selection intents, Use/PickUp, auto-walk deferral
|
||
├── Streaming/
|
||
│ ├── LandblockPresentationPipeline.cs # DAT build/apply/retire transaction and resource publication
|
||
│ ├── StreamingController.cs # typed frame scheduler + reveal destination reservation
|
||
│ ├── WorldRevealCoordinator.cs # graphical reveal projection and exact host receipts
|
||
│ └── ... # shipped streamer/world-state owners
|
||
├── Input/ # (already exists)
|
||
├── Audio/ # (already exists)
|
||
└── Plugins/ # (already exists)
|
||
```
|
||
|
||
What `GameWindow` keeps:
|
||
|
||
- `IWindow` / device / `IInputContext` lifecycle (constructor + `OnLoad` +
|
||
`Run` + `OnClosing`).
|
||
- `RuntimeOptions` reference (the typed startup config).
|
||
- GPU resource construction and top-level collaborator composition. Construction
|
||
is allowed here; feature algorithms and mutable subsystem state are not.
|
||
- One field per top-level collaborator (`_liveSessionController`,
|
||
`_liveEntityRuntime`, `_selectionInteraction`, `_streamingPresentation`,
|
||
`_liveObjectFrame`, `_renderFrameOrchestrator`).
|
||
- The Silk.NET event-handler stubs that delegate to collaborators.
|
||
|
||
What `GameWindow` loses:
|
||
|
||
- Live connect/enter/logout and event-subscription bodies → completed inside
|
||
`LiveSessionController` / `LiveSessionEventRouter`.
|
||
- Live-object hydration adapters and final animated-part presentation → focused
|
||
world/render owners over exact-key App projection sidecars backed by
|
||
canonical Runtime records.
|
||
- `WorldPicker`, target queries, and selection-driven Use/PickUp/auto-walk →
|
||
`WorldSelectionQuery` + `SelectionInteractionController`. Core
|
||
`SelectionState` remains the injected session owner.
|
||
- Landblock DAT build/apply/retirement presentation →
|
||
`LandblockPresentationPipeline`; `StreamingController` remains the residency
|
||
scheduler and `GpuWorldState` remains the spatial registry.
|
||
- Per-frame draw orchestration and its frame-local scratch state →
|
||
`RenderFrameOrchestrator`.
|
||
|
||
The eventual `GameEntity` aggregation (target state described in
|
||
`acdream-architecture.md` §"GameEntity: The Unified Entity") happens
|
||
**after** `RuntimeEntityDirectory` is the single owner of canonical entity
|
||
state. That boundary is now shipped. App's former parallel presentation
|
||
dictionaries are bounded by one exact-key projection store rather than spread
|
||
across `GameWindow`.
|
||
|
||
`RuntimeEntityDirectory` owns the only active server-GUID/incarnation map,
|
||
Runtime local-ID allocation/reverse lookup, accepted immutable CreateObject
|
||
snapshot and nine-channel timestamp gates, parent relations, operation
|
||
versions, and exact teardown tombstones. `RuntimeEntityRecord` is
|
||
presentation-free. `LiveEntityRuntime` is the App projection/lifecycle host:
|
||
it creates a `LiveEntityRecord` sidecar only after Runtime local-ID claim and
|
||
stores it in `LiveEntityProjectionStore` by exact `RuntimeEntityKey`.
|
||
Hydration, animation, effects, lights, equipped children, render identity,
|
||
movement observations, liveness, teardown, and spatial worksets borrow that
|
||
same exact identity. `LiveEntityPresentationController` projects Runtime state
|
||
transitions into draw, collision, effects, child-NoDraw, and target visibility
|
||
without becoming a second canonical owner.
|
||
|
||
`Rendering/Vfx/EntityEffectController` owns the focused mixed F754/F755 pending
|
||
FIFO, effect profiles, typed-table resolution, and readiness markers. A GUID
|
||
is retained there only as pending wire-delivery metadata; current-incarnation
|
||
resolution always asks `RuntimeEntityDirectory` and then the exact projection
|
||
store. `EntityScriptActivator` uses the same Runtime-issued `WorldEntity.Id` as
|
||
rendering and physics; disjoint static ID allocators fail fast instead of
|
||
wrapping into another landblock's namespace. All other non-Parent pre-create
|
||
packet families still need the future general queue tracked by divergence
|
||
AD-32.
|
||
|
||
The per-frame object scheduler is extracted, but final animated-part
|
||
presentation is not. `LiveEntityAnimationScheduler` snapshots canonical spatial
|
||
root records and advances the incarnation-stable object clock, PartArray,
|
||
hooks, one selected movement owner, and manager tail in retail order. The
|
||
remaining `GameWindow.TickAnimations` loop still composes part transforms,
|
||
drawable `MeshRef`s, and effect poses from the scheduler output; moving that
|
||
body into `LiveEntityAnimationPresenter` is an explicit pending slice.
|
||
Manager-less
|
||
bodies delegate their candidate/Transition/cell/shadow commit to
|
||
`LiveEntityOrdinaryPhysicsUpdater`; retained projectile bodies and remote
|
||
MovementManagers remain mutually exclusive movement owners. Static animation
|
||
is deliberately separate: `RetailStaticAnimatingObjectScheduler` owns the
|
||
`CPhysics::static_animating_objects` workset for DAT and live PhysicsState-
|
||
Static owners with Setup DefaultAnimation. Both schedulers are wired by
|
||
`GameWindow`, but neither owns GUID identity or logical resources.
|
||
The typed animation view fills its reusable snapshot and render-ID set from
|
||
the exact-key projection workset, avoiding interface-enumerator boxing on both
|
||
update and render hot paths.
|
||
`RemoteInboundMotionDispatcher` similarly keeps UpdateMotion protocol behavior
|
||
outside `GameWindow`: GameWindow resolves the canonical record/body and the
|
||
optional PartArray sink, while one dispatcher owns retail's interrupt, style,
|
||
MoveTo/type-0, sticky, and standing-long-jump order. Static root projection is
|
||
bounded by `StaticLiveRootCommitter`, which synchronizes changed roots to
|
||
effects and collision without rebuilding zero-omega shadows or resurrecting a
|
||
Hidden/withdrawn registration.
|
||
|
||
Synchronous network and lifecycle callbacks are bounded by
|
||
`RetailInboundEventDispatcher`. It owns no wire state and no identity; it only
|
||
serializes nested live-object operations until the current packet or full
|
||
object-frame tail completes. State-bearing direct dispatch is allocation-free;
|
||
only a genuinely nested operation allocates its retained queue wrapper.
|
||
`LiveEntityRecord` then supplies exact-incarnation
|
||
and per-channel authority versions at callback boundaries. Position, State,
|
||
Vector, and Movement remain independent, while a separate velocity version
|
||
invalidates only an older operation that would overwrite a newer velocity
|
||
installed by Position, Vector, or Movement. This prevents re-entrant App
|
||
observers from creating call-stack ordering that retail's update-thread packet
|
||
FIFO cannot produce.
|
||
|
||
Pose-dependent hook deferral is similarly incarnation-scoped rather than GUID-
|
||
or local-ID-scoped. `EntityEffectPoseRegistry` publishes a monotonic pose-owner
|
||
lifetime, and `AnimationHookFrameQueue` captures it before semantic callbacks
|
||
and rechecks it before each semantic AnimationDone and each routed hook. Static animation retains `process_hooks`
|
||
until its root, live parts, and children are published; withdrawal invalidates
|
||
both its prepared pose and pending hook tail.
|
||
|
||
Resolved ordinary motion commits its body/root/contact state before the
|
||
canonical full-cell setter enters `LiveEntityRuntime.RebucketLiveEntity`.
|
||
Because that setter may synchronously move the projection to pending or replace
|
||
the GUID, both `RemotePhysicsUpdater` and `LiveEntityOrdinaryPhysicsUpdater`
|
||
revalidate the exact incarnation before collision or manager-tail publication.
|
||
Collision residency itself is projection-owned, not updater-owned:
|
||
`LiveEntityPresentationController` suspends retained non-projectile shadows on
|
||
every unavailable-projection edge, restores them on hydration, and reconciles
|
||
the pending-first case at `OnLiveEntityReady` after collision registration.
|
||
Local projection and both authoritative remote UpdatePosition tails commit the
|
||
complete root before rebucketing, then publish collision only through an
|
||
exact-record/spatial-residency gate. Remote reflood tracks translation,
|
||
sign-invariant complete orientation, and cell changes so an in-place turn or a
|
||
same-pose EnvCell crossing cannot leave offset Setup shapes stale.
|
||
The projectile controller retains its separate body/InWorld/shadow edge owner.
|
||
|
||
During login/portal quiescence, loaded projection residency and gameplay
|
||
visibility are separate. `LiveEntityRuntime` validates spatial ownership
|
||
through `GpuWorldState.IsLiveEntityProjectionResident`, allowing destination
|
||
renderer preparation to converge offscreen. World consumers continue through
|
||
availability-gated `IsLiveEntityVisible`, so drawing, collision, picking,
|
||
radar/status targeting, effects, and audio remain closed until reveal. This
|
||
distinction is part of the Slice E connected gate, not an alternate live-entity
|
||
lifetime.
|
||
|
||
Remote teleport placement is bounded in `Physics/RemoteTeleportController`,
|
||
not `GameWindow`: it retains at most one pending request per materialized
|
||
incarnation, scopes it by the live generation and accepted PositionSequence,
|
||
and asks `RemoteTeleportPlacement` to collision-seat the current body when the
|
||
destination projection is available. `GameWindow` supplies lifecycle and
|
||
shadow-sync callbacks only; canonical identity remains in
|
||
`RuntimeEntityDirectory`, and App placement retains the exact projection key.
|
||
Failed hydration restores the captured source and delegates an
|
||
incarnation-scoped shadow restore to `LiveEntityPresentationController` while
|
||
that source is unloaded, so Hidden/UnHide and teleport never become competing
|
||
restore owners. A newer placement transfers that restore into an explicit
|
||
generation-scoped active-placement state before its rebucket visibility edge
|
||
even while Hidden. All intervening Hidden/UnHide and projection edges defer to
|
||
that owner until stable success or rollback completes; only then can it restore,
|
||
re-defer the source, or hand a Hidden result back for UnHide. The
|
||
`ILiveEntityRemotePlacementRuntime` seam keeps the complete cell/contact
|
||
handoff available across same-body runtime-wrapper replacement; replacing the
|
||
canonical body or dropping the placement contract within one incarnation is
|
||
rejected even after an operational component clear. `RemoteMotion.Body` is
|
||
constructor-owned; hydration compares pending/current wrappers directly to the
|
||
record body rather than trusting wrapper-to-wrapper equality. Binding reads an
|
||
interface Body getter once and reuses that snapshot. `GpuWorldState`
|
||
performs remove+place as one spatial rebucket,
|
||
then commits and serially drains visibility edges; `LiveEntityRuntime` filters
|
||
delayed duplicates. A rollback inside an observer cannot race the outer
|
||
destination-visible notification or expose an intermediate false pulse.
|
||
`LiveEntityTeardown` executes those independent owner callbacks to completion
|
||
and aggregates failures afterwards, so a throwing effect/plugin sink cannot
|
||
strand teleport, movement, shadow, light, or GUID-scoped state.
|
||
|
||
---
|
||
|
||
## 4. Reconciled ownership ledger — 2026-07-21
|
||
|
||
This section replaces the original six-step sketch. That sketch correctly
|
||
identified the target but overstated some extractions and omitted large bodies
|
||
added during M2/M3. The current audit is based on implementation ownership, not
|
||
whether a class with the planned name exists.
|
||
|
||
### 4.1 What counts as extracted
|
||
|
||
An extraction is **complete** only when:
|
||
|
||
1. the collaborator owns the mutable state and behavior body;
|
||
2. `GameWindow` constructs it and delegates through a narrow method;
|
||
3. the collaborator does not call back into arbitrary `GameWindow` methods;
|
||
4. focused tests exercise the collaborator without constructing a Silk window;
|
||
5. the accepted connected/visual behavior is unchanged.
|
||
|
||
A class that stores delegates back to substantial `GameWindow` methods is a
|
||
useful ordering seam, but its ownership status is **partial**.
|
||
|
||
### 4.2 Current implementation truth
|
||
|
||
| Area | Status | Current truth |
|
||
|---|---|---|
|
||
| Startup options | **Complete** | `RuntimeOptions` owns startup configuration (`eda936dc`). Remaining direct environment reads are legacy runtime diagnostics, not startup configuration. |
|
||
| Network session | **Complete Runtime ownership** | `RuntimeLiveSessionController` owns the sole `WorldSession` generation and resolve/create/Connect/selection/EnterWorld/Tick/stop/reconnect/disposal transaction. Runtime route owners preserve exact inbound/outbound ordering and retryable teardown. App supplies immutable options, graphical/domain callbacks, and one borrowed inertable UI command projection—no mirrored session or reset plan (`75930787`). |
|
||
| World environment | **J6.1 complete Runtime ownership** | `RuntimeWorldEnvironmentState` owns the instance-scoped Dereth calendar, synchronized clock, weather progression/state, selected day group, AdminEnvirons state, and typed debug overrides. App converts immutable DAT sky definitions once and projects the borrowed Runtime snapshot into rendering; no process-global Region origin or second App clock/weather owner remains (`902076c0`). TS-54/TS-55 register the remaining centered UI sound and full fog/ambient/radar behavior gaps. |
|
||
| Live identity/lifetime | **J3 complete** | `RuntimeEntityObjectLifetime` owns the sole `RuntimeEntityDirectory`, live `ClientObjectTable`, direct views, and ordered entity/object stream. The directory owns canonical GUID/incarnation/local-ID identity, accepted snapshots/timestamps, parent state, operation versions, and tombstones. `LiveEntityProjectionStore` owns App graphical sidecars by exact `RuntimeEntityKey`; hydration, presentation components, `GpuWorldState` residence/visibility, and retryable teardown preserve that key without another authority. Exact receipts precede fallible callbacks, re-entrant commits drain synchronously in sequence, and stable reset/disposal must converge the complete ledger to zero (`f46ddb5c`, `420e5eea`, `e937cc36`, `5ef8b537`, `ce3ac310`, `119b7c11`). |
|
||
| Inbound/object-frame order | **Complete App orchestration** | `UpdateFrameOrchestrator` owns the complete typed host phase graph; `RetailInboundEventDispatcher`, `RetailLiveFrameCoordinator`, `LiveObjectFrameController`, `LiveSpatialPresentationReconciler`, streaming/input/teleport/player-mode/camera owners preserve the accepted order. `GameWindow.OnUpdate` is one profiler-scoped handoff (`e91f3102`). |
|
||
| World reveal/transit | **J6 complete Runtime ownership** | `RuntimeWorldTransitState` owns the sole monotonic login/portal reveal generation, wrap-safe F751 history, pending/active teleport sequence, both accepted F751/Position packet orders, exact destination, typed atomic readiness latch, generation/sequence/cell placement and materialization validation, simulation edge, viewport observation, completion, cancellation, retail wait cue, portal count, and exact typed graphical-host acknowledgement suffix. `WorldRevealCoordinator` and `LocalPlayerTeleportController` are graphical adapters over App readiness, generation-scoped resource receipts, render-space placement, tunnel, and UI; failure/re-entrancy retains the exact unacknowledged suffix. `WorldGenerationAvailabilityState` borrows Runtime truth and owns no mutable generation. The App transit coordinator and accepted-destination mirror are deleted (`a6860d55`, `acb845d8`, `6a063a27`, `18d17d8b`). |
|
||
| Retained gameplay UI | **Mostly complete feature ownership** | `RetailUiRuntime` and focused panel/controllers own layout and behavior. `GameWindow.OnLoad` still performs substantial service composition, which is allowed until the final composition cleanup. |
|
||
| Action/combat/magic/movement | **J5 complete** | Runtime `RuntimeActionState` owns the exact selection, temporary target mode, interaction transactions, combat attack/target/mode, and spell-cast intent children. Interaction owns use/appraisal/pickup identity, ordered FIFO, and exact post-arrival token while borrowing J4's sole busy gate. `RuntimeLocalPlayerMovementState` owns the exact local controller, construction seam, autorun latch, typed view, and outbound MTS/jump/AP cadence. Retained bars, physical input, world/content queries, transport, lighting, toasts, drag/drop, effects, and animation remain App adapters/presentation. One combined Runtime ledger spans entity/object, gameplay, physics, remote, and projectile ownership (`b298f99f`, `f5f7b417`, `20df9d15`, `aa3f4a60`, `cdee7a4b`). |
|
||
| Physics/remote simulation | **J5.5 complete** | `RuntimePhysicsState` owns one per-session engine, production cache/cell graph, transition scratch, shadow registry, typed collision admissions, canonical bodies/hosts/remotes, keyed ordinary/remote worksets, simulation, and cell commits. App streaming supplies immutable prepared collision; animation/shape adapters supply graphical inputs and project committed snapshots only (`7e6033d0`). |
|
||
| Projectile simulation | **J5.6 complete** | `RuntimeEntityRecord`/`RuntimePhysicsState` own the canonical projectile component, exact-key workset, prediction/correction state, unchanged retail quantum/integration/sweep/collision loop, and cell commits. App resolves immutable Setup/DAT shape input and projects committed render, shadow, and effect-pose results only (`2aee3356`). |
|
||
| Landblock presentation | **Complete** | `LandblockBuildFactory` owns the captured-origin DAT transaction; concrete render/physics/DAT-static publishers and `LandblockPresentationPipeline` own typed-meter publication and exact retryable retirement. `StreamingController` owns stable destination/control/unload/Near/Far queues and destination reservation. CPU mesh-cache restaging requires an exact live owner. `StreamingOriginRecenterCoordinator` serializes old-window retirement with teleport/session origin lifetimes. `GameWindow` retains construction and one pipeline field only (`c79d0a49`, closeout `4a205a3e`; Slice E closeout `91e82c3c`). |
|
||
| Render-frame orchestration | **Complete** | `RenderFrameOrchestrator` owns the GPU-flight, resource, world/PView/shared-alpha, private-presentation, diagnostics, screenshot, and recovery graph. `GameWindow.OnRender` takes one logical window-size snapshot and performs one immutable handoff (`9d7df1bf`). |
|
||
| Unified `GameEntity` | **Slice J complete** | Canonical identity, retained-object lifetime, direct views, ordered entity/object deltas, gameplay state, action/combat/magic/movement/physics/remote/projectile simulation, world environment, reveal/transit truth, and host acknowledgement share failure-safe Runtime owners while exact graphical sidecars stay in App. One `GameRuntime` composes the graph. `RuntimeGenerationReset` is the sole retryable canonical-generation reset for graphical and no-window hosts; the deterministic direct host proves lifecycle, commands, portal, reconnect, GUID reuse, fault recovery, isolation, and terminal convergence without presentation assemblies (`a9a822f2`). |
|
||
| Headless host | **Slice K complete** | `AcDream.Headless` references only Runtime and loads no App/UI/graphics/window/audio assembly. Portable paths/config/credentials, deterministic command/event scheduling, shared immutable content, exact per-session identity, failure quarantine, reconnect, resource telemetry, 1/5/10/30-session isolation, two-hour simulated endurance, and the exact two-account native-Linux connected soak pass through `776482da`. |
|
||
|
||
### 4.3 Revised extraction sequence
|
||
|
||
Every numbered slice is behavior-preserving and independently committed. A
|
||
slice does not include feature work or opportunistic gameplay fixes.
|
||
|
||
#### Gate 0 — deterministic baseline — COMPLETE
|
||
|
||
The Release suite, connected R6 soak, world-lifecycle screenshots/checkpoints,
|
||
graceful reconnect, local locomotion/collision/projectile/teleport gate, and
|
||
two-client portal observer gate form the pre-refactor baseline. Each later
|
||
slice runs the subset capable of detecting its risk; render/session slices run
|
||
the complete connected lifecycle gate.
|
||
|
||
#### Slice 1 — finish selection/interaction ownership — COMPLETE 2026-07-21
|
||
|
||
Detailed execution plan:
|
||
[`docs/plans/2026-07-21-gamewindow-slice-1-selection-interaction.md`](../plans/2026-07-21-gamewindow-slice-1-selection-interaction.md).
|
||
|
||
Split the old Step 4 into three reviewable commits:
|
||
|
||
1. `WorldSelectionQuery` receives camera/scene/live-object read seams and owns
|
||
picking, target classification, selection bounds, closest-target lookup,
|
||
names, and descriptions. It cannot mutate selection or send packets.
|
||
2. `SelectionInteractionController` owns selection intents, double-click Use,
|
||
Use/PickUp packet requests, range decisions, speculative facing, and the
|
||
pending auto-walk action. It composes the existing item/combat controllers
|
||
rather than duplicating their rules.
|
||
3. Selection/Use/PickUp/combat-target `InputAction` cases delegate to the new
|
||
controller; the old methods and fields are deleted from `GameWindow`.
|
||
|
||
Tests cover read-only query classification separately from stateful intents,
|
||
including direct use, distant auto-walk completion, corpse/container opening,
|
||
pickup placement, hostile-only targeting, Hidden objects, and same-GUID reuse.
|
||
The cutover landed in `047a4c83`, `52dbb574`, `e74f2ca9`, `fa8d5232`,
|
||
`d2bb5af4`, and review-correction commit `5acc3f01`. The final review also
|
||
ported the shared `ACCWeenieObject::prevRequest` transaction shape across
|
||
inventory, toolbar, paperdoll, external-container, give, split, merge, and
|
||
drop routes. Queued actions, pending placements, and responses are bound to
|
||
exact object incarnations/tokens; optimistic projection, rollback projection,
|
||
and authoritative response notices are distinct.
|
||
|
||
Measured against `cf50ee3d`, `GameWindow` fell from 15,723 to 14,912 lines and
|
||
from 205 to 191 methods; field count stayed at 278 because the slice exchanged
|
||
legacy state for composed owners. Three independent retail, architecture, and
|
||
adversarial review loops finished clean. Release build passed; 6,558 tests
|
||
passed and five fixture/conformance tests skipped intentionally.
|
||
|
||
#### Slice 2 — finish live animation presentation — COMPLETE 2026-07-21
|
||
|
||
Move `TickAnimations`, final rigid/visual part composition, effect-pose
|
||
publication, and motion-done binding into `LiveEntityAnimationPresenter`.
|
||
`LiveEntityAnimationScheduler` remains the time/movement owner; the presenter
|
||
only consumes one scheduler result and publishes the final draw/effect pose.
|
||
Move the surviving motion diagnostics with this owner instead of adding more
|
||
environment reads to `GameWindow`.
|
||
|
||
Tests pin legacy and sequencer paths, Hidden/static eligibility, ObjScale versus
|
||
rigid effect poses, part availability, same-frame hook pose publication, and
|
||
incarnation replacement. Run the R6 object-frame and projectile/effect suites
|
||
plus the connected locomotion/projectile gate.
|
||
|
||
Result: `LiveEntityAnimationPresenter` owns final visual/rigid part
|
||
composition, effect-pose publication, MotionDone binding, and typed diagnostics.
|
||
Schedules carry exact record/entity/component/epoch/projection/presentation
|
||
identity and own their authored frame prefix until the next scheduler tick.
|
||
Appearance and static-owner rebinding invalidate stale work. Named-retail
|
||
`CPartArray::UpdateParts` short-frame retention now preserves both visual and
|
||
rigid trailing poses. `GameWindow` lost 366 lines, two methods, and one net
|
||
field. Three independent retail, architecture, and adversarial review loops
|
||
finished clean; focused App/Core tests and the complete App suite pass.
|
||
|
||
#### Slice 3 — complete live-session ownership — COMPLETE 2026-07-21
|
||
|
||
Detailed execution plan:
|
||
[`docs/plans/2026-07-21-gamewindow-slice-3-live-session.md`](../plans/2026-07-21-gamewindow-slice-3-live-session.md).
|
||
|
||
`LiveSessionController` now owns the complete resolve/create/bind/Connect/
|
||
CharacterList/select/EnterWorld/Tick/stop/reconnect/dispose transaction. Exact
|
||
generation and operation-depth gates prevent synchronous lifecycle callbacks
|
||
from resurrecting a superseded session. Staged teardown makes commands inert,
|
||
detaches events, gracefully disposes the exact `WorldSession`, detaches the
|
||
host, and runs the full reset manifest to convergence before reuse.
|
||
|
||
`LiveSessionLifecycleHost` is the narrow App adapter; the router and command
|
||
owners are transactional and session-bound. `GameWindow` lost `_liveSession`,
|
||
the router/command fields, and the old startup/reset/wiring/disposal methods.
|
||
Both UI stacks resolve `controller.Commands`; direct feature sends borrow
|
||
`CurrentSession` at call time. Forty-seven controller cases plus host,
|
||
transport-allocation, and structural gates cover failure/reentrancy/reuse.
|
||
Three independent retail, architecture, and adversarial reviews finished
|
||
clean. The Release suite passes 6,714 tests with five intentional skips. The
|
||
306-second connected lifecycle gate passed capped login, five travel/revisit
|
||
checkpoints, exact graceful close, and uncapped fresh-process reconnect; both
|
||
client processes exited normally.
|
||
|
||
`GameWindow` fell another 236 lines and three fields to 14,310 lines / 274
|
||
fields / 190 methods. Slice 4 is next.
|
||
|
||
#### Slice 4 — extract live-entity App integration — COMPLETE 2026-07-21
|
||
|
||
Detailed execution ledger:
|
||
[`docs/plans/2026-07-21-gamewindow-slice-4-live-entity-integration.md`](../plans/2026-07-21-gamewindow-slice-4-live-entity-integration.md).
|
||
|
||
This is two owners, not one replacement god object:
|
||
|
||
- `LiveEntityHydrationController` owns CreateObject/ObjDesc/Parent/Pickup/Delete
|
||
integration, DAT-backed appearance hydration, resource registration, and
|
||
exact teardown callbacks over `LiveEntityRuntime`.
|
||
- `LiveEntityNetworkUpdateController` owns App routing for accepted
|
||
Position/Vector/State/Movement updates into the existing remote physics,
|
||
motion, projectile, presentation, and teleport controllers.
|
||
|
||
Neither may own a GUID dictionary. Both resolve the current incarnation only
|
||
through `LiveEntityRuntime` and must preserve the inbound FIFO/authority-token
|
||
rules. Run the complete live-entity stress suite, R6 connected route, inventory
|
||
equip/parenting, death/corpse, and portal gates.
|
||
|
||
Checkpoints A and B are complete. `d68c83d1`, `5882b308`, and `fcb66198`
|
||
established the shared origin, lifecycle, remote-motion, and exact-record
|
||
physics-host seams. `69a2ca0c` extracted appearance/collision/default-pose and
|
||
projection-withdrawal mechanics, including exact recursive `leave_world`
|
||
subtree handling and retained retry ownership. Three independent reviews were
|
||
clean and the Release suite passes 6,799 tests with five intentional skips.
|
||
Checkpoints C and D are complete. `d10c5f2d` moved CreateObject, first
|
||
materialization, origin bootstrap, and landblock recovery into
|
||
`LiveEntityHydrationController`; `fe551496` moved ObjDesc, Parent, Pickup,
|
||
child-ready, and exact leave-world/re-entry routing. Appearance and attached
|
||
subtree recovery now use independent authority transactions and preserve the
|
||
same logical/runtime identities. Checkpoint E is complete in `f38822c4`:
|
||
Delete, visibility prune, retryable exact-incarnation component cleanup, and
|
||
derived remote stop-observation lifetime now have focused owners. Checkpoint F
|
||
completed in `aa90c646`: accepted Position/Vector/State/Movement and the
|
||
equal-generation CreateObject tail now route through focused owners.
|
||
Independent authority/version captures survive reentrant callbacks and GUID
|
||
reuse; shared motion-runtime policy is one-way bound and no extracted owner
|
||
keeps a second identity dictionary. ForcePosition serializes and stamps one
|
||
exact canonical outbound Position. Three corrected-diff reviews, the complete
|
||
6,940-test Release suite, and the 310-second seven-destination connected R6
|
||
route all passed. `GameWindow` is 10,301 lines / 267 fields / 163 methods.
|
||
Slice 5 checkpoints A–G are complete. The pipeline owns the serialized worker
|
||
build, concrete render/physics/static publication, and retryable Near/full
|
||
retirement. The direct cutover deleted the legacy apply/facade/scratch body and
|
||
added one retained shared-origin lifetime transaction for teleport, logout,
|
||
and fast relogin. `GameWindow.cs` was 8,793 raw lines at checkpoint G and no
|
||
longer owns landblock build/apply/retirement behavior. H then corrected native
|
||
shutdown ordering so live-session reset converges before the streamer and
|
||
other reset dependencies are disposed. The final measurement is 8,811 raw
|
||
lines / 247 fields / 153 methods. The complete Release suite (7,054 pass / 5
|
||
skip), the 311-second capped/reconnect lifecycle gate, and the synchronized
|
||
394-second nine-stop resource soak all pass (`4a205a3e`).
|
||
|
||
#### Slice 5 — extract landblock presentation — COMPLETE
|
||
|
||
Detailed execution ledger:
|
||
[`docs/plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md`](../plans/2026-07-21-gamewindow-slice-5-landblock-presentation.md)
|
||
(complete).
|
||
|
||
Create `LandblockPresentationPipeline` around the existing immutable
|
||
`LandblockBuild` transaction. It owns DAT build context, scenery/EnvCell
|
||
construction, mesh/collision/light publication, demotion/full-retirement, and
|
||
the single-reader DAT lock contract. `StreamingController` continues deciding
|
||
what is resident; `GpuWorldState` continues owning spatial buckets.
|
||
|
||
Tests pin loaded/pending/demoted/unloaded symmetry, stale build generations,
|
||
resource pin/release balance, collision footprints across landblock seams, and
|
||
first-login bootstrap replacement. The deterministic world-lifecycle gate and
|
||
nine-destination resource soak pass.
|
||
|
||
#### Slice 6 — extract update-frame orchestration — COMPLETE 2026-07-22
|
||
|
||
Detailed execution ledger:
|
||
[`docs/plans/2026-07-21-gamewindow-slice-6-update-frame-orchestration.md`](../plans/2026-07-21-gamewindow-slice-6-update-frame-orchestration.md)
|
||
(complete).
|
||
|
||
After the stateful bodies above have owners, make the update path a real
|
||
orchestrator instead of delegates back into the window. It preserves the
|
||
accepted production order—not an overclaimed retail host order—covering
|
||
teardown/clock/streaming convergence, input, the retail-shaped object → inbound
|
||
network → command barrier, conditional spatial reconciles, liveness, local
|
||
teleport, player-mode entry, and camera presentation. The exact twelve-phase
|
||
graph and registered TS-53 host-order adaptation live in the detailed ledger.
|
||
`GameWindow.OnUpdate` becomes a short time/input handoff.
|
||
|
||
Frame-order tests and the existing R6 gate must produce the same lifecycle and
|
||
movement traces before and after extraction.
|
||
|
||
The production cutover is complete in `e91f3102`. `GameWindow.OnUpdate` now
|
||
contains the profiler scope and one `UpdateFrameOrchestrator.Tick` handoff;
|
||
streaming, input, the object/network barrier, liveness, teleport, player-mode,
|
||
and camera phases have typed owners with no substantial callback facade into
|
||
the window. The complete Release suite passes 7,182 tests with five intentional
|
||
skips. The 314-second lifecycle/reconnect gate and 394-second synchronized
|
||
nine-stop resource soak both pass with graceful exits, stable live-owner
|
||
counts, and a 0.8 ms update-frame p95 at the final Caul checkpoint.
|
||
`GameWindow` is 7,026 raw lines / 241 fields / 108 methods: 8,697 lines (55.3%)
|
||
smaller than the campaign baseline. TS-50, TS-51, and TS-53 remain accurately
|
||
registered; this behavior-preserving extraction introduced no new divergence.
|
||
|
||
#### Slice 7 — extract `RenderFrameOrchestrator` — COMPLETE 2026-07-22
|
||
|
||
Detailed execution ledger:
|
||
[`docs/plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md`](../plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md)
|
||
(complete).
|
||
|
||
Move the complete draw graph and its reusable frame-local scratch state into a
|
||
GPU-owning App collaborator. Preserve the exact modern pipeline order, clip
|
||
routing, PView flood, landscape/opaque/shared-alpha flush boundaries,
|
||
particles, debug draw, paperdoll, retained UI, and frame fences. Do not pass a
|
||
hundred individual delegates or let the orchestrator reach back into
|
||
`GameWindow`; inject a small immutable service set plus explicit per-frame
|
||
input.
|
||
|
||
Automated acceptance uses framebuffer artifacts and render/resource
|
||
checkpoints. Visual acceptance compares outdoor, building, dungeon, portal
|
||
exit, translucent lifestone/particles, and UI at the same camera positions.
|
||
|
||
The production draw cutover is complete. `GameWindow.OnRender` now performs one
|
||
value-only handoff to `RenderFrameOrchestrator`; focused resource, world,
|
||
private-presentation, and diagnostic owners contain the former frame body and
|
||
failure recovery. The Release suite passes 7,341 tests with five intentional
|
||
skips. `GameWindow` is 4,666 raw lines / 196 fields / 70 methods: 11,057 lines
|
||
(70.3%) smaller than the campaign baseline. The 315.6-second lifecycle and
|
||
395.2-second synchronized nine-stop soak pass with code-zero graceful exits;
|
||
the six stable framebuffer checkpoints preserve the Slice 6 presentation.
|
||
Issue #232 records the coarse process-residency gate's run-to-run variance so
|
||
future diagnostics can distinguish canonical owner growth from OS/driver/GC
|
||
residency without weakening leak detection.
|
||
|
||
#### Slice 8 — composition and shutdown cleanup — AUTOMATED COMPLETE
|
||
|
||
Detailed execution ledger:
|
||
[`docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](../plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md).
|
||
|
||
Keep device/window construction in `GameWindow.OnLoad`, but group creation into
|
||
small composition functions and delete feature state left behind by prior
|
||
slices. `OnClosing` delegates to the existing retryable shutdown transaction.
|
||
Silk callbacks become narrow calls into the input, update, render, resize,
|
||
focus, and shutdown owners.
|
||
|
||
Checkpoints E, F, G, and H are complete. `CameraPointerInputController` owns raw pointer,
|
||
camera cursor/focus/scroll/sensitivity behavior; `FramebufferResizeController`
|
||
alone publishes physical framebuffer changes. All Silk, dispatcher, retained
|
||
UI, and optional devtools device edges are reversible, transactional, and
|
||
terminal after disposal. Logical cutoff precedes live-session retirement;
|
||
physical detach follows it, so neither copied callbacks nor a failed event
|
||
remove can interfere with graceful transport teardown.
|
||
|
||
`GameplayInputActionRouter` is now the sole `InputDispatcher.Fired`
|
||
subscriber and preserves the accepted input-priority graph. Focused combat,
|
||
diagnostic, and general command owners replace the remaining window command
|
||
bodies. The retained-root item-drop edge has one reversible owner, while its
|
||
deferred toolbar targets become inert before session retirement. `GameWindow`
|
||
now has no settings-store construction, duplicate settings loads, persisted
|
||
settings mirrors, or display/quality feature bodies. `RuntimeSettingsController`
|
||
is constructed before `Window.Create`, owns the one immutable startup snapshot
|
||
and current/toon settings state, applies startup display/pacing/FOV/audio once,
|
||
then borrows late-bound runtime targets without replay. Saved FOV now applies at
|
||
startup even when devtools are disabled; this is a deliberate correction of the
|
||
old accidental SettingsVM gate. The controller also preserves unrelated unsaved
|
||
Gameplay draft fields when combat preferences change. `GameWindow` is now 3,663
|
||
raw lines / 162 fields / 37 methods at G. H adds sole lifetime roots for the
|
||
terrain atlas and dedicated sky shader, one retained Host/runtime lease, an
|
||
atomic update/render frame-root slot, and a prepare-aware portal fallback and
|
||
transfer slot. GPU construction and state mutations now use checked commit
|
||
boundaries with exact retry ownership for failed names, bindless residency,
|
||
and texture-binding restoration. `GameWindow` is 3,689 raw lines / 162 fields /
|
||
37 methods at H. Checkpoint I.1–I.5 now provide the executable nine-phase
|
||
oracle, platform/host/content/settings phases, and the production world/render
|
||
phase. `WorldRenderCompositionPhase` owns Region/environment, the mandatory
|
||
modern-renderer foundation, immutable terrain-worker inputs, and WB/texture/
|
||
sampler construction; every Phase-4 GPU constructor has retryable prefix
|
||
ownership. `GameWindow` is 3,522 raw lines after I.5. I.6 moves interaction,
|
||
retained UI, live presentation, and landblock publication into ordered
|
||
composition phases. Typed exact-owner sources bridge later session,
|
||
selection, radar, view-plane, diagnostics, inventory, hydration, and automation
|
||
owners without callbacks to the window. The old item-use and selection wrapper
|
||
methods are deleted. I.7 now owns the complete streaming/session/hydration/
|
||
local-player/teleport construction phase and every late edge through named
|
||
exact-owner tokens; the former 432-line inline body and spawn-claim memo are
|
||
gone. I.8a moves the exact live-session reset/router graph, combat and
|
||
diagnostic command targets, and sole gameplay input subscriber into Phase 7
|
||
before frame publication. I.8b moves the complete update/render construction
|
||
body into `FrameRootCompositionPhase`, publishes the pair through an exact
|
||
lease, and gives lifecycle resource sampling a focused source. `GameWindow` is
|
||
1,945 raw lines after I.9 corrects production to invoke the same executable
|
||
pipeline as the failure oracle, carries exact platform/settings results through
|
||
the typed contract, and keeps live-session start terminal. Checkpoint J then
|
||
moves the complete shutdown manifest into `GameWindowLifetime`: typed root
|
||
groups, retry/no-replay progress, structured reportable physical-detach
|
||
failures, hard session/GPU barriers, and native-window-last release now live
|
||
outside the host. The shell captures roots once and makes one lifetime call
|
||
from Closing/Dispose; no shutdown stage remains in it. `GameWindow` is 1,625
|
||
raw lines after J. Checkpoint K adds acknowledged same-frame canonical resource
|
||
checkpoints and leaves the shell at 1,622 lines. The App gate passes 3,451 tests
|
||
/ 3 intentional skips and the complete Release suite passes 7,823 / 5. Two
|
||
fresh-process nine-stop soaks and the connected lifecycle/reconnect route pass
|
||
with graceful exits; deterministic framebuffer captures preserve accepted
|
||
Slice 7 geometry, UI/paperdoll layering, private viewports, depth/alpha, and
|
||
reveal. The clean solution build retains only the 17 test-project warnings
|
||
tracked by #228, and all three final corrected-diff reviews are clean. The
|
||
user's connected visual matrix passed 2026-07-23, including first-login radar,
|
||
movement/combat, shared retained panels, recall/portal presentation, and
|
||
graceful reconnect.
|
||
|
||
### 4.4 Exit criteria
|
||
|
||
The campaign is complete when:
|
||
|
||
- `GameWindow` contains no AC gameplay algorithm, entity scan, packet builder,
|
||
DAT landblock builder, animation-part composer, or draw-graph body;
|
||
- `OnUpdate`, `OnRender`, `OnInputAction`, and live-session callbacks are short
|
||
delegation methods;
|
||
- every extracted owner has App-layer tests and symmetric teardown;
|
||
- the Release suite and connected lifecycle/resource gates stay green after
|
||
every slice;
|
||
- the final local visual matrix passes unchanged.
|
||
|
||
Line count is a progress signal, not the acceptance test. The expected result
|
||
is below roughly 5,000 lines, but ownership and dependency direction decide
|
||
completion. Full `GameEntity` type aggregation is evaluated only afterwards as
|
||
a separate migration; it is not folded into this campaign.
|
||
|
||
---
|
||
|
||
## 5. Rules of the road during the extraction
|
||
|
||
1. **One slice at a time.** Each commit ships one ownership boundary or
|
||
mechanical call-site cutover. Bundling slices makes failures hard to
|
||
isolate.
|
||
2. **Behavior preservation is the acceptance criterion.** Every slice
|
||
must build clean, all tests pass, and visual verification at the
|
||
appropriate accepted milestone scenarios must succeed. We're moving code, not
|
||
changing it.
|
||
3. **No new features during an extraction step.** If you spot a real
|
||
bug while extracting, file it in `docs/ISSUES.md` and address it in
|
||
a separate commit (before or after the extraction, not folded into
|
||
it).
|
||
4. **Diagnostic toggle migrations are opportunistic.** When a method
|
||
moves to a new owner, the diagnostic flag inside it can move to a
|
||
diagnostic class as part of the same commit. We do not do a bulk
|
||
diagnostic-cleanup pass.
|
||
5. **Update this document when the plan changes.** If a slice turns out
|
||
to need a different ownership shape than described above, update §4 in the
|
||
same session you discover the divergence.
|
||
6. **No façade-only completion claims.** A delegate from a new class back to a
|
||
substantial `GameWindow` method is a useful intermediate seam, not a
|
||
completed extraction.
|
||
7. **No duplicate ownership.** New collaborators query `LiveEntityRuntime`,
|
||
`GpuWorldState`, `SelectionState`, or the relevant existing owner. They do
|
||
not create replacement GUID, visibility, session, or resource maps.
|
||
|
||
---
|
||
|
||
## 6. What this document is **not**
|
||
|
||
- **Not a full rewrite plan.** The point is the *opposite* — small
|
||
steps, verified at each boundary.
|
||
- **Not a feature phase.** Following the 2026-07-21 user decision, this
|
||
behavior-preserving campaign is the structural prerequisite before new M4
|
||
subsystem bodies are added. Severe regressions remain fixable in separate
|
||
commits; ordinary feature work waits.
|
||
- **Not a substitute for the milestones / roadmap.** Those drive the
|
||
feature work. This drives the structural work that runs underneath.
|