fix(streaming): block login on complete world reveal
Make initial login and portal arrival consume one WorldRevealReadinessBarrier that joins near-tier mesh publication, destination composite uploads, and collision residency before normal world geometry becomes visible. This ports retail SmartBox's blocking-cell completion edge into the asynchronous client instead of exposing a ground-only login. Add focused outdoor, indoor, texture, and invalid-claim tests; update the retail pseudocode, architecture, divergence record, issue ledger, roadmap baseline, and synchronized agent guidance. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
6c3bd4ce4b
commit
b60cb67009
14 changed files with 360 additions and 102 deletions
|
|
@ -98,7 +98,7 @@ production runtime failure.
|
|||
|
||||
**Acceptance:** A non-incremental Release solution build reports zero warnings
|
||||
and zero errors without suppressing analyzers or weakening nullable checking;
|
||||
the full 6,452-pass / 5-skip suite remains green.
|
||||
the full 6,457-pass / 5-skip suite remains green.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -7431,6 +7431,28 @@ outdoors at the angle that previously erased it.
|
|||
|
||||
# Recently closed
|
||||
|
||||
## #229 — [DONE 2026-07-20] Initial login revealed an incomplete world
|
||||
|
||||
**Closed:** 2026-07-20
|
||||
**Resolution:** Login auto-entry waited only for the terrain height or EnvCell
|
||||
floor needed by local physics. It then removed the sky-only render gate while
|
||||
the destination's static meshes, nearby scenery, and composite textures could
|
||||
still be uploading. Portal travel appeared to repair the world because it
|
||||
already waited on all of those domains before revealing its destination.
|
||||
|
||||
Login and portal arrival now share `WorldRevealReadinessBarrier`, the async
|
||||
equivalent of retail `SmartBox::UseTime` keeping position completion behind
|
||||
`CellManager::blocking_for_cells`. Both paths invalidate destination texture
|
||||
readiness, prepare composites after mesh publication, and require the same
|
||||
render-plus-collision predicate. Focused login/barrier tests and the complete
|
||||
Release suite pass; capped connected visual verification remains the final
|
||||
gate.
|
||||
|
||||
**Research:**
|
||||
`docs/research/2026-07-16-portal-completion-pseudocode.md` §2.
|
||||
|
||||
---
|
||||
|
||||
## #215 — [DONE 2026-07-13] Same-dungeon death respawn removed the dungeon floor physics
|
||||
|
||||
**Closed:** 2026-07-13
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ banner cannot silently override the current program state.
|
|||
- **Divergence audit:** 178 active rows — IA 17, AD 37, AP 85, TS 34,
|
||||
and UN 5 — plus the retained retired TS-37 history note.
|
||||
- **Latest automated baseline:** Release build succeeds with 17 known
|
||||
test-project warnings (`#228`); 6,452 tests passed and 5 intentionally
|
||||
test-project warnings (`#228`); 6,457 tests passed and 5 intentionally
|
||||
skipped. The unattended connected R6 route
|
||||
completed seven portal materializations, production input exercises, and a
|
||||
graceful close. See
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@ src/
|
|||
Streaming/
|
||||
StreamingController.cs -> done
|
||||
GpuWorldState.cs -> done
|
||||
WorldRevealReadinessBarrier.cs -> shared login/portal reveal owner
|
||||
Input/
|
||||
PlayerMovementController.cs -> active movement driver
|
||||
Plugins/
|
||||
|
|
@ -705,6 +706,19 @@ replace state produced by another job. CPU mesh extraction may still be schedule
|
|||
onto `ObjectMeshManager`'s thread-safe work queue, but the worker never publishes
|
||||
partially hydrated cell membership or shell placement to the renderer.
|
||||
|
||||
### World-reveal readiness ownership
|
||||
|
||||
`WorldRevealReadinessBarrier` is the single App-layer owner of the edge that
|
||||
allows normal world geometry to become visible at initial login or after a
|
||||
portal. It joins `StreamingController`/`GpuWorldState` static-mesh publication,
|
||||
`WbDrawDispatcher` composite-texture warmup, and `PhysicsEngine` destination
|
||||
residency. Login's sky-only gate and portal-space transit consume the same
|
||||
predicate; they differ only in presentation. Composite preparation advances
|
||||
after `WbMeshAdapter.Tick` on the render thread, never from the streaming
|
||||
worker. This is acdream's asynchronous equivalent of retail
|
||||
`SmartBox::UseTime` holding position completion while
|
||||
`CellManager::blocking_for_cells` is set.
|
||||
|
||||
### Teleport cell identity at the streaming boundary
|
||||
|
||||
The network and physics layers carry retail's complete `Position`: an
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|---|---|---|---|---|---|
|
||||
| AD-38 | Outgoing teleport viewports retire when retail's quantized animation level exceeds the last captured visible level 1022 (index 96), suppressing levels 1023/1024 up to 20.2 ms before retail's literal `elapsed >= 1.0` state edge. Incoming fades retain the exact timer. | `src/AcDream.Core/World/TeleportAnimSequencer.cs` (`OutgoingViewportReachedTerminalProjection`) | An uncapped 2000 FPS pass can publish the finite tunnel at levels 1023/1024 even though the paired 2013 retail capture switches viewports after 1022. The table-level cutover preserves the captured visible viewport ordering without throttling the application. | Exit sound, viewport replacement, and logout tunnel entry can occur at most two easing-table quanta (about 20.2 ms) earlier than retail's logical timer. | `UIGlobals::GetAnimLevel @ 0x004EE540`; `gmSmartBoxUI::UseTime @ 0x004D6E30`; paired retail/acdream captures documented in `docs/research/2026-07-15-retail-portal-space-pseudocode.md` |
|
||||
| AD-1 | Lost-cell machinery replaced by recoverable outdoor demote (**#107** safety net) + outdoor-restore `max(terrainZ, z)` under-terrain lift; retail goes `GotoLostCell` | `src/AcDream.Core/Physics/PhysicsEngine.cs:553` (+ :808) | acdream has no lost-cell state machine; outdoor landcell is the recoverable equivalent; the #107 auto-entry hold should make the demote branch unreachable | Gap in the hold → player committed to outdoor terrain inside/under a building (fake-grounded spawn, fall-through); a legit below-heightmap server restore is silently lifted — upward warp vs server | `GotoLostCell` pc:283418; `SetPositionInternal` 0x00515bd0, pc:283892-283945 |
|
||||
| AD-2 | Async readiness gates replace retail's synchronous destination cell load. Login placement keeps #135's split: a hydratable indoor claim gates on its EnvCell floor (`IsSpawnCellReady`) rather than meaningless dungeon terrain; outdoor placement gates on terrain. **#218 refinement (2026-07-16):** F751 portal-space exit joins BOTH publication domains in `TeleportWorldReady`: indoor requires the center landblock's Near-tier static/EnvCell mesh set uploaded plus the EnvCell in physics, while outdoor requires that Near-tier render readiness plus terrain/collision residency for the priority near ring. Hard-recenter generations and tier-aware completion application prevent stale overlapping loads/unloads or Far/Near jobs from opening or erasing the gate; mesh upload remains separate from balanced landblock ownership. Claims beyond NumCells still take the loud forced-placement path. The hold→materialize→regain-control lifecycle remains owned by `TeleportAnimSequencer`. | `src/AcDream.App/Rendering/GameWindow.cs` (`TeleportWorldReady` + TAS transit tick); `src/AcDream.App/Streaming/StreamingController.cs` (`IsRenderNeighborhoodResident`); `src/AcDream.App/Streaming/GpuWorldState.cs` (`IsRenderReady`); `src/AcDream.App/Rendering/Wb/LandblockSpawnAdapter.cs`; `src/AcDream.Core/Physics/PhysicsEngine.cs` (`IsSpawnCellReady`, `IsNeighborhoodTerrainResident`) | This is the asynchronous equivalent of retail completing its blocking cell load before `EndTeleportAnimation`: neither an empty collision world, a terrain-only Far shell, nor a published-but-not-drawable GPU landblock may be revealed. Indoor does not require a terrain heightmap, only the owning render landblock and the exact EnvCell. | Gate opens early → grey/untextured reveal, free-fall, wrong-cell rooting, or missing scenery; predicate never satisfies (streamer/DAT/upload failure) → portal transit reaches its existing loud timeout/forced-placement diagnostic instead of silently hanging forever. | retail synchronous cell load before SetPosition / `gmSmartBoxUI::EndTeleportAnimation` 0x004D65A0 |
|
||||
| AD-2 | Async readiness gates replace retail's synchronous destination cell load. **#229 refinement (2026-07-20):** login and F751 portal-space exit now share `WorldRevealReadinessBarrier`, so neither path can expose the normal viewport until the same render-publication, composite-texture, and collision domains converge. A hydratable indoor claim requires its owning Near-tier static/EnvCell mesh set, destination composites, and exact EnvCell physics (`IsSpawnCellReady`); an outdoor claim requires those render domains plus terrain/collision residency for the priority near ring. Hard-recenter generations and tier-aware completion application prevent stale overlapping loads/unloads or Far/Near jobs from opening or erasing the gate; mesh upload remains separate from balanced landblock ownership. Claims beyond NumCells still take the loud forced-placement path. The portal hold→materialize→regain-control lifecycle remains owned by `TeleportAnimSequencer`. | `src/AcDream.App/Streaming/WorldRevealReadinessBarrier.cs`; `src/AcDream.App/Rendering/GameWindow.cs` (login auto-entry + TAS transit tick); `src/AcDream.App/Streaming/StreamingController.cs` (`IsRenderNeighborhoodResident`); `src/AcDream.App/Streaming/GpuWorldState.cs` (`IsRenderReady`); `src/AcDream.App/Rendering/Wb/LandblockSpawnAdapter.cs`; `src/AcDream.Core/Physics/PhysicsEngine.cs` (`IsSpawnCellReady`, `IsNeighborhoodTerrainResident`) | This is the asynchronous equivalent of retail leaving `SmartBox::position_update_complete` false while `CellManager::blocking_for_cells` is set: neither initial login nor portal arrival may reveal an empty collision world, a terrain-only Far shell, or a published-but-not-drawable GPU landblock. Indoor does not require a terrain heightmap, only the owning render landblock and exact EnvCell. | Gate opens early → grey/untextured first login or portal reveal, free-fall, wrong-cell rooting, or missing scenery; predicate never satisfies (streamer/DAT/upload failure) → login remains behind the world render gate, while portal transit reaches its existing loud timeout/forced-placement diagnostic. | `SmartBox::UseTime` 0x00455410; `gmSmartBoxUI::EndTeleportAnimation` 0x004D65A0 |
|
||||
| AD-3 | Outdoor seeds always walk the transit array (retail skips the walk when the seed CLandCell is null/unloaded); per-cell lookups no-op on unhydrated data | `src/AcDream.Core/Physics/CellTransit.cs:503` | Equivalence argument: with nothing hydrated every lookup inside the walk no-ops, so the result matches retail's skipped walk | Near partially-streamed landblocks, building-transit promotion silently can't fire until structs hydrate — membership stays outdoor while the player is inside a building | `CObjCell::find_cell_list` 0052b535-0052b56c (null-CLandCell case) |
|
||||
| AD-4 | `point_in_cell` against an unhydrated CellBSP returns false (skip) rather than the null-node "inside" default; retail never queries unloaded cells | `src/AcDream.Core/Physics/CellTransit.cs:588` | The null-node default would make an unhydrated cell spuriously claim every point; skipping is the conservative streaming-safe choice | During hydration, a point genuinely inside a not-yet-loaded cell resolves outdoor/stale — transient membership misclassification driving wrong collision set and render root | `CEnvCell::find_visible_child_cell` :311397; cell-BSP vtable[0x84] |
|
||||
| AD-5 | Outdoor `point_in_cell` is an identity compare against the global XY-column cell from `LandDefs.AdjustToOutside` (no per-cell containment test) | `src/AcDream.Core/Physics/CellTransit.cs:865` | Landcells are disjoint 24 m columns — identity-compare against the column under the sphere centre is exactly equivalent to retail's per-candidate test | If block-origin/lcoord math is wrong at a landblock seam, the compare silently never matches — outdoor membership freezes at boundaries (the pre-#106 symptom) | `find_cell_list` pick pc:308788-308825; `CLandCell::point_in_cell` (get_block_offset pc:308804) |
|
||||
|
|
|
|||
|
|
@ -920,7 +920,7 @@ diagnostic scaffolding, not yet the final collision system.
|
|||
PositionManager facade remains the final R6 ownership cleanup.
|
||||
Final 2026-07-20 gate: three independent retail/architecture/adversarial
|
||||
re-reviews are clean; Release builds successfully with 17 known test-project
|
||||
warnings tracked by #228; the repository suite is 6,452 passed / 5 skipped.
|
||||
warnings tracked by #228; the repository suite is 6,457 passed / 5 skipped.
|
||||
The unattended connected Release rebaseline also
|
||||
passes: seven portal materializations across Caul, Sawato, Rynthid,
|
||||
Aerlinthe, Holtburg, and same-location revisits; production-dispatcher turn,
|
||||
|
|
@ -1446,7 +1446,7 @@ OpenGL ceiling; revisit macOS only if a supported graphics backend is chosen.
|
|||
|
||||
## Cross-cutting work tracked in parallel
|
||||
|
||||
- **Test coverage.** Each phase lands with layer-matched unit and integration tests in `tests/`. Current Release baseline: 6,452 passed / 5 intentional skips (2026-07-20).
|
||||
- **Test coverage.** Each phase lands with layer-matched unit and integration tests in `tests/`. Current Release baseline: 6,457 passed / 5 intentional skips (2026-07-20).
|
||||
- **Memory files.** Live subsystem state and digests are indexed by `claude-memory/MEMORY.md`; stable engineering references remain under `memory/`. Canonical program status stays in milestones/roadmap/issues.
|
||||
- **`CLAUDE.md` discipline.** Check all four references (ACE, ACViewer, WorldBuilder, Chorizite) before committing to an approach. WorldBuilder is the closest stack match and should be checked first.
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Carried:
|
|||
registered TS-50/TS-51 timing residuals (the complete-root-Frame/object-workset
|
||||
cutover is automated-test complete 2026-07-20; three independent re-reviews are
|
||||
clean, Release builds with 17 known test-project warnings tracked by #228, and
|
||||
6,452 tests pass / 5 skip), and
|
||||
6,457 tests pass / 5 skip), and
|
||||
the deferred Modern Pipeline track (MP1b+). The separate #225 shared-alpha
|
||||
implementation has passed connected lifetime/performance gates and awaits only
|
||||
its lifestone/particle visual comparison.
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ sequence without advancing time and republish all part transforms. Forcing a
|
|||
Ready motion, recognizing recall by id, or freezing the previously published
|
||||
part pose would all diverge from this general retail mechanism.
|
||||
|
||||
## 2. Portal exit waits for render publication as well as collision data
|
||||
## 2. Login and portal exit wait for render publication as well as collision data
|
||||
|
||||
Named retail references:
|
||||
|
||||
|
|
@ -82,6 +82,15 @@ that teleport is no longer in progress,
|
|||
`EndTeleportAnimation` begins `TAS_TUNNEL_CONTINUE`; the normal world is not
|
||||
revealed before the blocking load edge has completed.
|
||||
|
||||
The same `SmartBox::UseTime` edge governs initial position completion. It does
|
||||
not distinguish “enough collision to stand” from “enough render state to show
|
||||
the world”: while `CellManager::blocking_for_cells` is set it only checks
|
||||
prefetch status, and only after the block clears does it change the player's
|
||||
position, set `position_update_complete`, advance object/physics/landscape
|
||||
time, and draw the normal scene. Therefore acdream's login auto-entry cannot
|
||||
gate on one sampled terrain height while portal arrival uses a complete render
|
||||
barrier. Both presentations must consume one shared asynchronous equivalent.
|
||||
|
||||
acdream's load is split into two independent readiness domains:
|
||||
|
||||
```text
|
||||
|
|
@ -98,18 +107,28 @@ worker/register path
|
|||
Required asynchronous equivalent:
|
||||
|
||||
```text
|
||||
TeleportWorldReady(destination):
|
||||
WorldRevealReady(destination):
|
||||
if destination claim cannot be hydrated:
|
||||
return true so the existing loud forced-placement path diagnoses it
|
||||
|
||||
if destination is indoor:
|
||||
return render center landblock Near-tier and ready
|
||||
AND destination composite textures ready
|
||||
AND destination EnvCell collision data ready
|
||||
|
||||
return every landblock in the priority near ring Near-tier and render-ready
|
||||
AND destination composite textures ready
|
||||
AND every landblock in that ring collision-resident
|
||||
```
|
||||
|
||||
`WorldRevealReadinessBarrier.Begin` invalidates destination-scoped composite
|
||||
readiness for both the first accepted player position and each accepted portal
|
||||
destination. After the render-thread mesh tick, `Prepare` advances composite
|
||||
uploads only when the required static meshes are published. Login remains
|
||||
behind its sky-only render gate and portal travel remains in its tunnel until
|
||||
the same `IsReady` predicate opens. This prevents the two paths from drifting
|
||||
apart again while preserving their different presentations.
|
||||
|
||||
Streaming completion is generation- and tier-aware. Every hard recenter,
|
||||
dungeon collapse, and dungeon expansion advances a token carried by Load,
|
||||
Promote, Unload, and their results. This rejects an old build even when its id
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue