Record the passing capped and uncapped connected evidence, coordinator ownership, graceful reconnect contract, current test baseline, and the remaining human-only visual gates. Keep AGENTS.md synchronized with CLAUDE.md and distill the durable reveal-lifecycle lesson into project memory.
177 lines
8.7 KiB
Markdown
177 lines
8.7 KiB
Markdown
# Automated world-lifecycle gate
|
|
|
|
**Status:** Complete (2026-07-20)
|
|
**Owner:** M3 stabilization / R6 rendering-lifecycle rebaseline
|
|
**Depends on:** `WorldRevealReadinessBarrier`, retained-UI automation,
|
|
`FrameProfiler`, connected local ACE
|
|
|
|
## Outcome
|
|
|
|
Create a deterministic connected Release gate that catches the structural
|
|
failures which previously required someone to stare at the client for several
|
|
minutes:
|
|
|
|
- the first login frame cannot expose world geometry before the destination's
|
|
render meshes, composite textures, and collision data are all ready;
|
|
- every portal/recall lifetime crosses the same readiness edge, materializes
|
|
once, and returns to the normal viewport once;
|
|
- dungeon entry and exit use the same lifecycle without grey frames,
|
|
stale-source geometry, or an uncollidable destination;
|
|
- graceful logout followed by a fresh reconnect starts a new reveal lifetime
|
|
and cannot inherit readiness or resources from the previous process;
|
|
- repeated destinations do not cause cumulative memory, owner-count,
|
|
allocation, update-time, or GPU-time growth;
|
|
- each stable checkpoint produces machine-readable state plus a screenshot for
|
|
later human comparison.
|
|
|
|
This is a correctness and resource-lifetime gate. Screenshots preserve visual
|
|
evidence, but automation does **not** claim retail visual equivalence. Color,
|
|
composition, animation feel, and subtle geometry artifacts remain user gates.
|
|
|
|
## Canonical runtime contract
|
|
|
|
`WorldRevealReadinessBarrier` remains the only definition of destination
|
|
readiness. It will expose a structured snapshot rather than forcing telemetry
|
|
to repeat the readiness formula. A snapshot records the destination cell,
|
|
indoor/outdoor classification, required radius, render publication, composite
|
|
texture readiness, collision readiness, an impossible/unhydratable claim, and
|
|
the final `Ready` result.
|
|
|
|
A focused App-layer `WorldRevealLifecycleTelemetry` owns diagnostic reveal
|
|
generations. It has no GL calls and no knowledge of `GameWindow`. For each
|
|
login or portal generation it records:
|
|
|
|
1. begin;
|
|
2. destination acquisition;
|
|
3. each distinct readiness transition;
|
|
4. the first frame where the normal world viewport is actually eligible to
|
|
draw;
|
|
5. completion or cancellation.
|
|
|
|
Making the normal viewport visible before `Ready` is a hard invariant failure,
|
|
not a warning. A newer generation retires the older one, so stale asynchronous
|
|
completion cannot satisfy the new destination.
|
|
|
|
`GameWindow` only supplies facts and invokes the controller at existing
|
|
lifecycle seams. It does not gain a new feature body.
|
|
|
|
## Production automation seam
|
|
|
|
The retained-UI automation runner gains runtime commands through an injected
|
|
interface:
|
|
|
|
- `wait world-ready [timeout-ms]` waits on canonical lifecycle state instead
|
|
of a guessed sleep;
|
|
- `wait materialized <occurrence> [timeout-ms]` waits for the authoritative
|
|
portal completion count;
|
|
- `checkpoint <name>` emits one JSON record containing reveal state,
|
|
landblock/entity/animation and resource-owner counts, managed memory, and
|
|
the latest frame-profiler summary;
|
|
- `screenshot <name> [timeout-ms]` queues a default-framebuffer capture after
|
|
the complete world and retained UI have drawn, then waits for the render
|
|
thread to finish the PNG;
|
|
- existing `input` commands perform deterministic turns and movement through
|
|
`InputDispatcher`.
|
|
|
|
All state mutation and GL readback stay on the update/render thread. The
|
|
external gate only launches the process, observes files/logs and OS process
|
|
metrics, and requests normal `WM_CLOSE` shutdown.
|
|
|
|
## Connected route
|
|
|
|
The gate runs two capped/uncapped-aware sessions against local ACE:
|
|
|
|
1. fresh login → wait for canonical readiness → checkpoint + screenshot;
|
|
2. outdoor dense scene → turn → stable checkpoint + screenshot;
|
|
3. world-edge scene → turn → stable checkpoint;
|
|
4. known dungeon destination → checkpoint + screenshot → local movement;
|
|
5. return outdoors → checkpoint + screenshot;
|
|
6. revisit the first dense scene → stable resource comparison;
|
|
7. graceful close;
|
|
8. reconnect the same account → repeat the fresh-login readiness and
|
|
screenshot gate → graceful close.
|
|
|
|
The existing seven-destination R6 soak remains the longer performance route.
|
|
The lifecycle gate is shorter and more diagnostic; it composes with that soak
|
|
rather than replacing it.
|
|
|
|
## Hard failures
|
|
|
|
- world viewport observed before the active reveal snapshot is ready;
|
|
- destination never reaches readiness or materialization within the bounded
|
|
connected test timeout;
|
|
- missing or duplicate reveal/materialization/completion edges;
|
|
- missing checkpoint JSON or screenshot, zero-sized/corrupt screenshot;
|
|
- absent required destination landblock/EnvCell/collision readiness;
|
|
- retained per-owner resources after delete/session teardown in deterministic
|
|
tests;
|
|
- same-location resource counts or memory/per-frame allocation grow beyond the
|
|
route's documented relative tolerance;
|
|
- unhandled exception, access violation, OOM, GL/device loss, disconnect,
|
|
unexpected WeenieError, nonzero exit, or failure of graceful shutdown.
|
|
|
|
Absolute FPS is recorded, never compared across local and RDP sessions. The
|
|
gate compares like-for-like samples within one process and reports CPU and GPU
|
|
frame time separately.
|
|
|
|
## Implementation sequence
|
|
|
|
1. Add a structured readiness snapshot and exhaustive barrier tests.
|
|
2. Add lifecycle telemetry as a pure App owner with stale-generation,
|
|
cancellation, duplicate, and early-visible tests.
|
|
3. Add the automation runtime interface, deterministic waits, checkpoints, and
|
|
screenshot request/completion protocol.
|
|
4. Add the render-thread screenshot owner and structured resource snapshots.
|
|
5. Wire the owners at login, F751 portal start, TAS placement/completion, world
|
|
draw eligibility, and session teardown.
|
|
6. Add the connected route and PowerShell report/gate orchestration.
|
|
7. Run capped and uncapped Release gates, fix root causes, and retain artifacts.
|
|
8. Extract the now-protected streaming/portal/reveal frame orchestration from
|
|
`GameWindow` without changing the accepted lifecycle trace.
|
|
9. Run focused tests, full Release build/test, connected gates, graceful-close
|
|
verification, and documentation reconciliation.
|
|
|
|
## Completion evidence
|
|
|
|
- Focused App tests cover readiness composition, stale generations,
|
|
cancellation, duplicate lifecycle edges, early-visible rejection, artifact
|
|
capture, automation timeouts, and resource snapshots.
|
|
- `dotnet build AcDream.slnx -c Release` succeeds. The complete repository
|
|
suite is 6,480 passed / 5 skipped; the 17 existing test-project warnings
|
|
remain tracked by issue #228.
|
|
- The final two-process connected report passed with no failures. The capped
|
|
process completed fresh login, Aerlinthe, Rynthid, Facility Hub, Holtburg,
|
|
and an Aerlinthe revisit in 244.424 seconds. The uncapped process then
|
|
reconnected the same account and completed in 61.717 seconds.
|
|
- Both processes closed through `WM_CLOSE`, received the authoritative
|
|
character-logoff confirmation, exited with code 0, and were matched to
|
|
ACE's exact client UDP endpoint and `PacketHeader Disconnect` record.
|
|
- Every checkpoint had zero pending live teardowns, landblock retirements,
|
|
staged mesh uploads, composite warmups, and reveal invariant failures.
|
|
- All six PNG artifacts were valid and inspected. Login, indoor dungeon,
|
|
dungeon exit, Aerlinthe revisit, and reconnect showed complete world
|
|
geometry rather than a grey or partially revealed destination.
|
|
- The uncapped reconnect sampled 172.7 FPS / 5.79 ms at the checkpoint; its
|
|
latest profiler window measured 5.7 ms CPU p50, 6.1 ms p95 and 5.7 ms GPU
|
|
p50, 6.0 ms p95 with zero pacing time. This is machine-specific throughput
|
|
evidence, not a cross-display FPS requirement.
|
|
- The Aerlinthe revisit retained balanced owners and stayed inside the
|
|
documented relative memory/resource tolerances. One circuit is not claimed
|
|
as a permanent memory plateau proof; the longer seven-destination R6 soak
|
|
remains the endurance gate.
|
|
- `WorldRevealCoordinator` now owns the protected lifecycle outside
|
|
`GameWindow`; the accepted trace did not change after extraction.
|
|
|
|
## Landed commits
|
|
|
|
- `db45b81f` — expose canonical readiness snapshots and lifecycle telemetry;
|
|
- `354c2adc` — add deterministic checkpoints and framebuffer screenshots;
|
|
- `b03371c0` — add the connected lifecycle/reconnect gate;
|
|
- `68578fa5` — port retail graceful character logout and transport disconnect;
|
|
- `a4ef5788` — centralize the reveal lifetime in `WorldRevealCoordinator`;
|
|
- `f7b09617` — preserve singleton endpoint/checkpoint collections in the gate.
|
|
|
|
The remaining visual work is deliberately outside this gate: the R6
|
|
locomotion/collision/projectile/teleport retail comparison, the two-client
|
|
portal-out/materialization observer comparison, and issue #225's translucent
|
|
lifestone/particle ordering comparison.
|