300 lines
14 KiB
Markdown
300 lines
14 KiB
Markdown
# Modern Runtime Slice E — Cost-budgeted streaming and retirement
|
|
|
|
**Status:** active — E0 contract/inventory complete
|
|
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
|
**Baseline:** Slice D closeout commit `66690805`
|
|
**Behavior contract:** no visual-quality or retail-behavior reduction
|
|
|
|
## 1. Outcome
|
|
|
|
Slice E removes whole-window portal publication and retirement transactions
|
|
from a single update frame. It preserves the existing worker, canonical
|
|
`GpuWorldState`, presentation owners, retry receipts, world-reveal barrier,
|
|
and renderer resource owners.
|
|
|
|
The change is scheduling and lifecycle architecture:
|
|
|
|
- one immutable `StreamingWorkBudget` defines real per-frame limits;
|
|
- one single-writer scheduler admits and advances explicit work stages;
|
|
- destination-critical work receives reserved capacity across portal frames;
|
|
- old generations become unavailable and quiescent immediately;
|
|
- expensive owner/resource teardown resumes from exact cursors;
|
|
- publication advances through exact owner receipts under time, byte, entity,
|
|
upload, and retirement-operation limits;
|
|
- the existing `WorldRevealCoordinator` remains the only reveal predicate.
|
|
|
|
No distance, texture, particle, cell, collision, or presentation quality is
|
|
reduced.
|
|
|
|
## 2. Retail boundary
|
|
|
|
Retail teardown is synchronous:
|
|
|
|
- `CLandBlock::destroy_static_objects @ 0x0052FA50`;
|
|
- `CLandBlock::Destroy @ 0x0052FAA0`;
|
|
- `CObjectMaint::DestroyObjects @ 0x00508C30`.
|
|
|
|
Retail portal/cell blocking freezes object maintenance, physics, landscape,
|
|
game time, and ambient sound while continuing scene/UI/event work:
|
|
`SmartBox::UseTime @ 0x00455410`.
|
|
|
|
Acdream's async adaptation may defer physical cleanup, but may not leave a
|
|
retired owner active. Full translation:
|
|
[`../research/2026-07-24-retail-streaming-retirement-pseudocode.md`](../research/2026-07-24-retail-streaming-retirement-pseudocode.md).
|
|
|
|
## 3. Existing-owner inventory
|
|
|
|
| Concern | Current owner | Current behavior | Slice E action |
|
|
|---|---|---|---|
|
|
| Worker inbox/outbox | `LandblockStreamer` | unbounded channels; near jobs prioritized | retain worker; expose bounded, allocation-free single-consumer drain and backlog facts |
|
|
| Completion apply | `StreamingController._deferredApply` | retained `List`; count-budgeted loads; unload and priority ring bypass | replace with explicit generation/priority FIFO stage queues and exact retained-byte accounting |
|
|
| Full reload | `BeginFullWindowRetirement` | snapshots and begins every resident retirement synchronously | retain stable snapshot/cursor and advance admissions under budget |
|
|
| Origin recenter | `OriginRecenterRetirement` | has a cursor but drains it to completion in one call | make the cursor the canonical budgeted admission receipt |
|
|
| Logical/spatial detach | `GpuWorldState.DetachLandblock/DetachNearLayer` | exact immediate bucket withdrawal; captures entity receipt | preserve; add destination-generation quiesce before cursored detaches |
|
|
| Retirement ledger | `LandblockRetirementCoordinator` | retryable per-stage/per-entity state, but advances a complete ticket on begin/advance | expose one bounded atomic step at a time; stable FIFO |
|
|
| Retirement effects | `LandblockPresentationRetirementOwner` | lights, translucency, plugin, terrain, physics, cells/buildings | preserve order; each entity/stage consumes measured budget |
|
|
| Publication ledger | `LandblockPresentationPipeline` | retryable stage booleans, but `Advance` runs the complete transaction | convert booleans/cursors into resumable atomic publication steps |
|
|
| Render publication | `LandblockRenderPublisher` | terrain/visibility/AABB/buildings/EnvCells whole stages | retain receipts; meter GPU bytes and split iterable registries where needed |
|
|
| Physics publication | `LandblockPhysicsPublisher` | rebuilds terrain/cells/buildings/statics/reflood in one call | cursor cell/building/static preparation and commit at safe mutation boundaries |
|
|
| Static presentation | `LandblockStaticPresentationPublisher` | allocates dictionaries/sorts arrays, then loops prior/plugin entities | prepare stable ordered arrays once; cursor cleanup/light/plugin work |
|
|
| Mesh upload | `ObjectMeshManager`/`WbMeshAdapter` | existing per-frame renderer owner/budgets | report exact upload bytes/operations to the streaming meter; never move GL ownership |
|
|
| Reveal | `WorldRevealCoordinator` + `WorldRevealReadinessBarrier` | one correct login/portal predicate | add destination generation/readiness stage facts; never duplicate predicate |
|
|
| Portal wait cue | `PortalTunnelPresentation` | authored tunnel and centered wait notice port exists | hold tunnel past five seconds and publish diagnostic; never early reveal |
|
|
| Audio | `OpenAlAudioEngine`/`AudioHookSink` | one-shot owner is not retained in source slot | tag 3-D voices by owner; stop old-generation owners at quiesce |
|
|
|
|
## 4. Work model
|
|
|
|
### 4.1 Immutable budget
|
|
|
|
The App-layer type is equivalent to:
|
|
|
|
```csharp
|
|
public readonly record struct StreamingWorkBudget(
|
|
TimeSpan MaxUpdateTime,
|
|
int MaxCompletionAdmissions,
|
|
long MaxAdoptedCpuBytes,
|
|
int MaxEntityOperations,
|
|
long MaxGpuUploadBytes,
|
|
int MaxGlRetireOperations,
|
|
float DestinationReserveFraction);
|
|
```
|
|
|
|
All fields are positive and validated as one profile. The initial High/default
|
|
profile is evidence-tuned, not a reduction in quality:
|
|
|
|
- 2.0 ms update-thread streaming work;
|
|
- 64 completion admissions;
|
|
- 8 MiB retained CPU payload adoption;
|
|
- 256 entity operations;
|
|
- 8 MiB requested GPU upload;
|
|
- 64 logical GL-retirement admissions;
|
|
- 75% reserved for destination-critical work while a reveal is pending.
|
|
|
|
These are initial scheduling ceilings, not content ceilings. Work continues on
|
|
later frames until exact convergence. Connected E gates may retune the profile
|
|
as one atomic change.
|
|
|
|
The compatibility `MaxCompletionsPerFrame` setting maps to complete Low/Medium/
|
|
High/Ultra work profiles during migration. It does not remain an independent
|
|
hidden second throttle.
|
|
|
|
### 4.2 Meter
|
|
|
|
`StreamingWorkMeter` is stack/frame scoped and single-thread owned. It records:
|
|
|
|
- start/deadline timestamp;
|
|
- admitted results and retained CPU bytes;
|
|
- entity operations;
|
|
- GPU bytes requested;
|
|
- GL retirement operations;
|
|
- stage completions, yields, overruns, failures, and oldest-work age.
|
|
|
|
Reservation is checked before an atomic operation using its conservative known
|
|
cost. Elapsed time is checked after every atomic operation. A first operation
|
|
may run when a dimension is otherwise empty so progress cannot deadlock; any
|
|
single-operation overrun is named in diagnostics and becomes a required split
|
|
site.
|
|
|
|
### 4.3 Cost estimation
|
|
|
|
`LandblockStreamResultCost` is computed once from immutable payloads:
|
|
|
|
- terrain vertex/index bytes;
|
|
- entity count;
|
|
- mesh-reference count;
|
|
- EnvCell visibility/shell count;
|
|
- physics cell/building/GfxObj counts;
|
|
- prepared payload arrays retained by the completion.
|
|
|
|
It never uses process working set as a scheduling unit and never counts the
|
|
memory-mapped package's address space as adopted bytes.
|
|
|
|
## 5. Queue and ordering contract
|
|
|
|
One scheduler owns queues by `(generation, priority, stage)`.
|
|
|
|
Priorities:
|
|
|
|
1. destination collision/required Near scene;
|
|
2. visible Near;
|
|
3. ordinary Near;
|
|
4. Far/speculative;
|
|
5. retirement cleanup after immediate quiesce.
|
|
|
|
Rules:
|
|
|
|
- FIFO is exact within one generation/priority.
|
|
- Generation supersession cancels stale unpublished work and releases its
|
|
retained-byte charge.
|
|
- Destination reserve applies only while the canonical reveal generation is
|
|
pending.
|
|
- Unreserved capacity is work-conserving: any queue may use it.
|
|
- At least one noncritical/retirement operation is admitted when capacity
|
|
remains, preventing starvation during a slow destination.
|
|
- Failed stages retain their exact receipt and queue position.
|
|
- Callback reentrancy appends work; it never mutates the active cursor.
|
|
- `_deferredApply`, `MaxDrainIterations`, the eager priority-radius bypass, and
|
|
count-only `MaxCompletionsPerFrame` execution disappear at cutover.
|
|
|
|
## 6. Immediate quiesce versus deferred release
|
|
|
|
At a hard destination-generation edge:
|
|
|
|
1. The old world generation is marked unavailable to normal world drawing,
|
|
picking, radar, status targeting, and collision queries.
|
|
2. Object/static animation, PhysicsScript, particle emission, and ambient/
|
|
owner-tagged audio for that generation are frozen or silenced.
|
|
3. The portal tunnel/UI/event path continues, matching retail's
|
|
`blocking_for_cells` branch.
|
|
4. Stable resident landblock IDs are captured once.
|
|
5. Per-landblock spatial detach and owner/resource release advance from cursors.
|
|
|
|
Quiesce is idempotent and generation-scoped. It does not destroy live server
|
|
identity, replay Hidden/UnHide, or clear the replacement generation.
|
|
|
|
For ordinary Near→Far demotion outside a hard portal edge, the exact landblock
|
|
detach remains the immediate visibility/collision boundary; only its expensive
|
|
owner cleanup is deferred.
|
|
|
|
## 7. Publication contract
|
|
|
|
One accepted completion progresses through:
|
|
|
|
```text
|
|
Admitted
|
|
-> PreparedReceipt
|
|
-> RenderPrefix
|
|
-> PhysicsBase
|
|
-> RenderCellsBuildings
|
|
-> StaticEntityPresentationAndCollision
|
|
-> SpatialCommit
|
|
-> RenderPinsAndEnvCellReplay
|
|
-> LiveProjectionRecovery
|
|
-> Complete
|
|
```
|
|
|
|
Each transition owns a retry receipt and known cost. Mutation order remains the
|
|
existing retail-rooted order. A stage cannot publish partial externally visible
|
|
state unless its receipt describes and resumes that exact partial prefix.
|
|
|
|
Destination reveal consumes only `Complete`/renderer-upload facts belonging to
|
|
the active destination generation. Far-ring work may continue after reveal.
|
|
|
|
## 8. Threading
|
|
|
|
- Worker threads build immutable `LandblockBuild`/prepared payloads and enqueue
|
|
immutable completions only.
|
|
- The update/render thread is the sole scheduler, meter, world-state,
|
|
publication, and retirement writer.
|
|
- GL upload/non-residency/deletion remains with renderer resource owners.
|
|
- No update operation waits for the worker, a GPU fence, file I/O, or a lock
|
|
held by render work.
|
|
- Back-pressure never blocks the update thread. Worker-side bounded queues may
|
|
wait/cancel; update-side admission is non-blocking.
|
|
|
|
## 9. Delivery checkpoints
|
|
|
|
### E0 — Oracle, inventory, and contract
|
|
|
|
- Record named-retail teardown/blocking pseudocode.
|
|
- Inventory every current publication/retirement owner and bypass.
|
|
- Define budget dimensions, atomic work, queue ordering, quiesce, retries,
|
|
threading, diagnostics, and acceptance.
|
|
|
|
**Complete 2026-07-24.**
|
|
|
|
### E1 — Typed budgets, meter, cost model, and diagnostics
|
|
|
|
- Add validated `StreamingWorkBudgetOptions` to `RuntimeOptions`.
|
|
- Add pure `StreamingWorkMeter` and deterministic injected-clock tests.
|
|
- Add exact immutable completion-cost estimation.
|
|
- Publish per-stage backlog, bytes, oldest age, work/yield/overrun facts through
|
|
lifecycle artifacts.
|
|
- Preserve current execution while measuring the would-yield decisions.
|
|
|
|
### E2 — Explicit admission queues
|
|
|
|
- Replace `_deferredApply` with stable generation/priority queues.
|
|
- Bound result admission by count and CPU bytes.
|
|
- Remove `MaxDrainIterations`.
|
|
- Preserve exact retry position and stale-generation rejection.
|
|
- Keep current publication atomic until E4, but execute only work admitted by
|
|
the typed meter.
|
|
|
|
### E3 — Quiesce and budgeted retirement
|
|
|
|
- Add destination-generation world quiesce.
|
|
- Tag/stop owner audio at the immediate edge.
|
|
- Make full-window and recenter retirement admission cursor/time/entity bounded.
|
|
- Make each retirement ticket advance by bounded atomic entity/stage work.
|
|
- Prove no old generation ticks, emits, collides, targets, or renders while its
|
|
retained memory converges.
|
|
|
|
### E4 — Cursor-budgeted publication
|
|
|
|
- Move stable ordered arrays/cost facts into retained receipts.
|
|
- Cursor static cleanup, static light/collision/plugin publication, physics
|
|
cell/building work, and environment-cell publication at safe boundaries.
|
|
- Feed GPU requested-byte and retirement-operation facts from renderer owners.
|
|
- Prove every failure resumes the exact unfinished operation without replay.
|
|
|
|
### E5 — Destination reservation and reveal generation
|
|
|
|
- Replace eager `PriorityRadius` execution with destination reservations.
|
|
- Join scheduler destination generation to the existing reveal coordinator.
|
|
- Keep the tunnel animating and surface the retail wait cue/diagnostic beyond
|
|
five seconds.
|
|
- Remove the last count-only execution/bypass paths.
|
|
|
|
### E6 — Gates and closeout
|
|
|
|
- Deterministic pressure, reentrancy, failure, cancellation, stale-generation,
|
|
GUID reuse, dungeon, outdoor, and session-reset suites.
|
|
- Release build and full solution tests.
|
|
- Physical-local capped/uncapped nine-stop and pinned dense-Arwic routes.
|
|
- Compare portal p99 and maximum single-frame allocation to Slice A and Slice C.
|
|
- Require zero `viewport-before-ready`, zero stranded old generation, staged
|
|
upload, collision, effect, audio, or GPU owner.
|
|
- Fixed-camera screenshots and subjective visual gate only if pixels change.
|
|
- Update architecture, inventory, issues, roadmap, divergence register, and
|
|
durable memory.
|
|
|
|
## 10. Acceptance
|
|
|
|
Slice E closes only when:
|
|
|
|
1. No priority or unload path bypasses all work budgets.
|
|
2. No frame exceeds any configured count/byte/entity dimension.
|
|
3. Every time overrun names one indivisible operation; none remain above the
|
|
configured time ceiling at closeout.
|
|
4. Old generations become unavailable, tick-frozen, and audio-silent at the
|
|
hard transition edge.
|
|
5. Retirement/publication retries resume exact cursors without replay.
|
|
6. Deferred retained CPU bytes stay within their queue budget.
|
|
7. Reveal is possible only for the active destination generation and the
|
|
existing readiness predicate.
|
|
8. Portal-window p99 and largest frame allocation materially improve over the
|
|
post-Slice-A physical baseline.
|
|
9. Complete connected routes end with zero pending publication, retirement,
|
|
upload, effect, collision, audio, and stale-generation owners.
|
|
10. Visual output/range/quality and retail gameplay behavior are unchanged.
|