Record the exact J5.2 owner boundary, retail ordering, Release and connected evidence, rollback, and J5.3 handoff across architecture, roadmap, milestones, agent instructions, and durable memory. Co-authored-by: Codex <noreply@openai.com>
1328 lines
66 KiB
Markdown
1328 lines
66 KiB
Markdown
# Modern Runtime Architecture — detailed execution plan
|
||
|
||
**Date:** 2026-07-24
|
||
|
||
**Status:** Slices A–I, J0–J4, and J5.1–J5.2 are complete. Flat-authoritative collision, zero-
|
||
allocation resolve scratch, parsed-graph removal, connected correctness/
|
||
lifetime acceptance, and Slice-I closeout landed 2026-07-25. Slices F–L were
|
||
explicitly approved 2026-07-24. Slice J0–J1 established the enforced Runtime
|
||
boundary and borrowed contract seam; J2 moved canonical session/transport
|
||
lifetime and ordered inbound routing; J3.1–J3.6 moved canonical entity identity
|
||
and object-table lifetime to Runtime, exact graphical projection identity to
|
||
App, and one ordered canonical entity/object stream plus direct views to all
|
||
hosts and closed the failure-safe owner ledger. J4.1 moved communication and
|
||
social ownership into Runtime at `c9d25ade`; J4.2 moved inventory transaction
|
||
ownership into Runtime at `011efbea`; J4.3 moved the coupled magic/player-sheet
|
||
owner into Runtime at `d02a12ce`; J4.4 moved character settings/projections
|
||
and the shared typed state-command seam at `dcb61efb`; J4.5 closed canonical
|
||
gameplay-state ownership at `89e6b207`. J5 movement, physics, interaction,
|
||
and combat ownership is active at J5.3 after canonical action state
|
||
(`b298f99f`) and interaction transactions (`f5f7b417`) moved to Runtime.
|
||
|
||
**Scope:** Reconcile and sequence the existing Modern Pipeline (`MP`) and
|
||
Linux/headless (`LH`) tracks using the 2026-07-24 connected performance audit.
|
||
|
||
**Primary objective:** Minimize CPU, managed/native memory, GPU memory,
|
||
allocation, and frame-time variance without reducing view distance, particles,
|
||
world detail, or retail behavior.
|
||
|
||
This plan refines rather than replaces
|
||
[`docs/superpowers/specs/2026-07-05-modern-pipeline-design.md`](../superpowers/specs/2026-07-05-modern-pipeline-design.md).
|
||
It preserves the shipped N.5 bindless/MDI renderer, the completed `GameWindow`
|
||
decomposition, canonical Runtime entity ownership with exact-key App
|
||
projections, the retail PView/cell visibility ports, and all behavior already
|
||
accepted by the user.
|
||
|
||
---
|
||
|
||
## 0. Governance and standing-decision interlocks (2026-07-24 review)
|
||
|
||
Added by the 2026-07-24 adversarial review. These interlocks are binding on
|
||
every executor of this plan; the audit's correction record is
|
||
[`../research/2026-07-24-runtime-performance-audit.md`](../research/2026-07-24-runtime-performance-audit.md)
|
||
section 13.
|
||
|
||
1. **The ECS deferral stands until the user lifts it.** On 2026-07-05 the
|
||
user deferred the ECS/MP3 render-world rewrite ("revisit ONLY if the user
|
||
wants a higher raw FPS number and accepts the rewrite risk" —
|
||
`claude-memory/project_mp_track_findings.md`), and the follow-up
|
||
measurement note concluded MP3 was not warranted for town FPS. Slices A–E
|
||
do not touch that deferral. **Slices F and G resume the deferred MP3
|
||
work and MUST NOT start without explicit user approval recorded against
|
||
this section**, taken on the post-Slice-E corrected uncapped-local
|
||
profile (does partition/submission cost still dominate?).
|
||
**Approval recorded 2026-07-24:** after reviewing the post-E result and
|
||
rewrite risk, the user authorized "all slices." F/G may proceed from the
|
||
pre-runtime rollback anchor `e7d9d6fa`. Note the prior
|
||
"GPU-bound at ~200 FPS" Sawato reading is itself invalidated by the
|
||
`GpuFrameTimer` boundary artifact this plan's Slice A corrects — the
|
||
question is genuinely open until re-measured.
|
||
2. **Session discipline.** This plan executes in dedicated Track MP/LH
|
||
side-track sessions only. The ACTIVE milestone's critical path wins every
|
||
conflict; do not begin any slice inside a milestone session.
|
||
3. **Freeze-exception scope.** The recorded 2026-07-05 Track MP exception
|
||
covers streaming and WB rendering only. Slice H's network-I/O item and
|
||
Slice J's gameplay-owner moves touch otherwise-frozen subsystems
|
||
(network, chat, input) and each requires its own recorded user
|
||
authorization before starting. **Approval recorded 2026-07-24:** the
|
||
user's authorization for "all slices" lifts the Slice J gate as well.
|
||
(The 2026-07-24 receive-thread robustness fix was separately reviewed and
|
||
user-approved as a bug fix, not a rework.)
|
||
4. **Evidence-base corrections.** The §1 exception-traffic row previously
|
||
cited "4,520 + 1,168" — figures with no derivation in the audit or its
|
||
committed artifacts; the verifiable figures are the focused-trace 1,587
|
||
(1,335 invalid DAT probes + 252 receive timeouts). And the audit's
|
||
204 MiB single-frame allocation is a FRAME-THREAD measurement that cannot
|
||
include worker-side mesh decode: **Slice E owns the frame-spike symptom;
|
||
Slices B/C own the process-wide allocation rate and GC pressure.** Win
|
||
attribution must respect that split.
|
||
5. **Baseline prerequisite.** No behavior-changing slice (C onward) starts
|
||
until the Slice A exit criteria in §10 are met: committed uncapped-local
|
||
AND capped-local nine-stop baselines, a pinned dense-town uncapped
|
||
baseline, and per-portal allocation/frame timelines, all captured with
|
||
the corrected tooling and a committed reference-hardware definition.
|
||
|
||
---
|
||
|
||
## 1. Why the architecture should change
|
||
|
||
The complete methodology, attribution, sanitized checkpoint data, and raw-
|
||
artifact retention policy are recorded in
|
||
[`../research/2026-07-24-runtime-performance-audit.md`](../research/2026-07-24-runtime-performance-audit.md).
|
||
|
||
The 2026-07-24 six-minute, nine-stop connected route established:
|
||
|
||
| Observation | Measurement | Architectural implication |
|
||
|---|---:|---|
|
||
| Steady capped update work | approximately 0.4–1.3 ms median | The gameplay/update architecture is not the steady-state crisis |
|
||
| External GPU use | 7.6% median, 15.9% p95 | The tested route was not GPU-bound |
|
||
| Portal allocation | up to approximately 204 MiB in one frame | Runtime content preparation and publication must leave the frame transaction |
|
||
| Managed allocation rate | approximately 40.8 MB/s average, 2.22 GiB/s maximum | Portal work is creating short-lived graphs and pixel/mesh arrays faster than GC can absorb |
|
||
| GC | 38 Gen-2 collections; 256 ms longest pause | Frame spikes are dominated by allocation/publication, not upload time alone |
|
||
| Longest frame | 276 ms | Landblock-count budgets are not real cost budgets |
|
||
| Final live GC heap | approximately 194 MiB | The 1–2 GiB process high-water is not an unbounded live-object leak |
|
||
| Dedicated GPU memory | 633–818 MiB | Texture arrays/caches are bounded but expensive and insufficiently observable |
|
||
| Revisited locations | entity/emitter/particle counts returned to the same plateaus | Logical lifetime is broadly convergent; optimize rather than replace it |
|
||
| Exception traffic | 1,587 focused-trace exceptions (1,335 invalid DAT probes, 252 receive timeouts); full-route totals pending re-derivation (see §0.4) | Exceptions are being used as normal control flow |
|
||
|
||
Allocation attribution names the runtime mesh/texture path
|
||
(`MeshExtractor.PrepareMeshData` → `PrepareGfxObjMeshData` →
|
||
`AddSurfaceToBatch`) as the largest portal source. The update-thread
|
||
publication/retirement graph then amplifies it with complete dictionaries,
|
||
sorts, effect profiles, and unmetered per-entity teardown.
|
||
|
||
The conclusion is:
|
||
|
||
> Keep retail-shaped gameplay and the modern GPU backend. Replace the runtime
|
||
> content, scene-projection, residency, and work-scheduling architecture that
|
||
> feeds the renderer.
|
||
|
||
---
|
||
|
||
## 2. Fixed architectural decisions
|
||
|
||
These decisions are binding unless a later measured gate disproves one.
|
||
|
||
1. **No whole-client rewrite.** Existing retail ports, network behavior,
|
||
physics, animation, UI, plugins, and the N.5 renderer remain in place.
|
||
2. **No simulation ECS.** Retail-shaped object-oriented code remains the oracle-
|
||
traceable form for physics, motion, sequencers, combat, inventory, and magic.
|
||
3. **A data-oriented render world only.** Arch remains the selected storage
|
||
implementation, hidden behind acdream-owned interfaces so it can be replaced
|
||
without changing simulation or rendering contracts.
|
||
4. **One authoritative live-object owner.** `LiveEntityRuntime` is evolved and
|
||
later extracted; no second GUID map, gameplay world, or mirrored entity state
|
||
may be introduced during migration.
|
||
5. **Headless is not a hidden window.** A headless session constructs no
|
||
Silk.NET window, GL context, render assets, particles, audio, or gameplay UI.
|
||
6. **One content interpretation.** Bake and runtime validation use the exact
|
||
existing `AcDream.Content` interpretation code. A baked artifact must be
|
||
byte/field equivalent before runtime uses it.
|
||
7. **No visual-quality optimization by default.** View horizon, retail PView,
|
||
particles, effects, and texture pixels stay unchanged. Retail distance LOD is
|
||
a faithful mechanism and remains separately configurable.
|
||
8. **Work is budgeted by cost, not object count.** Streaming stages use elapsed
|
||
microseconds, prepared bytes, uploaded bytes, and entity operations. “Four
|
||
landblocks” is not a meaningful budget.
|
||
9. **GL remains render-thread owned.** Workers may perform I/O, parsing,
|
||
decompression, mesh preparation, and collision preparation, but never mutate
|
||
GL or live scene state.
|
||
10. **Frame products are borrowed immutable views, not object graphs.** Snapshot
|
||
boundaries use double-buffered arrays/spans and generation stamps; they do
|
||
not allocate a deep immutable tree each frame.
|
||
11. **Parallelism follows data cleanup.** Do not add a job system to compensate
|
||
for repeated decode, sorting, allocation, or whole-world scans.
|
||
12. **Every cutover deletes the replaced production path.** Temporary shadow
|
||
execution is allowed for comparison, but no permanent dual renderer,
|
||
duplicate world, or silent decode fallback remains after its gate.
|
||
|
||
---
|
||
|
||
## 3. Target dependency architecture
|
||
|
||
```text
|
||
AcDream.GraphicalHost (current AcDream.App)
|
||
├── window/input/OpenGL/OpenAL/retail UI
|
||
├── render scene projection + renderer
|
||
└── owns one AcDream.Runtime.GameRuntime
|
||
|
||
AcDream.Headless
|
||
├── deterministic host clock
|
||
├── bot/plugin command surface
|
||
└── owns one or more AcDream.Runtime.GameRuntime instances
|
||
|
||
AcDream.Runtime [new, extracted incrementally]
|
||
├── session lifecycle and inbound ordering
|
||
├── authoritative live world and properties
|
||
├── movement/physics/interaction/combat/magic/inventory/chat
|
||
├── instance-scoped clocks, queues, commands, and events
|
||
└── no Silk.NET, GL, OpenAL, retained UI, or OS window dependency
|
||
|
||
AcDream.Content [already exists]
|
||
├── DAT interpretation
|
||
├── deterministic bake pipeline
|
||
├── mapped pak/content manifest
|
||
├── immutable prepared render/collision metadata
|
||
└── shared read-only content store for multiple sessions
|
||
|
||
AcDream.Core / AcDream.Core.Net [existing retail-shaped logic]
|
||
AcDream.Plugin.Abstractions [existing BCL-only contracts]
|
||
```
|
||
|
||
`AcDream.Runtime` is a target boundary, not the first implementation step. App
|
||
types move only after their presentation dependencies have been removed and
|
||
parity tests prove that the graphical host is still driving the same owner.
|
||
|
||
### Dependency rules
|
||
|
||
- `Core` never depends on Runtime, App, Content, or a backend.
|
||
- `Core.Net` depends on Core only.
|
||
- `Content` depends on Core, never App or a graphics backend.
|
||
- `Runtime` may depend on Core, Core.Net, Content, and Plugin.Abstractions.
|
||
- The graphical and headless hosts depend on Runtime; Runtime never depends on
|
||
either host.
|
||
- UI and rendering consume read-only runtime views plus explicit commands/events.
|
||
- Shared multi-session caches contain immutable content only. Credentials,
|
||
GUIDs, clocks, packets, plugins, object state, and automation remain
|
||
instance-scoped.
|
||
|
||
---
|
||
|
||
## 4. Runtime ownership model
|
||
|
||
| Concern | Canonical owner | Consumers |
|
||
|---|---|---|
|
||
| Server GUID/incarnation/timestamps/properties | `GameRuntime` evolved from `LiveEntityRuntime` | physics, UI, render projection, plugins, bots |
|
||
| Static prepared content | `ContentStore` | streaming, collision, render projection |
|
||
| Desired spatial window | `StreamingRegion` | streaming scheduler |
|
||
| Landblock generation and stage receipts | `StreamingWorkScheduler` | reveal, physics publication, render publication |
|
||
| Render entity/component data | `IRenderScene` / Arch implementation | visibility, snapshot builder |
|
||
| CPU/GPU asset residence | `ResidencyManager` | render scene, upload owner |
|
||
| GL names, fences, physical retirement | renderer resource owners | renderer only |
|
||
| Retail PView/visible-cell result | existing PView owner | render-scene query/snapshot builder |
|
||
| UI state | focused UI controllers/ViewModels | retained UI and optional dev UI |
|
||
|
||
Identity types must remain explicit:
|
||
|
||
```text
|
||
ServerGuid authoritative network identity
|
||
ObjectIncarnation ServerGuid + generation
|
||
RuntimeEntityId runtime-local logical identity
|
||
RenderEntityId presentation projection identity
|
||
AssetHandle<T> immutable content identity + generation
|
||
GpuHandle<T> render-thread physical resource identity
|
||
```
|
||
|
||
No layer may infer one identity from another through unchecked integer casts.
|
||
|
||
---
|
||
|
||
## 5. Content and asset architecture
|
||
|
||
### 5.1 Prepared assets
|
||
|
||
The runtime world hot path should consume prepared records, not DBObj graphs:
|
||
|
||
- Packed mesh vertices, indices, batch/material records, bounds, and part tables.
|
||
- Exact decoded texture pixels used by the current renderer.
|
||
- EnvCell geometry and portal records with content-deduplicated aliases.
|
||
- Terrain and deterministic scenery placement.
|
||
- Flattened collision/BSP arrays.
|
||
- Compact Setup presentation metadata:
|
||
default script, script table, animation, part availability, sound table,
|
||
collision references, and effect-relevant flags.
|
||
- Retail degrade tables.
|
||
|
||
Dynamic character palette/appearance composition, low-volume UI assets, audio,
|
||
and motion/animation tables may remain runtime DAT consumers until separately
|
||
measured and migrated.
|
||
|
||
### 5.2 Content-addressed pak manifest
|
||
|
||
Finish the existing pak implementation rather than replacing it:
|
||
|
||
```text
|
||
Source asset key -> manifest entry -> content hash/shared blob
|
||
```
|
||
|
||
- EnvCell file IDs may alias the same blob offset.
|
||
- Blob contents are deterministic and 64-byte aligned.
|
||
- DAT iteration stamps, serializer version, and bake-tool version invalidate
|
||
stale content.
|
||
- The writer builds a temporary artifact, validates it, then atomically replaces
|
||
the prior artifact.
|
||
- The reader memory-maps immutable data and exposes typed borrowed views.
|
||
- Corrupt/stale entries fail loudly and name the required rebake.
|
||
- Production streaming does not silently fall back to runtime decode after the
|
||
cutover. Developer equivalence tools retain both sources explicitly.
|
||
|
||
### 5.3 Asset handles and residence
|
||
|
||
Callers receive typed handles rather than retaining decoded arrays:
|
||
|
||
```csharp
|
||
readonly record struct AssetHandle<T>(uint Index, ushort Generation);
|
||
readonly record struct AssetLease<T>(AssetHandle<T> Handle, OwnerToken Owner);
|
||
```
|
||
|
||
The eventual `ResidencyManager` tracks:
|
||
|
||
- CPU prepared bytes.
|
||
- Decoded/pinned/staging bytes.
|
||
- GPU buffer and texture bytes.
|
||
- Current owners and reference count.
|
||
- Last used generation/frame.
|
||
- Rebuild/reload cost.
|
||
- Priority: destination-critical, visible, near, far, speculative.
|
||
- State: absent, requested, prepared, upload-pending, resident, retiring.
|
||
|
||
Eviction is generation-safe and owner-scoped. A stale completion cannot revive a
|
||
retired world generation or release the replacement generation's resource.
|
||
|
||
---
|
||
|
||
## 6. Streaming and reveal architecture
|
||
|
||
### 6.1 Staged pipeline
|
||
|
||
```text
|
||
Request
|
||
-> I/O/map lookup
|
||
-> parse/borrow prepared records
|
||
-> build landblock publication
|
||
-> physics/collision publish
|
||
-> render asset request
|
||
-> GL upload
|
||
-> render-scene publish
|
||
-> reveal-ready
|
||
```
|
||
|
||
Every item carries:
|
||
|
||
- World/session generation.
|
||
- Landblock/cell identity.
|
||
- Stage and priority.
|
||
- Estimated and actual byte cost.
|
||
- Entity-operation count.
|
||
- Cancellation token.
|
||
- Retryable stage receipt.
|
||
|
||
Bounded queues provide back-pressure. Background stages may run concurrently;
|
||
authoritative world, render-scene publication, and GL upload remain ordered on
|
||
their owning thread.
|
||
|
||
### 6.2 Cost budget
|
||
|
||
Each frame receives independently configurable budgets:
|
||
|
||
- Update-thread publication time.
|
||
- Entity create/retire operations.
|
||
- CPU bytes adopted.
|
||
- GPU bytes uploaded.
|
||
- GL resource retire operations.
|
||
|
||
Immediate work:
|
||
|
||
- Remove old landblocks from visibility, collision, picking, radar, and target
|
||
eligibility.
|
||
- Mark the old generation unavailable to new consumers.
|
||
|
||
Budgeted work:
|
||
|
||
- Walk old presentation owners.
|
||
- Release scripts/effects/lights/plugin snapshots.
|
||
- Retire CPU/GPU resources after fences permit.
|
||
- Publish far-ring content.
|
||
|
||
This preserves correct visible lifetime while preventing a 600-landblock
|
||
retirement from becoming one frame transaction.
|
||
|
||
### 6.3 Portal generation and reveal
|
||
|
||
A destination generation progresses through:
|
||
|
||
```text
|
||
Requested -> Prepared -> CollisionReady -> NearSceneReady -> Revealed
|
||
|
|
||
+-> FarSceneConverging
|
||
```
|
||
|
||
Reveal is an atomic edge. The world cannot become visible until the destination
|
||
near ring, collision root, camera identity, and required scene publication all
|
||
belong to the same generation. Far content continues under normal budgets after
|
||
reveal.
|
||
|
||
This replaces “priority work bypasses the budget” with a prepared-behind-portal
|
||
contract and eliminates `viewport-before-ready`.
|
||
|
||
---
|
||
|
||
## 7. Data-oriented render scene
|
||
|
||
### 7.1 Scope
|
||
|
||
Arch stores render projections only:
|
||
|
||
- Static landblock objects and scenery.
|
||
- EnvCell objects.
|
||
- Live entities and equipped children.
|
||
- Lights and effect anchors where a packed projection is beneficial.
|
||
|
||
It never owns gameplay properties, network sequencing, physics authority,
|
||
inventory, combat, or interaction state.
|
||
|
||
### 7.2 Components
|
||
|
||
Initial components are blittable or stable handles:
|
||
|
||
```text
|
||
RenderTransform
|
||
PreviousRenderTransform
|
||
MeshAsset
|
||
MaterialVariant
|
||
SpatialResidency (landblock, cell)
|
||
WorldBounds
|
||
RenderFlags
|
||
DegradeState
|
||
SortKey
|
||
OwnerIncarnation
|
||
DirtyMask
|
||
```
|
||
|
||
Separate archetypes cover static, dynamic, equipped-child, translucent, and
|
||
light-bearing projections. Do not force every entity to carry every component.
|
||
|
||
### 7.3 Incremental indices
|
||
|
||
Creation, rebucketing, mutation, and removal maintain:
|
||
|
||
- Outdoor-static set.
|
||
- Per-cell static sets.
|
||
- Dynamic set.
|
||
- Translucent set.
|
||
- Selectable/pickable spatial index.
|
||
- Light candidates.
|
||
- Dirty transform/material/mesh ranges.
|
||
|
||
The frame loop no longer partitions every loaded entity. Existing retail PView
|
||
continues to determine visible cells; the render scene enumerates only those
|
||
cell buckets plus the dynamic set.
|
||
|
||
### 7.4 Simulation-to-render seam
|
||
|
||
`LiveEntityRuntime` and static publication emit ordered projection deltas:
|
||
|
||
```text
|
||
Register
|
||
UpdateTransform
|
||
UpdateAppearance
|
||
UpdateFlags
|
||
Rebucket
|
||
Unregister
|
||
```
|
||
|
||
Deltas contain exact incarnation identity. The render world rejects stale
|
||
generation updates. It never calls back into simulation dictionaries while
|
||
drawing.
|
||
|
||
---
|
||
|
||
## 8. Render-frame product and GPU submission
|
||
|
||
The snapshot is a double-buffered, generation-stamped borrowed view:
|
||
|
||
```text
|
||
RenderFrameView
|
||
Visible opaque instance ranges
|
||
Visible alpha instance ranges
|
||
Dynamic transforms
|
||
Light set
|
||
Effect draw records
|
||
Selection records
|
||
Existing PView/clip products
|
||
```
|
||
|
||
The update/render order initially remains on the accepted host thread. The
|
||
snapshot seam prevents mutation during draw and permits later thread separation
|
||
without requiring it now.
|
||
|
||
GPU submission evolves in measured steps:
|
||
|
||
1. Persistent global mesh/instance buffers remain.
|
||
2. Dirty ranges update only changed instance records.
|
||
3. MDI command buffers are reused and rewritten in place.
|
||
4. Static command templates are cached by scene generation/cell visibility.
|
||
5. Accurate timestamp queries bracket actual render passes.
|
||
6. GPU culling, command compaction, GPU particles, or GPU light selection are
|
||
introduced only when the corrected profile names them as the next bottleneck.
|
||
|
||
The existing portal/PView pass graph, clipping, translucency ordering, and
|
||
bindless material behavior remain authoritative.
|
||
|
||
---
|
||
|
||
## 9. Presentation-independent runtime and headless host
|
||
|
||
### 9.1 Extraction rule
|
||
|
||
Do not create a new parallel `GameRuntime` and synchronize it with
|
||
`LiveEntityRuntime`. Instead:
|
||
|
||
1. Define narrow runtime read/command/event contracts around the current owner.
|
||
2. Remove presentation-specific fields from that owner into App projections.
|
||
3. Move the now presentation-independent owner and collaborators into
|
||
`AcDream.Runtime`.
|
||
4. Keep the graphical host using the same instance throughout the move.
|
||
|
||
### 9.2 `GameRuntime`
|
||
|
||
One instance owns:
|
||
|
||
- Connection/authentication/character/session lifetime.
|
||
- Packet receive ordering and retail update phases.
|
||
- Authoritative objects, properties, containers, inventory, spell state,
|
||
enchantments, vitals, targets, and combat state.
|
||
- Movement, collision state required for gameplay, and interaction commands.
|
||
- Instance clock, random sources where applicable, queues, plugins/behaviors,
|
||
diagnostics identity, and teardown.
|
||
|
||
It exposes:
|
||
|
||
- Immutable or borrowed read views.
|
||
- Typed commands.
|
||
- Ordered events/deltas.
|
||
- Deterministic `Tick`.
|
||
- Retryable, complete shutdown.
|
||
|
||
#### 9.2.1 Structural teardown protocol
|
||
|
||
Every graphical or headless host follows one retryable, generation-scoped
|
||
teardown transaction. New runtime owners register through the same
|
||
`CompositionAcquisitionScope` lease discipline already used by the graphical
|
||
composition root; release order is encoded by leases and acknowledgements,
|
||
never remembered by callers.
|
||
|
||
1. **Cancel scheduler generations.** Make commands inert, cancel the active
|
||
session/runtime generation, stop new receive publication, and prevent any
|
||
timer, bot, plugin, or worker callback from creating more work for it.
|
||
2. **Drain or poison journals and queues.** Detach inbound producers, drain
|
||
already-accepted ordered events through the generation boundary, then
|
||
poison every command/event/delta queue so delayed callbacks fail against
|
||
their exact generation instead of entering a replacement session.
|
||
3. **Unpublish presentation.** The graphical host consumes the final ordered
|
||
withdrawals and acknowledges that the render scene, retained gameplay UI,
|
||
audio/effect projections, selection, radar, and private viewports no longer
|
||
expose the runtime generation. Headless hosts acknowledge this stage
|
||
immediately because they construct no presentation owners.
|
||
4. **Release residency after fences.** Withdraw content owners, finish
|
||
streaming/landblock retirement, and allow GPU-backed stores to retire only
|
||
after their existing frame-flight fences signal. A failed fence or release
|
||
retains the exact unfinished lease and resumes without replaying completed
|
||
stages.
|
||
5. **Release the shared `ContentStore` last.** Session-scoped collision/content
|
||
leases go away before the runtime instance. The immutable mapped content
|
||
store outlives every runtime and presentation host and is disposed only by
|
||
the process host after all registered sessions have acknowledged teardown.
|
||
|
||
Logout, session reset, mid-portal disconnect, reconnect replacement, ordinary
|
||
window close, and construction rollback all use this protocol. No stage may
|
||
clear another generation's state, and no teardown failure may be converted
|
||
into a best-effort purge.
|
||
|
||
### 9.3 Hosts
|
||
|
||
`GraphicalGameHost` provides input, camera, rendering, UI, audio, frame pacing,
|
||
and a single runtime instance.
|
||
|
||
`HeadlessGameHost` provides a monotonic scheduler, navigation collision/content,
|
||
bot actions, diagnostics, and one or more runtime instances. It performs no
|
||
render-content bake lookup unless navigation/collision requires that content.
|
||
|
||
For 30 clients:
|
||
|
||
- Immutable content and flattened collision assets are shared.
|
||
- Every mutable session structure is instance-scoped.
|
||
- One process may host many sessions, but one-process-per-session remains a
|
||
supported diagnostic/isolation mode.
|
||
- Scheduling uses a deterministic round-robin/time-wheel rather than 30 busy
|
||
loops.
|
||
- Server-safe outbound rate limits are per session.
|
||
|
||
---
|
||
|
||
## 10. Detailed implementation sequence
|
||
|
||
Each slice is independently buildable, testable, bisectable, and behavior-
|
||
preserving. Each gets a focused implementation plan when it starts.
|
||
|
||
### Slice A — Measurement and contract correction
|
||
|
||
**Purpose:** Make every later gate trustworthy.
|
||
|
||
- Correct `GpuFrameTimer` so queries cover actual GL submission/pass intervals,
|
||
not frame pacing.
|
||
- Extend canonical checkpoint JSON with CPU/GPU resident bytes, staging bytes,
|
||
per-stage queue depth, stage work time, exception counts, GC pause/heap
|
||
fields, and render-scene generation.
|
||
- Add a tracked analysis script that summarizes the existing nine-stop route.
|
||
- Record capped-RDP and uncapped-local results separately.
|
||
- Add a deterministic allocation/exception attribution recipe without enabling
|
||
developer UI or changing gameplay.
|
||
|
||
**Gate:** repeated idle measurements have low observer effect; CPU stage sums
|
||
reconcile with active frame time; external GPU engine direction agrees with GL
|
||
timestamps.
|
||
|
||
**Amendment (2026-07-24 review).** Slice A additionally includes, and its
|
||
gate requires:
|
||
|
||
- Per-frame history export (frame index, stage times, frame-thread alloc,
|
||
gpu time) — landed post-review via `ACDREAM_FRAME_HISTORY`; route-wide
|
||
p50/p95/p99 populations must come from this export. Until it is the gate
|
||
population, every percentile gate reads "no 5-second window with p99 > X".
|
||
- Process-wide allocation rate per scene (dotnet-counters) alongside the
|
||
frame-thread profiler, plus a per-thread split recipe — the frame-thread
|
||
gates alone are gameable by moving work to workers (§0.4).
|
||
- One contention capture WITH STACKS on the route (the 4,614 contentions are
|
||
unattributed; `_datLock` build-vs-hydration and
|
||
`ObjectMeshManager._pendingRequests` are the leading candidates, not only
|
||
the DAT typed-object lock).
|
||
- Cache hit/miss/evict counters in the checkpoint JSON (landed post-review)
|
||
so Slice B/C wins are attributable to cold-vs-warm portals.
|
||
- LOH size + fragmentation (+POH) checkpoint fields (landed post-review; the
|
||
audit's own JSON showed 65% LOH fragmentation, never analyzed).
|
||
- Fixed-camera screenshot COMPARISON tooling (pulled forward from Slice G)
|
||
with a defined match rule (per-channel tolerance + max differing-pixel
|
||
fraction; pinned `/time`, pinned camera, particles pinned-seed or masked).
|
||
Slice C's visual gate depends on it existing.
|
||
- A committed reference-configuration block (CPU, GPU, driver, OS build,
|
||
display path, resolution, refresh, VSync state, re-baseline rule) — the
|
||
§11 phrase "reference local hardware" is undefined without it.
|
||
|
||
**Exit criterion (blocks Slice C, per §0.5):** committed uncapped-local and
|
||
capped-local nine-stop baselines, a pinned dense-town uncapped baseline
|
||
(name the town, pin the camera), and per-portal allocation/frame timelines
|
||
around each teleport materialization.
|
||
|
||
**Progress (2026-07-24):** the corrected GL timer, per-frame history, process
|
||
counter/trace capture, screenshot comparison, reference configuration, binary
|
||
provenance, and route-bound stationary sampling have landed. Corrected capped
|
||
and uncapped nine-stop RDP routes, a pinned dense-Arwic RDP sample, observer
|
||
effect, portal windows, and contention stacks are committed in
|
||
[`../research/2026-07-24-slice-a-rdp-baselines.md`](../research/2026-07-24-slice-a-rdp-baselines.md)
|
||
and its machine-readable companion. They prove the tooling is usable but are
|
||
diagnostic-only. The authoritative capped/uncapped nine-stop and pinned
|
||
dense-Arwic repetitions are committed in
|
||
[`../research/2026-07-24-slice-a-physical-local-baselines.md`](../research/2026-07-24-slice-a-physical-local-baselines.md)
|
||
and its machine-readable companion. All routes completed and disconnected
|
||
gracefully. They also preserve the two repeated-portal memory plateau failures,
|
||
~200 MiB portal allocation frames, and typed-Setup exception storm as Slice C's
|
||
before-state instead of weakening the gates. Slice A and Slice B are complete;
|
||
Slice C is unblocked.
|
||
|
||
### Slice B — Finish MP1b EnvCell dedup and full bake
|
||
|
||
**Purpose:** Make the existing pak physically usable.
|
||
|
||
- Add shared-blob/alias support to `PakWriter` while retaining unique source
|
||
keys.
|
||
- Compute the existing runtime EnvCell geometry identity before extraction.
|
||
- Extract each unique geometry once; map all source file IDs to the shared blob.
|
||
- Make side-staged particle preloads and ordinary assets use the same
|
||
deterministic content table.
|
||
- Add alias, collision, determinism, stale-version, corruption, cancellation,
|
||
and atomic-replace tests.
|
||
- Run the complete bake and publish counts, unique ratios, size, time, failures,
|
||
and peak memory.
|
||
|
||
**Gate:** full bake finishes in practical time/space; all fixture and random
|
||
sample equivalence tests pass; duplicate EnvCells share blob offsets; no runtime
|
||
code consumes the pak yet.
|
||
|
||
**Completed 2026-07-24:** 729,888 EnvCell keys collapsed to 17,117 unique
|
||
geometries plus 712,771 aliases (42.6×), with 751,141 total keys, 38,370
|
||
physical blobs, zero failures, a 28,192.4 MiB artifact, and an 81.4-second
|
||
validated atomic publication. The gate also exposed and corrected a real
|
||
collision in WorldBuilder's legacy 31× geometry hash. Evidence:
|
||
[`../research/2026-07-24-slice-b-full-bake-report.md`](../research/2026-07-24-slice-b-full-bake-report.md).
|
||
|
||
### Slice C — Runtime prepared-asset source and MP1c cutover
|
||
|
||
**Purpose:** Remove mesh/texture decode from portal frames.
|
||
|
||
- Introduce `IPreparedAssetSource` in Content and inject it into App streaming.
|
||
- Teach `ObjectMeshManager`/landblock builders to adopt pak-backed prepared
|
||
payloads without reconstructing complete intermediate object graphs.
|
||
- Add compact Setup presentation metadata to the bake or a keyed prepared cache
|
||
so static activation does not parse arbitrary IDs as Setup.
|
||
- Replace `ResolveId().ToList().OrderBy...` with typed non-allocating lookup.
|
||
- Add explicit negative/type metadata; exceptions are not type tests.
|
||
- Retain live extraction only in bake/equivalence tooling and required dynamic
|
||
appearance paths.
|
||
- Remove production streaming fallback at the gate.
|
||
|
||
**Gate:** byte/field equivalence; no invalid Setup exception storm; portal
|
||
single-frame allocation and p99 materially improve from the 2026-07-24 baseline;
|
||
connected nine-stop route and screenshots pass.
|
||
|
||
**Completed 2026-07-24:** production streaming now consumes the validated
|
||
format-1/bake-tool-3 package through `IPreparedAssetSource`, with no implicit
|
||
live-DAT mesh fallback. Capped, uncapped, and dense-Arwic physical routes,
|
||
installed-DAT equivalence, lifecycle assertions, graceful teardown, the full
|
||
Release suite, and the user's visual gate passed. Process allocation fell
|
||
41.8–61.9%, GC pause time fell 48.4–54.1%, uncapped CPU/GPU p99 improved
|
||
17.0%/20.6%, and invalid typed-Setup exceptions fell to zero. Evidence:
|
||
[`../research/2026-07-24-slice-c-prepared-asset-cutover-report.md`](../research/2026-07-24-slice-c-prepared-asset-cutover-report.md).
|
||
|
||
**Amendment (2026-07-24 review).** Binding contracts for this slice:
|
||
|
||
- **Precedence contract.** The LINQ being replaced is load-bearing content
|
||
selection, not just allocation:
|
||
`OrderByDescending(r => r.Database == _dats.Portal)` means
|
||
**Portal-dat-wins** when an id resolves in more than one database. The
|
||
typed non-allocating lookup must encode that precedence explicitly, and
|
||
the equivalence fixtures must include at least one dual-dat id (an id
|
||
shadowed by `client_highres.dat`) so a precedence regression fails the
|
||
gate instead of silently changing pixels.
|
||
- **Seam fixtures.** The DAT-equivalence suite must include ObjDesc-composed
|
||
fixtures that straddle the bake/runtime split: a drudge, a robed player,
|
||
and palette-dyed armor (baked base mesh + runtime appearance overlay).
|
||
- **Cache identity rule.** Pak-backed default-palette pixels and
|
||
runtime-composed palette-overlaid pixels for the same surface id are
|
||
distinct cache identities and must never share a key.
|
||
- **Exception semantics.** When the probe storm is removed, also narrow
|
||
`ResolveActivation`'s bare `catch { return null; }` (and the sibling
|
||
`SequencerFactory`, which today has NO catch at all): catch only the
|
||
specific corrupt-data exception types, rethrow `OperationCanceledException`,
|
||
log anything unexpected. Otherwise the "no exception storm" gate can pass
|
||
while real activation failures stay invisible — the pattern CLAUDE.md's
|
||
no-workarounds rule names explicitly.
|
||
- **Baseline comparison validity.** "p99 materially improve" compares
|
||
against the post-Slice-A uncapped-local baseline, not the capped-RDP
|
||
2026-07-24 capture (whose steady p99 sits at the 31.2 ms display floor).
|
||
Portal single-frame allocation is FPS-independent and may compare against
|
||
either.
|
||
|
||
### Slice D — Typed asset handles and unified residency
|
||
|
||
**Purpose:** Bound and explain CPU/GPU memory.
|
||
|
||
- Add `AssetHandle<T>`, owner tokens, leases, generations, and accounting.
|
||
- Place object mesh, standalone texture, composite texture-array, staging, and
|
||
prepared-content residence behind one policy owner while retaining specialized
|
||
physical caches.
|
||
- Track logical, CPU, staging, GPU-requested, GPU-resident, and retiring bytes
|
||
separately.
|
||
- Add configurable budgets through `RuntimeOptions`/settings with current visual
|
||
behavior as the default.
|
||
- Implement generation-safe LRU/cost-aware eviction and fence-delayed physical
|
||
release.
|
||
- Expose exact budget/occupancy/fragmentation facts to diagnostics.
|
||
|
||
**Gate:** same-location third-visit residence plateaus; no stale-generation
|
||
release; no missing textures; no resource growth after repeated portal loops.
|
||
|
||
**Completed 2026-07-24:** typed asset/owner generations, leases, immutable
|
||
budget profiles, specialized-owner adapters, exact aggregate accounting,
|
||
bounded animation/audio/alpha-scratch policies, forced-pressure coverage, and
|
||
fence-aware retirement diagnostics are live. The capped/uncapped physical
|
||
nine-stop routes and dense Arwic passed with graceful shutdown. The third Caul
|
||
checkpoint reduced cache-attributed CPU/GPU residence from the second visit,
|
||
animations plateaued at 55 entries, scratch stayed below budget, and no
|
||
checkpoint retained staged or retiring bytes. Evidence:
|
||
[`../research/2026-07-24-slice-d-unified-residency-report.md`](../research/2026-07-24-slice-d-unified-residency-report.md).
|
||
|
||
**Amendment (2026-07-24 review).** Two corrections to this slice's scope:
|
||
|
||
- **Do not rebuild what exists.** Fence-gated, budgeted eviction already
|
||
runs per frame in three of the four systems this slice names:
|
||
`ObjectMeshManager` (1 GiB / 50-object LRU, reclaimed every frame),
|
||
`CompositeTextureArrayCache` (128/64 MiB budgets, per-frame throttled GL
|
||
deletion), and `GlobalMeshBuffer` (capacity shrink with hysteresis). The
|
||
2026-07-24 route never pushed those ceilings, so its "bounded" residency
|
||
was bounded-by-route, not policy-exercised. Slice D's job is to UNIFY
|
||
accounting and observability over the existing physical caches, add a
|
||
forced-pressure eviction test (deliberately exceed the ceilings), and
|
||
retune the budgets for multi-session load — plus bring policy to the
|
||
items that genuinely have none: deferred-alpha scratch arrays,
|
||
`ObjectMeshManager._boundsCache`, and the audio/animation content caches
|
||
(audio caches bounded post-review).
|
||
- **Concurrency contract before code.** The `ResidencyManager` design in
|
||
§5.3 lists states with no transitions. Before this slice starts, its plan
|
||
must specify: a transition table including failure/cancellation/corrupt
|
||
edges; which fields are single-thread-owned vs shared (policy decisions
|
||
on the update thread, physical release render-thread-only, accounting via
|
||
atomics or a single-writer journal); the rule that eviction never blocks
|
||
the render thread and never runs under a lock the render thread takes;
|
||
and that `ResidencyManager` never touches GL names (only the renderer
|
||
resource owners row of §4 may).
|
||
|
||
### Slice E — Cost-budgeted streaming and retirement
|
||
|
||
**Completed plan:** [`2026-07-24-modern-runtime-slice-e-cost-budgeted-streaming.md`](2026-07-24-modern-runtime-slice-e-cost-budgeted-streaming.md).
|
||
|
||
**Completed 2026-07-24:** E0 fixed the retail/adaptation contract and current
|
||
owner inventory. E1 landed the validated work profile, deterministic meter and
|
||
completion charge model. E2 cut execution over to stable priority FIFOs. E3
|
||
added immediate generation quiescence and cursored retirement. E4 advances
|
||
render/physics/static publication from retained exact receipts. E5 replaced
|
||
mutable priority-radius state with the reveal coordinator's exact
|
||
generation-scoped destination reservation, protected its share across every
|
||
typed budget dimension, removed forced early materialization, and added
|
||
retail's centered five-second portal wait cue.
|
||
|
||
E6's deterministic suites, Release build, complete tests, capped/uncapped
|
||
nine-stop routes, and pinned dense-Arwic route pass. The connected gate
|
||
corrected stale cache-hit staging and separated quiesced destination spatial
|
||
residency from gameplay visibility. Every canonical checkpoint converged with
|
||
zero pending publication/retirement/backlog, staged upload, composite warmup,
|
||
or retiring resource. Against Slice A, capped/uncapped CPU p99 improved
|
||
34.7%/30.4% and largest frame allocation fell 84.7%/82.2%; against Slice C,
|
||
CPU p99 improved 21.4%/16.1% and allocation fell 33.5%/9.7%. Evidence:
|
||
[`../research/2026-07-24-slice-e-cost-budgeted-streaming-report.md`](../research/2026-07-24-slice-e-cost-budgeted-streaming-report.md).
|
||
|
||
**Purpose:** Remove update-thread portal transactions.
|
||
|
||
- Introduce explicit stage queues and `StreamingWorkBudget`.
|
||
- Split immediate logical/spatial detach from budgeted owner/resource teardown.
|
||
- Make per-entity retirement cursor/time bounded.
|
||
- Make publication cursor/time/byte bounded.
|
||
- Reserve destination-critical work across portal frames rather than bypassing
|
||
all budgets on one frame.
|
||
- Preserve FIFO within priority/generation and exact retry receipts.
|
||
- Connect reveal to the single destination-generation readiness state.
|
||
|
||
**Gate (rescoped, 2026-07-24 review; completed):** zero
|
||
`viewport-before-ready`; no scheduler continuation after a configured
|
||
time/byte/entity limit (the documented first-indivisible-operation rule is
|
||
named explicitly); portal-window frame p99 and single-frame allocation
|
||
materially improve versus the post-Slice-A baseline; no stranded old
|
||
generation, staged upload, collision, effect, or GPU owner. The
|
||
program-level 16.67 ms p99 / 33.3 ms maximum targets are §11 gates verified
|
||
at Slice H — Slice E cannot own them while the audit's own P2 costs
|
||
(whole-world partition, unconditional UI/diagnostic work) are assigned to
|
||
Slices G and H.
|
||
|
||
**Amendment (2026-07-24 review).** Binding contracts for this slice:
|
||
|
||
- **Release-later never means run-later.** Retired-generation owners are
|
||
tick-frozen and audio-silenced at the immediate detach edge even when
|
||
their memory release is budgeted across later frames. Audio is added to
|
||
the immediate set (or bounded at ≤1 frame): a looping emitter on a
|
||
retired landblock is user-audible during portal transit, and audio
|
||
appears in neither of §6.2's lists today. Retail teardown anchors for the
|
||
slice plan: `CLandBlock::Destroy` 0x0052faa0, `destroy_static_objects`
|
||
0x0052fa50, `CObjectMaint::DestroyObjects` 0x00508c30 (retail teardown is
|
||
one synchronous transaction; deferral has retail precedent only via the
|
||
`AddObjectToBeDestroyed` queue, whose drain is complete-when-run).
|
||
- **Reveal stall behavior.** A reveal hold longer than retail's 5-second
|
||
fade ceiling keeps the tunnel animating, surfaces the retail "In Portal
|
||
Space - Please Wait" cue (AP-115), and emits a diagnostic — never a
|
||
silent early reveal. The `IsUnhydratable` loud-failure escape stays.
|
||
Retail anchor: `SmartBox::UseTime` 0x00455410 `blocking_for_cells`; the
|
||
1/2/5 s fade thresholds are recorded in
|
||
`claude-memory/project_portal_space.md`.
|
||
- **All count constants convert.** `MaxCompletionsPerFrame` is not the only
|
||
count-shaped budget: `MaxDrainIterations` (the ×64 outer drain ceiling)
|
||
and the priority-ring radius are part of the same redesign; and the
|
||
uncapped one-frame paths (`BeginFullWindowRetirement`,
|
||
`TryAdvanceOriginRecenterPreparation` — the ~600-landblock teleport
|
||
retire) are the primary offenders, not the 9-landblock eager ring.
|
||
- Deferred loads retained in `_deferredApply` during a burst are a
|
||
retained-memory high-water source; the staged pipeline must account for
|
||
them under the CPU-bytes budget.
|
||
|
||
### Slice F — Incremental render scene foundation
|
||
|
||
**Prepared plan:** [`2026-07-24-modern-runtime-slices-f-g-render-scene.md`](2026-07-24-modern-runtime-slices-f-g-render-scene.md).
|
||
|
||
**Purpose:** Stop rebuilding/partitioning the world each frame.
|
||
|
||
- Add acdream-owned `IRenderScene`, identifiers, components, and delta journal.
|
||
- Implement it with Arch in App only.
|
||
- Mirror static publication and live projection into a non-drawing shadow world.
|
||
- Add deterministic scene digest and compare it with current owners at
|
||
checkpoints.
|
||
- Maintain cell/outdoor/dynamic/translucent/light indices incrementally.
|
||
- Add stale-incarnation, duplicate-create, rebucket, delete/recreate, hidden,
|
||
parent-child, and session-reset tests.
|
||
|
||
**Gate:** shadow scene matches canonical world/resource checkpoints through the
|
||
nine-stop route with bounded memory and zero authoritative ownership.
|
||
|
||
**Amendment (2026-07-24 review).** Preconditions and contracts:
|
||
|
||
- **User approval required before this slice starts** (§0.1 — F/G resume
|
||
the deferred MP3 work).
|
||
- **Referee before the match.** The deterministic scene digest is built and
|
||
validated against the CURRENT pipeline in steady state as its own
|
||
preceding sub-slice, so the comparison tool has a proven track record
|
||
before it gates a mirror. During soak/dev runs the digest also runs
|
||
continuously through portal windows (every N frames), not only at the
|
||
nine checkpoints — checkpoint-only comparison is blind to drift that
|
||
self-corrects between stops, which peaks exactly when churn does.
|
||
- **Journal discipline.** The delta journal is drained to empty every host
|
||
tick while single-threaded (assert on carryover); deltas coalesce
|
||
last-writer per (entity, kind) between drains, bounding the journal at
|
||
O(live entities) instead of O(mutations); on any future thread
|
||
separation it adopts §6.1's bounded-queue discipline with an explicit
|
||
no-block rule for the update thread.
|
||
- **Interface home.** `IRenderScene`, its components, and the delta journal
|
||
types live in `AcDream.App` for Slices F–I. At Slice J the seam becomes
|
||
Runtime-emitted ordered entity events (incarnation + transform + flags,
|
||
no render vocabulary) that an App-side projection adapter maps to
|
||
`IRenderScene` deltas — write Slice F against that end-state so no render
|
||
type bakes into a contract Slice J must break.
|
||
- **Identity enforcement.** The §4 no-unchecked-cast rule gets a concrete
|
||
mechanism here: wrapper structs with a single `.Value` accessor plus a
|
||
grep-based architecture test (or analyzer) restricting that accessor to
|
||
owning assemblies.
|
||
|
||
### Slice G — Render snapshot and delta submission cutover
|
||
|
||
**Purpose:** Make render cost proportional to visible/changed data.
|
||
|
||
- Build the double-buffered `RenderFrameView`.
|
||
- Feed it the existing PView visible-cell/clip product.
|
||
- Replace full `InteriorEntityPartition` scans with render-scene bucket queries.
|
||
- Upload only dirty persistent instance ranges.
|
||
- Reuse MDI command/sort buffers.
|
||
- Build instance-set comparison and fixed-camera screenshot comparison.
|
||
- Run old and new submissions in compare mode without drawing twice.
|
||
- At the gate, delete the replaced production enumeration/submission path.
|
||
|
||
**Gate:** instance sets and screenshots match; dense-town uncapped target is
|
||
300 FPS or the corrected profile identifies a new dominant stage; steady frame
|
||
allocation is near zero.
|
||
|
||
**Amendment (2026-07-24 review).** The 300 FPS / 3.33 ms figure is
|
||
provisional until Slice A's corrected pass timing re-measures the dense-town
|
||
CPU-vs-GPU split on the local display (the prior "GPU-bound ~200 FPS"
|
||
reading was produced by the boundary-spanning `GpuFrameTimer` this plan
|
||
corrects, and the 2026-07-24 capture cannot settle it). Bucket queries must
|
||
preserve the retail dynamics contract: retail drops out-of-flood dynamics
|
||
via the per-dynamic viewcone CULL, not by set membership
|
||
(`RetailPViewRenderer.DrawDynamicsLast`) — replacing the whole-world walk
|
||
with buckets must not change which dynamics get culled. This slice also
|
||
retires the sibling per-frame rebuilds the audit missed:
|
||
`EnvCellRenderer.PrepareRenderBatches` (gated post-review) and the
|
||
nearby-building gather walk, both of which otherwise keep steady render cost
|
||
proportional to resident content.
|
||
|
||
### Slice H — Event-driven UI, diagnostics, lights, and frame cleanup
|
||
|
||
**Purpose:** Remove remaining work that scales with uncapped FPS.
|
||
|
||
**Execution plan (active 2026-07-25):**
|
||
[`2026-07-25-modern-runtime-slice-h.md`](2026-07-25-modern-runtime-slice-h.md).
|
||
The plan separates H-a text/UI/attachment/frame work, H-b's exact light-set
|
||
optimization, and H-c's ordered network-I/O change into independently
|
||
revertible gates.
|
||
|
||
- Skip diagnostics with no consumer and reuse renderer visibility facts.
|
||
- Dirty-layout retained UI: apply anchors only after geometry changes.
|
||
- Maintain an overlay-participant registry instead of a second full-tree walk.
|
||
- Track active cooldown items/effects/dialogs through events.
|
||
- Replace per-frame frame-input objects, LINQ arrays, iterator objects, and
|
||
liveness collections with reusable storage or borrowed views.
|
||
- Make light candidates spatial/cell-driven and select top-k without a complete
|
||
per-frame sort.
|
||
- Replace socket-timeout exceptions and datagram `ToArray` copies with normal
|
||
cancellable/poll/span-based I/O.
|
||
|
||
**Gate:** no steady-state Gen-2 collections; steady allocation target
|
||
≤ 4 KiB/frame initially and zero for the core world/render loop; UI and network
|
||
behavior tests unchanged; process-wide steady allocation rate within the §11
|
||
per-scene target (frame-thread numbers alone are gameable, §0.4); the
|
||
program-level traversal frame-time targets from §11 are verified here.
|
||
|
||
**Amendment (2026-07-24 review).** This slice splits into three separately
|
||
gated pieces:
|
||
|
||
- **H-a — UI/diagnostics/frame cleanup.** Includes: `UiText` shaping cache
|
||
(appraisal/character-info/effects panels re-run full word-wrap every
|
||
visible frame; apply the existing `ChatWindowController.GetTranscriptLines`
|
||
revision+width+font cache pattern — it is already proven in-tree), the
|
||
cooldown scan's visibility scoping (it is O(all mounted item-list slots)
|
||
regardless of open windows — the worst-scoped of the audited UI items),
|
||
diagnostics consumer-gating (reuse the AABB results as the partition's
|
||
landblock frustum cull rather than deleting them — the same test is
|
||
computed and discarded one call away from where it is needed),
|
||
`EnvCellRenderer` prepare gating (landed post-review), and the per-frame
|
||
scratch items (RetailPViewFrameInput reuse, iterator layers).
|
||
- **H-b — Lights (pinned; two-failure regression history, register AP-85).**
|
||
The candidate pool remains the resident registry (`_all`); any narrowing
|
||
is the existing latency-buffered visible-cell FILTER only; the over-cap
|
||
ranking stays dynamics-first + nearest-PLAYER; the only change is
|
||
replacing the >MaxGlobalLights overflow `List.Sort` with a top-k partial
|
||
select that produces the IDENTICAL selected set. Retail anchors to cite
|
||
in the slice plan: `CEnvCell::add_dynamic_lights` 0x0052d410,
|
||
`Render::insert_light` 0x0054d1b0, `Render::add_dynamic_light`
|
||
0x0054d420, caps 0x0081ec94/98. Gate adds: selected-set equivalence on
|
||
captured Town Network fixtures (463 lights) + fixed-camera screenshot
|
||
comparison. Note the audit overstated this item: per-object/per-cell
|
||
selection is already an allocation-free top-8 insertion; only the
|
||
overflow path sorts.
|
||
- **H-c — Network I/O (requires §0.3 authorization).** Scope includes the
|
||
receive-side per-packet allocation chain the audit missed (fresh
|
||
`UdpClient.Receive` array + `BodyBytes` copy + per-fragment `ToArray` +
|
||
per-packet `Packet`/`List`/`Optional` objects), not only the outbound
|
||
`ToArray`. Invariants pinned: single outstanding receive, kernel arrival
|
||
order preserved into the inbound queue, ack-per-received-packet at decode
|
||
acceptance (holtburger pattern), `LinkStatusHolder::OnHeartbeat`
|
||
0x004113D0 last-heard placement unchanged, `PumpOnce` handshake pacing
|
||
preserved. (Receive-thread death robustness landed post-review as an
|
||
approved bug fix.)
|
||
|
||
### Slice I — Flat collision assets and residual zero-allocation work
|
||
|
||
**Purpose:** Complete MP4 without changing retail math.
|
||
|
||
- Bake flattened index-based BSP/collision records.
|
||
- Port traversal data access line-for-line while preserving ordering and
|
||
arithmetic.
|
||
- Remove parsed DBObj graphs from streaming collision publication.
|
||
- Address remaining measured allocation sites, including any physics transition
|
||
pooling only after identity/lifetime tests prove it safe.
|
||
|
||
**Gate:** trajectory and retail conformance suites remain bit-equivalent;
|
||
navigation/collision fixtures match; portal and steady allocation targets pass.
|
||
|
||
**Amendment (2026-07-24 review).** Bit-equivalence across the layout change
|
||
is achievable (RyuJIT SSE scalar IEEE-754; the x87 concerns in project
|
||
memory are decomp-READING lessons, not runtime determinism) — but only
|
||
under two conditions the slice plan must state: (a) the bake stores
|
||
dat-parsed floats VERBATIM, zero arithmetic transformation (no plane
|
||
renormalization, no quantization, no double round-trips); (b) traversal
|
||
preserves front/back child visit order and every epsilon comparison. The
|
||
existing suites are scenario-bounded, so add an old-vs-flat DIFFERENTIAL
|
||
sweep during the shadow phase: mass BSPQuery/point-in-cell/sweep queries
|
||
across a large cell sample, outputs compared bit-for-bit between graph and
|
||
flat implementations while both coexist. Physics transition pooling stays
|
||
behind its identity/lifetime-test condition. This slice floats — it depends
|
||
only on Slice B's bake infrastructure, not on F–H.
|
||
|
||
### Slice J — Presentation-independent `AcDream.Runtime`
|
||
|
||
**Purpose:** Establish the graphical/headless shared client kernel.
|
||
|
||
- Add the Runtime project and dependency guards.
|
||
- Define `IGameRuntimeView`, commands, ordered events, clock, and lifecycle.
|
||
- Adapt the current graphical host to those interfaces first.
|
||
- Remove App presentation dependencies from canonical gameplay owners.
|
||
- Move owners by coherent lifetime groups; never mirror state.
|
||
- Add host-parity and no-backend construction tests after each move.
|
||
- End with `GameWindow`/App composing one `GameRuntime`.
|
||
|
||
**Gate:** the connected graphical route is unchanged; a no-window integration
|
||
test connects, enters world, moves, receives inventory/chat/world updates,
|
||
portals, logs out, reconnects, and tears down without loading App/Silk/OpenAL.
|
||
|
||
**Amendment (2026-07-24 review).** Requires §0.3 authorization (moves
|
||
frozen-subsystem owners). Add a **teardown protocol** subsection to §9.2
|
||
before this slice starts — session reset, logout, and mid-portal disconnect
|
||
traverse five new owners, and tests without a specified order mean the
|
||
order gets invented per-slice: (1) cancel scheduler generations; (2) drain
|
||
or poison journals/queues; (3) unpublish the render scene; (4) release
|
||
residency, fence-gated; (5) `ContentStore` last (shared, outlives
|
||
sessions). Every new owner registers with the existing
|
||
`CompositionAcquisitionScope` lease discipline so disposal order is
|
||
structural, not remembered. This slice is a multi-plan track (the
|
||
comparable `GameWindow` decomposition took 8 slices for 14K lines), not a
|
||
single bisectable slice — plan it as lifetime-group sub-slices with
|
||
host-parity tests after each move.
|
||
|
||
### Slice K — Linux headless and multi-session host
|
||
|
||
**Purpose:** Deliver efficient automated bots.
|
||
|
||
- Add Linux CI for Core, Core.Net, Content, Runtime, and Headless.
|
||
- Implement portable path/config/credential handling.
|
||
- Add `AcDream.Headless` CLI and deterministic scheduler.
|
||
- Add bot command/event APIs for movement, selection, use, combat, spells,
|
||
looting, chat, and commands.
|
||
- Share immutable content/collision stores across sessions.
|
||
- Audit and eliminate mutable statics and process-wide session state.
|
||
- Add clean cancellation, reconnect, plugin isolation, and credential-safe logs.
|
||
- Stress 1, 5, 10, and 30 sessions.
|
||
|
||
**Gate:** 30 local-server sessions in one Linux process, no GPU/display/audio
|
||
dependency, bounded incremental memory per session, no busy-loop CPU, clean
|
||
teardown/reconnect, and parity with graphical runtime command/event behavior.
|
||
|
||
**Amendment (2026-07-24 review).** The parity gate covers packet ORDER but
|
||
not inter-packet TIMING — and AutonomousPosition cadence is already a
|
||
tracked divergence (register row TS-33,
|
||
`claude-memory/project_retail_motion_outbound.md`). Add a wire-timing
|
||
assertion: AutonomousPosition inter-packet intervals and
|
||
MoveToState-on-change emission points within a stated tolerance band versus
|
||
the graphical host on the same scenario; the time-wheel scheduler must
|
||
derive wire cadence from the instance clock, never from tick count. The
|
||
30-session stress gets an explicit duration measured in HOURS (the
|
||
characteristic 30-bot failure is slow per-session creep — the #193 class
|
||
leaked ~1 GiB/min and was found by OOM, not by a 6-minute route), with
|
||
per-session incremental memory given a numeric ceiling at gate time.
|
||
|
||
### Slice L — Linux graphical host and evidence-gated GPU work
|
||
|
||
**Purpose:** Finish platform portability and only then pursue remaining GPU
|
||
opportunities.
|
||
|
||
- Validate Linux OpenGL extension/driver matrix and package native dependencies.
|
||
- Port paths, frame pacing, input, audio, and packaging without renderer
|
||
fallback.
|
||
- Reprofile locally with accurate pass timing.
|
||
- Add GPU culling, command compaction, particle simulation, or light selection
|
||
only for stages proven dominant.
|
||
|
||
**Gate:** graphical Linux connected route passes the same lifecycle, screenshot,
|
||
resource, and performance checks; every GPU migration has a CPU/GPU before/after
|
||
and visual equivalence result.
|
||
|
||
---
|
||
|
||
## 11. Performance gates
|
||
|
||
Reference measurements use a Release build, the same account/route, stable
|
||
camera scripts, and both capped and uncapped modes. RDP results are kept
|
||
separate from local-display results.
|
||
|
||
| Metric | Target |
|
||
|---|---:|
|
||
| Traversal frame p99 | ≤ 16.67 ms on reference local hardware |
|
||
| Traversal maximum | ≤ 33.3 ms after warm process startup |
|
||
| Dense-town uncapped frame p50 | ≤ 3.33 ms or a newly attributed blocker |
|
||
| Steady update p95 | ≤ 2 ms |
|
||
| Core world/render allocation | 0 B/frame after warmup |
|
||
| Whole-client steady allocation | initial gate ≤ 4 KiB/frame |
|
||
| Portal single-frame allocation | ≤ 4 MiB, then tighten from evidence |
|
||
| Gen-2 collections during canonical route | 0 after startup/bake warmup |
|
||
| Same-location third-visit resource growth | ≤ 5% and explained |
|
||
| World-visible-before-ready events | 0 |
|
||
| Staged resources at stable checkpoints | 0 |
|
||
| Exception-as-control-flow | 0 known sites |
|
||
| Headless renderer/audio/window allocations | 0 |
|
||
|
||
Memory budgets are measured by category rather than only process working set:
|
||
|
||
- Live managed heap.
|
||
- GC committed/fragmented.
|
||
- Native prepared/staging buffers.
|
||
- Mapped content pages.
|
||
- Tracked GPU buffers/textures.
|
||
- Driver-reported dedicated/shared GPU memory.
|
||
|
||
Absolute process/VRAM targets are fixed after Slice D produces trustworthy
|
||
category accounting; until then, the binding rule is plateau plus no regression
|
||
in pixels/range.
|
||
|
||
**Amendment (2026-07-24 review).** Gate-table corrections and additions:
|
||
|
||
- **Percentile populations.** Until the Slice A per-frame history export is
|
||
the population, every p99 row reads "no 5-second window with p99 > X" —
|
||
the windowed profiler cannot produce route-wide percentiles.
|
||
- **Anti-gaming complement.** New row: process-wide steady allocation rate
|
||
per scene (dotnet-counters), alongside the frame-thread rows. The
|
||
frame-thread gates alone reward moving garbage to workers, which is this
|
||
plan's own remediation shape.
|
||
- **Provisional budgets.** "≤ 4 MiB portal" and "≤ 4 KiB steady" are
|
||
round-number initial budgets, not derived values; tighten from post-A/C
|
||
evidence. "0 B/frame" means alloc p50 = 0 with every nonzero frame
|
||
attributable to the profiler/report path (the instrument itself allocates
|
||
its 5-second report on the frame thread).
|
||
- **Third-visit growth ≤ 5%** is scoped per metric class: same-location
|
||
tracked CPU/GPU residence measured against the location's own asset set
|
||
(possible after Slice D accounting); entity/emitter/particle counts stay
|
||
warnings on a live ACE world; absolute byte deltas reported alongside
|
||
percentages. The audit's own data shows legitimate +31.7% Sawato
|
||
tracked-GPU growth from route-wide cache warming — the unscoped gate
|
||
false-fails on exactly that.
|
||
- **New row — long-duration soak:** ≥ 60 minutes looping the route; managed
|
||
heap, LOH size + fragmentation, tracked GPU, handles, and threads all
|
||
plateau within stated bands. (The canonical route is 359 s; the #193
|
||
leak class is invisible to it.)
|
||
- **New row — frame-pacing jitter** (stddev / 99.9th) once the per-frame
|
||
export exists.
|
||
- **Scheduled harness.** Slice A or H tasks a scheduled local perf harness:
|
||
run the committed route against the last committed baseline JSON with the
|
||
soak script's relative-limit pattern applied cross-commit, failing
|
||
loudly. Manual-and-unscheduled is not a regression net.
|
||
- "Reference local hardware" refers to the committed reference-configuration
|
||
block required by Slice A; gates against an undefined machine are
|
||
unfalsifiable.
|
||
|
||
---
|
||
|
||
## 12. Test and review matrix
|
||
|
||
Every slice must pass the tests relevant to its boundary:
|
||
|
||
1. **Pure unit tests:** handles, generations, budgets, queues, manifest,
|
||
deduplication, stale/corrupt data, snapshot buffers.
|
||
2. **DAT equivalence:** live extractor versus prepared asset for representative
|
||
outdoor, dungeon, town, portal, animated, translucent, and particle assets.
|
||
3. **Scene equivalence:** canonical entity/instance digest at fixed checkpoints.
|
||
4. **Render equivalence:** instance-set diff plus fixed-camera screenshots.
|
||
5. **Retail conformance:** named-retail citations for any AC-specific selection,
|
||
degradation, visibility, collision, or timing behavior touched.
|
||
6. **Lifecycle:** fresh login, same-location revisit, world edge, dungeon,
|
||
repeated recalls, rapid generation replacement, logout/reconnect, graceful
|
||
close, cancellation, and failure injection.
|
||
7. **Resource:** exact create/retire counts, heap diff, GPU accounting, no stale
|
||
owner or queued work at stable checkpoints.
|
||
8. **Performance:** capped and uncapped CPU/GPU/allocation/GC percentiles.
|
||
9. **Headless parity:** same runtime packet/order/command results with
|
||
presentation attached or absent.
|
||
10. **Platform:** Windows graphical/headless plus Linux headless; Linux graphical
|
||
begins in Slice L.
|
||
|
||
For a replacement cutover:
|
||
|
||
- Shadow/compare mode may exist only during development.
|
||
- Confirmed differences are fixed at their source.
|
||
- The gate commit removes the old production path.
|
||
- No test threshold is loosened to accept a regression.
|
||
|
||
**Adjudication protocol (2026-07-24 review).** When a cutover comparison
|
||
disagrees: (1) any confirmed OLD-path bug fixed during the cutover gets its
|
||
divergence-register row or `docs/ISSUES.md` entry in the same commit;
|
||
(2) visual differences are adjudicated by the user (visual verification is
|
||
the user's role per CLAUDE.md), never self-certified; (3) any threshold
|
||
change requires a written rationale in the gate commit — "the new output is
|
||
an improvement" carries the same evidence burden as a regression claim.
|
||
|
||
---
|
||
|
||
## 13. Risks and controls
|
||
|
||
| Risk | Control |
|
||
|---|---|
|
||
| Pak repeats the 865 GB failure | content identity and aliasing land before another full bake; bake reports unique/shared ratios continuously |
|
||
| Prepared data changes pixels/geometry | byte/field equivalence against the current extractor; no compression in the equivalence phase |
|
||
| Streaming budget reveals an incomplete world | one destination-generation readiness barrier; reveal is an atomic state edge |
|
||
| Deferred retirement leaks or revives stale resources | incarnation/generation owner tokens and stable-checkpoint zero-outstanding assertions |
|
||
| Render ECS becomes a second game world | render-only component contract; no network properties or authoritative GUID map; digest checks against canonical owner |
|
||
| Snapshot adds a frame of latency | same-thread borrowed snapshot first; frame-number assertions; concurrency remains deferred |
|
||
| Arch dependency constrains the design | acdream-owned `IRenderScene`; Arch only in App; no Arch types cross the boundary |
|
||
| Headless extraction duplicates state | move the existing canonical owner in lifetime groups; adapters first, never synchronize two stores |
|
||
| Shared 30-client cache gains mutable state | immutable content only; tests create conflicting sessions/characters concurrently |
|
||
| Linux work forces renderer fallback | graphical host retains mandatory modern extensions and fails clearly on unsupported drivers |
|
||
| “Optimization” changes retail behavior | unchanged behavior is a hard gate; retail ports remain OO and traceable; visual/connected gates stay mandatory |
|
||
|
||
---
|
||
|
||
## 14. Current execution point
|
||
|
||
Slices A–H are complete. The retained render scene is now the sole production
|
||
entity source: G5 removed the old whole-world `InteriorEntityPartition` from
|
||
normal mesh and attached-particle routing while preserving it only for
|
||
standalone tests and explicit diagnostic/oracle probes. The user accepted the
|
||
retained-path visual matrix. Capped/uncapped/dense routes and the exact
|
||
seven-checkpoint lifecycle/reconnect gate pass with graceful shutdown.
|
||
|
||
The final ordinary-production minute on exact `14fbe92b` sustained 519.7 FPS:
|
||
CPU p50/p95/p99 1.869/2.306/2.484 ms and GPU
|
||
1.096/1.108/1.136 ms. Working/private memory was 652.1/928.3 MiB.
|
||
Frame-thread allocation at the G5 checkpoint was 22.34 KiB median. I1 has now
|
||
replaced fresh per-resolve transition graphs and query temporaries with
|
||
reset-complete retained scratch: player, remote, projectile, camera, and
|
||
grounded walkable-publication profiles each measure 0 B/resolve, while
|
||
fresh-vs-reused output and body state remain bit-identical. I2 now supplies
|
||
validated immutable flat collision records: synthetic and installed-DAT
|
||
source identity, exact float bits, deterministic pre-order, and corruption
|
||
tripwires pass without changing production traversal. I3 now packages all
|
||
flat collision/topology records through a shared typed mmap source: two
|
||
complete 2,232,170-key bakes are byte-identical with zero failures. I4–I6 then
|
||
proved exact flat traversal, strict dual publication, and flat-authoritative
|
||
gameplay before removing every production parsed collision graph. Slice I7
|
||
passes 8,413 Release tests / 5 skips plus both exact-binary connected routes;
|
||
every stable checkpoint reports zero retained parsed collision graphs. J1 is
|
||
complete with borrowed contracts, and J2 owns canonical session/transport
|
||
lifetime plus ordered inbound routing in Runtime. J3.1–J3.6 are complete:
|
||
Runtime owns the only entity GUID/incarnation/local-ID directory, and App's
|
||
materialized sidecars plus spatial/presentation worksets carry exact
|
||
`RuntimeEntityKey`. One `RuntimeEntityObjectLifetime` also owns the exact live
|
||
`ClientObjectTable`; App is a synchronous borrower. Exact `ce3ac310` adds the
|
||
one per-generation canonical entity/object delta stream, issues Runtime
|
||
identity before App hydration, and gives graphical/no-window hosts the same
|
||
direct borrowed views. J3.6 at `119b7c11` retains exact teardown receipts
|
||
before fallible callbacks, serializes re-entrant observation within the
|
||
committing call, protects every accepted channel by mutation version, and
|
||
requires reset/direct disposal to converge the complete ownership ledger to
|
||
zero. Its 8,484-test / 5-skip Release suite, exact-binary lifecycle/reconnect
|
||
gate, and canonical nine-stop route pass. J4 is complete at `89e6b207`: the
|
||
single Runtime shortcut/spellbook/inventory owners, graphical/no-window
|
||
command parity, failure-safe combined ledger, 8,544-test Release suite,
|
||
exact-binary lifecycle/reconnect gate, and canonical nine-stop route pass.
|
||
J5 movement/physics/interaction/combat ownership is the current execution
|
||
point. J5.1–J5.2 are complete; J5.3 combat/magic intent is active.
|
||
Evidence:
|
||
[`../research/2026-07-25-slice-g5-production-profile.md`](../research/2026-07-25-slice-g5-production-profile.md)
|
||
and
|
||
[`../research/2026-07-25-slice-i1-transition-scratch.md`](../research/2026-07-25-slice-i1-transition-scratch.md)
|
||
and
|
||
[`../research/2026-07-25-slice-i2-flat-collision-schema.md`](../research/2026-07-25-slice-i2-flat-collision-schema.md)
|
||
and
|
||
[`../research/2026-07-25-slice-i3-prepared-collision-package.md`](../research/2026-07-25-slice-i3-prepared-collision-package.md)
|
||
and
|
||
[`../research/2026-07-25-slice-i7-closeout.md`](../research/2026-07-25-slice-i7-closeout.md)
|
||
and
|
||
[`../research/2026-07-25-slice-j2-session-lifetime-closeout.md`](../research/2026-07-25-slice-j2-session-lifetime-closeout.md)
|
||
and
|
||
[`../research/2026-07-25-slice-j3-3-exact-projection-store.md`](../research/2026-07-25-slice-j3-3-exact-projection-store.md)
|
||
and
|
||
[`../research/2026-07-26-slice-j3-5-canonical-delta-stream.md`](../research/2026-07-26-slice-j3-5-canonical-delta-stream.md)
|
||
and
|
||
[`../research/2026-07-26-slice-j3-6-lifetime-closeout.md`](../research/2026-07-26-slice-j3-6-lifetime-closeout.md)
|
||
and
|
||
[`2026-07-25-modern-runtime-slice-j.md`](2026-07-25-modern-runtime-slice-j.md).
|
||
|
||
The intended order is therefore:
|
||
|
||
```text
|
||
honest metrics + committed baselines (A — exit criteria block C)
|
||
-> prepared content (B, C)
|
||
-> typed residence (D — unify/retune, not rebuild)
|
||
-> cost-budgeted streaming (E)
|
||
-> [APPROVED 2026-07-24: ECS deferral lifted under §0.1]
|
||
-> incremental render scene (F)
|
||
-> delta GPU submission (G)
|
||
-> residual frame cleanup (H-a, H-b, H-c)
|
||
-> flat collision assets (I — COMPLETE)
|
||
-> presentation-independent runtime (J — CURRENT at J5)
|
||
-> Linux/headless/multi-session (K)
|
||
-> evidence-gated GPU jobs (L)
|
||
```
|
||
|
||
This order improves the current graphical client first while laying clean
|
||
boundaries for Linux and approximately 30 automated headless clients later.
|
||
|
||
**Execution discipline (2026-07-24 review):** this plan runs in dedicated
|
||
Track MP/LH side-track sessions only; the active milestone's critical path
|
||
wins every conflict (§0.2). Slice I appears in this list explicitly because
|
||
both earlier order summaries omitted it. The 2026-07-24 review also landed
|
||
a set of independent fixes ahead of the plan (receive-thread robustness,
|
||
bounded audio caches, dead ParticleBatcher chain deletion, CellStruct clone
|
||
guard, solid-color texture cache, EnvCellRenderer prepare gating,
|
||
measurement tooling) — those are recorded in the relevant slices above as
|
||
"landed post-review" and must not be re-done.
|