fix(physics): restore retail cell availability semantics
This commit is contained in:
parent
d3c0d9ec0e
commit
7716c2ee89
14 changed files with 393 additions and 62 deletions
|
|
@ -62,7 +62,15 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|
||||
---
|
||||
|
||||
## 2. Adaptation (AD) — 46 rows (AD-25 retired 2026-07-30 at Campaign P Slice P3 — the remote dead-reckoning post-resolve now calls the exact ported `PhysicsObjUpdate.HandleAllCollisions` (the same function the local player and every ordinary body already use) instead of its own hand-inlined, narrower reflect gate; the row's own premise ("the remote DR sweep hasn't been rebuilt yet") no longer holds; AD-55 filed 2026-07-30 at Campaign P Slice P2, split out of the retired AP-7 row — the open cos(10°)-vs-0.99999536f Sledding slope-flatness constant; AD-53/AD-54 filed the same slice, split out of the retired TS-1 row — CliffSlide's three-source reference-normal fallback chain and the walkable-steepness reroute to CliffSlide; AD-52 filed 2026-07-29 at Campaign N slice N6 — the fragment-assembler 60 s partial TTL + completed-sequence ring; AD-51 filed 2026-07-29 at Campaign N slice N4 — the reclaimed-word pool for ACE's fresh-sequence cleartext RejectRetransmit; AD-50 filed 2026-07-29 at Campaign N slice N2 — the inbound-watermark ACE init; AD-49 stays reserved for Campaign N §5's blob-layer ordering deferral, filed when its slice lands; AD-47 and AD-48 filed 2026-07-29 at Campaign V slice V11 — the MSAA sample-position and present-pacing rows the campaign's risk register scheduled for the GL deletion; AD-11 retired 2026-07-23 — exact low-bit ItemUses predicate; AD-31 retired 2026-07-15 — the DAT-authored portal-space viewport replaces the black transit cover)
|
||||
## 2. Adaptation (AD) — 44 active rows
|
||||
|
||||
Recent retirements: AD-3/AD-4 retired 2026-07-31 by the exact loaded-cell
|
||||
availability and null-root containment port; AD-25 retired 2026-07-30 by the
|
||||
shared `PhysicsObjUpdate.HandleAllCollisions` remote path; AD-11 retired
|
||||
2026-07-23 by the exact low-bit ItemUses predicate; AD-31 retired 2026-07-15
|
||||
by the DAT-authored portal-space viewport. Recent additions and splits:
|
||||
AD-47/AD-48 (Vulkan sample/present behavior), AD-50..AD-52 (Campaign N), and
|
||||
AD-53..AD-55 (Campaign P response-layer findings).
|
||||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
|
|
@ -78,8 +86,6 @@ 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. **#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 required 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 unhydratable-placement path. `RuntimeWorldTransitState` owns the shared reveal generation, accepted readiness, transit correlation, and exact generation/cell-scoped host-acknowledgement suffix. `WorldRevealCoordinator` is a graphical adapter holding only App resource receipts; normalized Runtime checkpoints observe ownership without defining another readiness path. **Slice E3 refinement (2026-07-24):** the same generation now publishes an immediate `WorldGenerationQuiescence` edge: old-world drawing/spatial queries, simulation/effect clocks, reconciliation, targeting, and 3-D audio stop while retained physical teardown advances through metered cursors and destination network/UI/streaming/readiness remain live. **Slice E4 refinement (2026-07-24):** accepted render/physics/static publication may span update frames through retained exact cursors, but reveal still consumes only the completed spatial/render-ready generation; building and EnvCell snapshots remain invisible until complete and the final spatial identity swap stays observer-atomic. **Slice E5 refinement (2026-07-24):** the reveal generation owns one exact destination reservation across every typed budget dimension. Stale completion cannot consume or clear its replacement, and hydratable incomplete content is never force-revealed; portal transit retains the DAT tunnel and centered retail wait cue until readiness converges. The hold→materialize→regain-control lifecycle remains owned by `TeleportAnimSequencer`. | `src/AcDream.Runtime/World/RuntimeWorldTransitState.cs`; `src/AcDream.App/Streaming/WorldRevealCoordinator.cs`; `src/AcDream.App/Streaming/WorldGenerationQuiescence.cs`; `src/AcDream.App/Streaming/WorldRevealReadinessBarrier.cs`; `src/AcDream.App/Streaming/StreamingOriginRecenterCoordinator.cs`; `src/AcDream.App/Streaming/LandblockPresentationPipeline.cs`; `src/AcDream.App/Streaming/StreamingController.cs`; `src/AcDream.App/Rendering/PortalTunnelPresentation.cs`; `src/AcDream.App/UI/PortalWaitNoticeController.cs`; `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 or continue simulating an old/partial 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, missing scenery, or a still-active old generation; predicate never satisfies (streamer/DAT/upload failure) → login remains behind the world render gate, while portal transit remains in the authored tunnel and presents the centered wait cue after five seconds. | `SmartBox::UseTime` 0x00455410; `gmSmartBoxUI::UseTime` 0x004D6E30; `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) |
|
||||
| AD-6 | Per-LANDBLOCK shadow re-flood on hydration vs retail per-CELL `recalc_cross_cells` | `src/AcDream.Core/Physics/ShadowObjectRegistry.cs:339` | The streaming unit IS the landblock; one hook per hydration event covers both race directions (entity-before-cells, cells-after-spawn) | Any cell-hydration path that doesn't raise the landblock hook leaves an entity's shadow set stale — walk-through / missing collisions in just-streamed cells | `CObjCell::init_objects` → `recalc_cross_cells`, 0x0052b420 / 0x00515a30 |
|
||||
| AD-10 | Remote slope projection relocated to the queue-empty/head-reached combiner boundary; retail projects inside `CTransition::adjust_offset` during the sweep | `src/AcDream.Core/Physics/PositionManager.cs:47` | Remote bodies don't run a full local transition sweep; boundary projection removes the ~5 Hz Z staircase on slopes, no-op on flat ground | The single-point terrain-normal sample can differ from the sweep's contact plane (cell boundaries, props underfoot) — remote Z drift / stair-stepping | `CTransition::adjust_offset` pc:272296-272346 |
|
||||
|
|
|
|||
|
|
@ -178,6 +178,15 @@ package schema, bake, DAT reader, collision formula, or render portal graph
|
|||
changed. Evidence:
|
||||
`docs/research/2026-07-26-prepared-indoor-transit-regression.md`.
|
||||
|
||||
**Cell availability semantics (2026-07-31).** Raw and prepared CellStruct
|
||||
publication now retains a `CellPhysics` record even when authored physics or
|
||||
containment roots are empty. Payload absence is the unavailable state; a loaded
|
||||
null/-1 containment root keeps retail's universal-inside BSP base case.
|
||||
Registration-side outdoor floods still add outside cells but skip transit when
|
||||
the active CLandCell is unavailable, then recover through the existing reflood
|
||||
after terrain/cell hydration. No package schema or DAT reader changed.
|
||||
Evidence: `docs/research/2026-07-31-cell-availability-semantics.md`.
|
||||
|
||||
**Retail VFX hook compatibility seam (2026-07-14).** Chorizite.DatReaderWriter
|
||||
2.1.7 models `CreateBlockingParticleHook` as the common hook header only, while
|
||||
retail inherits the complete `CreateParticleHook` payload. The narrow readers in
|
||||
|
|
|
|||
62
docs/research/2026-07-31-cell-availability-semantics.md
Normal file
62
docs/research/2026-07-31-cell-availability-semantics.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# Retail cell availability and null-root containment — 2026-07-31
|
||||
|
||||
## Scope
|
||||
|
||||
This note closes divergence rows AD-3 and AD-4. It does not begin AD-6's
|
||||
atomic streaming-generation work.
|
||||
|
||||
The bug was one collapsed state. acdream treated all three of these as
|
||||
“containment unavailable”:
|
||||
|
||||
1. no visible cell payload is loaded;
|
||||
2. a loaded CellStruct has a null containment root;
|
||||
3. a loaded CellStruct has an authored containment root.
|
||||
|
||||
Retail distinguishes (1) from (2). A failed visible-cell lookup is
|
||||
unavailable. A loaded CellStruct remains a real cell even when its BSPTREE
|
||||
root is null, and the containment query's null-node base case is inside.
|
||||
|
||||
## Retail oracle
|
||||
|
||||
`CObjCell::find_cell_list @ 0x0052B4E0` in
|
||||
`docs/research/named-retail/acclient_2013_pseudo_c.txt:308742` establishes the
|
||||
availability gate:
|
||||
|
||||
- `CEnvCell::GetVisible` / `CLandCell::GetVisible` resolves the active seed at
|
||||
`0x0052B50C..0x0052B515`;
|
||||
- the outdoor branch still calls `CLandCell::add_all_outside_cells` at
|
||||
`0x0052B53F`, even when that seed lookup returned null;
|
||||
- the complete growing-array transit walk and containing-cell pick are gated
|
||||
by `seed != null && num_spheres != 0` at `0x0052B576`;
|
||||
- each later candidate is independently skipped when its stored cell pointer
|
||||
is null at `0x0052B58E`.
|
||||
|
||||
`CCellStruct::point_in_cell @ 0x005338F0` delegates directly to
|
||||
`BSPTREE::point_inside_cell_bsp @ 0x005398C0`. The already-ported graph and
|
||||
flat BSP queries preserve the retail null-root base case: a negative/null root
|
||||
returns true. Root presence is therefore not an availability predicate.
|
||||
|
||||
## Ported behavior
|
||||
|
||||
- `PhysicsDataCache` now publishes a `CellPhysics` record whenever an authored
|
||||
raw or prepared CellStruct payload exists, even if its physics BSP and/or
|
||||
containment BSP root is absent.
|
||||
- `CollisionTraversal.HasCellContainment` tests representation payload
|
||||
availability, not `Root` / `RootIndex`. `PointInsideCell` then lets the
|
||||
graph or flat query return true for the null-root base case.
|
||||
- `CellTransit.BuildShadowCellSet` still seeds all overlapped outdoor cells,
|
||||
but skips the transit walk when the active outdoor seed cannot be resolved
|
||||
from `CellGraph`. A separately cached building can no longer promote an
|
||||
object through an unavailable landcell.
|
||||
- The existing reflood lifecycle remains the recovery mechanism. Once terrain
|
||||
or an indoor CellStruct publishes, the next reflood walks the same authored
|
||||
portal/building relationships without reconstructing a different rule.
|
||||
|
||||
## Gates
|
||||
|
||||
Focused tests cover raw graph and prepared flat cache publication, absent
|
||||
versus loaded-null-root containment, indoor and outdoor seeds, preservation of
|
||||
outside-cell seeding, suppression of spurious building promotion, and
|
||||
hydration/reflood recovery. Final Release gates passed: Core 4,162 / 1 skipped,
|
||||
Runtime 440 / 0 skipped, App 4,002 / 3 skipped, plus the complete solution
|
||||
build with zero errors.
|
||||
Loading…
Add table
Add a link
Reference in a new issue