fix(render): eliminate EnvCell identity collisions
This commit is contained in:
parent
999201cca7
commit
b7b9aaa9dd
7 changed files with 129 additions and 23 deletions
|
|
@ -86,7 +86,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| AD-21 | Null-clipRoot legacy outdoor safety path (no portal visibility, no punches/seals, no-clip terrain) for pre-spawn / login / legacy cameras; in-world retail always has a viewer_cell root | `src/AcDream.App/Rendering/WorldRenderFrameBuilder.cs` (`RuntimeWorldFrameRootSource`, `WorldRenderFrame.ClipRoot`); `src/AcDream.App/Rendering/WorldSceneRenderer.cs` (null-root safety draw) | Result is null ONLY when neither an interior root nor the synthetic outdoor node exists; kept so the login screen shows the live sky | If viewer-root resolution ever returns null in-world (membership bug, fly-camera edge), the frame silently degrades — interiors stop drawing through doorways; the old two-branch FLAP reappears for those frames | `SmartBox::RenderNormalMode` decomp:92635 |
|
||||
| AD-22 | Async streamed mesh loading with bounded CPU replay residency, per-frame upload budgets, and point-of-use self-heal (`EnsureLoaded` re-request in the dispatcher's mesh-missing path, **#128**); retail loads synchronously — geometry is never absent | `src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs`; `src/AcDream.App/Rendering/Wb/MeshUploadCaches.cs`; `src/AcDream.App/Rendering/Wb/MeshUploadFrameBudget.cs` | Immutable preparation descriptors and the bounded CPU cache can re-stage an evicted mesh; dispatcher self-heal makes absence transient while upload budgets prevent a portal arrival from monopolizing a frame | A future consumer that neither retains an owner nor reaches the self-heal/replay path can remain invisible; under heavy admission pressure a valid mesh can pop in later than retail's synchronous path | retail synchronous content load; `docs/architecture/worldbuilder-inventory.md` portal-readiness and bounded-residency seams |
|
||||
| AD-23 | Live entities with `ServerGuid != 0` and null `ParentCellId` are culled (ClipSlotCull) while indoor clip routing is active; retail objects are always cell-resident (synchronous add-to-cell at creation) | `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs:484` | Phase U.4 policy: parentless = unresolved indoors, equivalent to retail's not-in-any-visible-cell ⇒ not drawn, *given membership resolves promptly* | An entity whose membership lags (late CreateObject hydration, resolver hiccup) blinks invisible while the player is indoors, even in plain sight | retail per-cell object lists in PView traversal |
|
||||
| AD-24 | EnvCell shell geometry hash-deduplicated ((environmentId, structure, surface overrides) → 31-multiplier hash) and instanced; retail draws each CEnvCell's own structure directly | `src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs:276` | Verbatim WB EnvCellRenderManager port (Phase A8); dedup is what makes the single-VAO MDI cell pipeline cheap; intended visuals identical | A hash collision between distinct tuples renders the wrong interior shell in some room with NO diagnostic firing — wrong walls/floor in a dungeon room | retail `PView::DrawCells` → per-cell drawing_bsp (cited at :319) |
|
||||
| AD-24 | EnvCell shell geometry content-deduplicated and instanced; retail draws each CEnvCell's own structure directly | `src/AcDream.Core/Rendering/Wb/EnvCellGeometryIdentity.cs` | Phase A8 retained WB's 31× hash; the 2026-07-24 full-DAT gate proved a real collision (`0x00030175`/`0x01BC0105`), so App+Bake now share a namespaced FNV-1a tuple identity and the bake rejects any full-tuple collision | A future collision outside the installed full-DAT gate could still merge different shells at runtime; the stronger 59-bit payload makes this extremely unlikely, and every bake fails loudly rather than publishing it | retail `PView::DrawCells` → per-cell drawing_bsp (cited at the former renderer `:319`) |
|
||||
| AD-25 | **REMOTE-DR sweep only** (the player half retired 2026-07-07 by the #182 verbatim rebuild): the remote dead-reckoning post-resolve still reflects velocity with the airborne-before-AND-after suppression; retail bounces unless grounded→grounded-and-not-sledding. The PLAYER path now runs the ported `handle_all_collisions` (`PhysicsObjUpdate`) with retail's `should_reflect` rule — the micro-bounce spiral it guarded is gone (contact is committed BEFORE the reflect and the small-velocity-zero is ungated) | `src/AcDream.App/Physics/RemotePhysicsUpdater.cs` (remote sweep post-resolve, #173 block) | The remote DR sweep hasn't been rebuilt yet (it has no fsf/SetPositionInternal chain); the old airborne-only suppression keeps remote landings from micro-bouncing on the remote landing-snap gate | Remote landing-reflection behavior (slope-landing momentum) won't reproduce; retire when the remote-DR sweep gets the same UpdateObjectInternal rebuild as the player | `handle_all_collisions` pc:282699-282715; ACE PhysicsObj.cs:2656-2721 |
|
||||
| AD-27 | Use/PickUp action fired on natural moveto completion via the `MoveToComplete` client-addition seam (retail's `CleanUpAndCallWeenie` contains no weenie call in this build and notifies nothing on arrival); retail sends the action once (server MoveToChain callback completes it). The companion `MoveToCancelled` seam only withdraws the waiting client presentation/action. | `src/AcDream.App/Interaction/SelectionInteractionController.cs` (`OnNaturalMoveToComplete`/`OnMoveToCancelled`); `src/AcDream.App/Rendering/GameWindow.cs` (player MoveTo seam binding); `src/AcDream.Core/Physics/Motion/MoveToManager.cs` (`MoveToComplete`/`MoveToCancelled`) | ACE's server-side chain may have timed out by the time our body arrives; the close-range deferred send hits ACE's WithinUseRadius fast-path. R4-V5 re-anchored from the deleted B.6 `AutoWalkArrived` event — same fires-on-arrival-only contract; cancellation/error only clears retained intent. | If the server's chain has NOT timed out, the action executes twice — door toggles open-then-closed, use-once interactions double-fire; protocol noise on non-ACE servers | ACE CreateMoveToChain / WithinUseRadius; `MoveToManager::CleanUpAndCallWeenie` 00529650 §7e (no weenie call) |
|
||||
| AD-28 | Chat transcript (`UiText`) and input (`UiChatInput`) are two separate widget classes placed inside their dat-authored container panels; retail's `ChatInterface` uses a single mode-flagged `UIElement_Text` (Type-12) that switches between read and edit mode | `src/AcDream.App/UI/Layout/ChatWindowController.cs:135` (transcript) + `:150` (input) | `UIElement_Text` is inside keystone.dll with no PDB/decomp; a two-widget split is functionally equivalent (read-only scroll, editable input) and is the structural adaptation required by our UiElement architecture | A future consumer expecting a single widget for both read/write (e.g. a plugin calling the chat API and getting one widget back) must be written to the two-widget contract | `UIElement_Text` (Type-12) @ keystone.dll; `gmMainChatUI::PostInit` @0x4ce130 |
|
||||
|
|
|
|||
|
|
@ -101,6 +101,19 @@ behavior change, no divergence-register row.
|
|||
running with no GL context, so baked pak output and live-client output stay
|
||||
byte-identical.
|
||||
|
||||
**MP1b EnvCell content identity correction (2026-07-24).** The extracted
|
||||
WorldBuilder `EnvCellRenderManager.GetEnvCellGeomId` 31× polynomial is not a
|
||||
safe unique resource key. A guarded full retail-DAT catalog found the concrete
|
||||
collision `0x00030175` versus `0x01BC0105`: different environment/surface
|
||||
tuples both map to `0x00000002020E8C13` and contain different polygons.
|
||||
`AcDream.Core.Rendering.Wb.EnvCellGeometryIdentity` now owns one namespaced
|
||||
FNV-1a identity shared by the App streaming build and `acdream-bake`; the
|
||||
legacy calculation remains executable only for the conformance test that proves
|
||||
the collision. The bake additionally compares the complete source tuple before
|
||||
aliasing and fails on any collision. This is an acdream resource-ownership seam,
|
||||
not a second DAT interpreter; `DatCollection` and `MeshExtractor` remain the
|
||||
only reader/extractor path.
|
||||
|
||||
**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
|
||||
|
|
|
|||
|
|
@ -21,11 +21,14 @@ bounded artifact whose size and time scale with unique geometry, not with all
|
|||
1. `DatCollection` remains the only DAT reader.
|
||||
2. `MeshExtractor` remains the only mesh/texture interpreter used by both the
|
||||
live path and the bake.
|
||||
3. Geometry identity is the already-shipped WorldBuilder calculation:
|
||||
`environmentId`, then `cellStructure`, then the ordered surface IDs through
|
||||
the `hash = hash * 31 + value` chain, with bit 33 set.
|
||||
4. The pure identity helper moves to `AcDream.Core.Rendering.Wb`; App and Bake
|
||||
call the same implementation. No second or adapted hash is allowed.
|
||||
3. Geometry identity is one deterministic hash of the complete typed tuple:
|
||||
`environmentId`, `cellStructure`, surface count, then the ordered surface
|
||||
IDs. App and Bake call the same Core implementation.
|
||||
4. The pure identity helper lives in
|
||||
`AcDream.Core.Rendering.Wb.EnvCellGeometryIdentity`. It uses FNV-1a in a
|
||||
dedicated high-bit namespace and retains bit 33 for compatibility with
|
||||
existing synthetic-geometry diagnostics. WorldBuilder's original
|
||||
`hash = hash * 31 + value` calculation remains as a conformance helper only.
|
||||
5. Every EnvCell TOC key is still
|
||||
`PakKey.Compose(PakAssetType.EnvCellMesh, fileId)`.
|
||||
6. Aliases share `offset`, `length`, and `crc32`. The pak format and reader do
|
||||
|
|
@ -42,16 +45,30 @@ bounded artifact whose size and time scale with unique geometry, not with all
|
|||
11. The pak itself and raw bake logs are machine artifacts and are not
|
||||
committed. A compact, path-free bake report is committed.
|
||||
|
||||
**Full-DAT correction (2026-07-24):** the first guarded full-catalog run proved
|
||||
that the original fixed contract was impossible. Installed retail DAT cells
|
||||
`0x00030175` (`environment=0x277`, structure `0`, surfaces `0x013B,0x0034`)
|
||||
and `0x01BC0105` (`environment=0x276`, structure `0`, surfaces
|
||||
`0x04FC,0x0034`) both produce WorldBuilder ID `0x00000002020E8C13` despite
|
||||
having visibly different geometry. The difference is algebraic:
|
||||
one environment step contributes `31³`, exactly canceled by the first-surface
|
||||
difference `0x04FC - 0x013B = 31²`. The collision guard stopped the bake before
|
||||
writing an artifact. B1 was therefore corrected at the root: the old polynomial
|
||||
is retained and tested as historical evidence, while runtime and bake now share
|
||||
the stronger namespaced identity. The full-catalog collision gate remains
|
||||
mandatory.
|
||||
|
||||
## Implementation checkpoints
|
||||
|
||||
### B1 — One geometry identity seam
|
||||
|
||||
- Extract the pure WorldBuilder identity calculation from the App transaction
|
||||
builder into `AcDream.Core.Rendering.Wb.EnvCellGeometryIdentity`.
|
||||
- Extract one pure identity calculation from the App transaction builder into
|
||||
`AcDream.Core.Rendering.Wb.EnvCellGeometryIdentity`.
|
||||
- Retain the existing App entry points as delegates so production behavior and
|
||||
callers do not change.
|
||||
- Add Core conformance tests for empty surfaces, ordered surfaces, the bit-33
|
||||
namespace, and App/Bake shared values.
|
||||
- Add Core conformance tests for empty surfaces, ordered surfaces, the
|
||||
dedicated namespace, App/Bake shared values, and the installed-DAT collision
|
||||
in the legacy WorldBuilder polynomial.
|
||||
|
||||
**Gate:** existing App geometry-ID tests and new Core tests pass with the exact
|
||||
same values.
|
||||
|
|
@ -133,4 +150,3 @@ dotnet test AcDream.slnx -c Release
|
|||
Then run the full bake from the Release binary, capture its compact report, and
|
||||
validate the output with `PakReader`. No visual gate is required because this
|
||||
slice changes no runtime path.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue