checkpoint: preserve user-gated FW closeout fixes

This commit is contained in:
Erik 2026-08-31 08:27:37 +02:00
parent a2f2eb7d78
commit b8befded8b
22 changed files with 1005 additions and 198 deletions

View file

@ -1096,34 +1096,47 @@ devtools, and screenshots; then closes the GPU-flight transaction in
> `TerrainModernRenderer` (terrain), fed by the unified PView stack. This is the
> authoritative current draw model; the 2026-05-31 reset handoff is historical.
**One visibility owner.** `RetailPViewRenderer.DrawInside` is the production
world gate. Its root is the collided camera/viewer cell, or the synthetic
outdoor cell adaptation; the player's current cell separately owns sunlight
and indoor lighting. A null root exists only for login/debug/streaming-gap
fallback frames. `RetailPViewRenderer` is the one authoritative PView owner and
product family: it builds the main frame, deliberate per-building exterior
floods for the synthetic outdoor root, and separate interior-root look-in
frames. No second per-frame ACME visibility BFS competes with that family.
**One visibility owner.** `RetailFrameWalk`, driven once per frame by
`WalkFrameDriver.Collect`, is the production authority for rooting, portal
recursion, visible cells/buildings, per-view admission, and draw order. Its root
is the collided camera/viewer cell or retail's synthetic outdoor cell; the
player's current cell separately owns sunlight and indoor lighting. A null root
exists only for login/debug/streaming-gap fallback frames. The classes named
`RetailPViewRenderer` and `RetailPViewPassExecutor` remain composition/pass
facades around that one walk; they do not build a second visibility product.
`PortalVisibilityBuilder` is retained only by tests and research diagnostics and
has no production caller. `CellVisibility` does not decide frame visibility.
**Current draw discipline.** Outside-view slices draw sky, terrain, and outdoor
statics first. Interior-root building look-ins then punch all entry apertures
before drawing their shell and contents. The landscape shared-alpha scope
flushes before the root-specific depth boundary. Interior roots perform the
conditional depth clear and then write true-depth exit seals; the synthetic
outdoor root retains world depth and writes far-Z building-entry punches.
Opaque EnvCell shells, immediate far-to-near transparent EnvCell shells, cell
statics and their particles, then the surviving main-stage dynamics and their
particles follow. Look-in and outside-stage dynamics intentionally draw in
their earlier landscape phases. The final world shared-alpha scope flushes
before private portal/paperdoll viewports and UI.
**Current draw discipline.** The walk records one ordered event stream for
landscape cells, building portals, EnvCell shells, statics, dynamics, particles,
depth boundaries, and alpha barriers. `WalkFrameDriver` replays those events in
retail order through the Vulkan leaf renderers. Interior roots preserve retail's
conditional depth clear and true-depth exit seals; the synthetic outdoor root
retains world depth and stamps far-Z entry punches. World translucents and scene
particles share `RetailAlphaQueue` and drain at the walk's exact barriers.
Private portal/paperdoll viewports and retained UI execute only after the world
stream finishes. Projectiles are ordinary live-entity draws, never a separate
global pass.
The modern renderer intentionally does not hard-clip every shell or entity to
the accumulated polygon. It combines PView admission and viewcone checks with
retail's punch/seal depth discipline; terrain/outside slices use the bounded
clip-plane/scissor adaptation. World Wb translucents and Scene particles share
one stable far-to-near `RetailAlphaQueue`; EnvCell transparent shells and
private viewports remain immediate. Projectiles are ordinary live-entity draws,
never a separate global pass.
**Portal-slice clipping is part of admission, not a later approximation.** Each
walk visit retains the exact authored pixel-space portal polygon. The frame
adapter converts it once to a Vulkan GPU clip slot, and the cell shell, statics,
and dynamics admitted by that visit draw through that same slot. A content
drawing sphere may reject a slice before submission, but an accepted slice
never emits the whole mesh unclipped. The same cell can therefore draw more
than once through distinct apertures, exactly as retail's `viewconeCheck` plus
`portal_view` loop requires. Particles remain unclipped at submission because
retail contains them positionally through owner-cell ordering, opaque depth,
and alpha barriers. The complete visibility answer is the union of the walk's
visited EnvCells and visited landscape land cells. EnvCell batch preparation
consumes only the former; particle, light, and shadow visibility consume the
union. Omitting the landscape half disables every outdoor emitter even though
its owner still reaches the draw stream.
WorldBuilder contributes DAT decoding, mesh preparation, residency, and Vulkan
batch mechanics only; it makes no visibility or ordering decision. Forced merge
breaks at walk event boundaries preserve retail order while compatible draws
inside a leaf may still use order-preserving MDI merging.
Retail anchors are `SmartBox::RenderNormalMode @ 0x00453AA0`,
`PView::DrawInside @ 0x005A5860`, `PView::DrawCells @ 0x005A4840`,

View file

@ -325,7 +325,6 @@ research and is no longer active.
| AP-27 | PlayerDescription trailer: GameplayOptions skipped by a 4-byte-aligned heuristic scan for a valid inventory parse; options blob captured opaque, never decoded (retail decodes + applies UI options) | `src/AcDream.Core.Net/Messages/PlayerDescriptionParser.cs:69` | Variable-length opaque blobs; mirrors holtburger's heuristics; follow-up issue extends when panels consume those sections | An options blob that coincidentally parses as a valid inventory (or inventory not landing at EOF) yields wrong/empty inventory+equipped at login; retail-persisted UI options silently ignored | ACE GameEventPlayerDescription.WriteEventBody; holtburger events.rs:195-218 |
| ~~AP-28~~ | **RETIRED 2026-08-08 (Campaign A slice A2).** The three picked AL parameters and the gain-driven eviction are both gone. `RetailSoundMixer` now carries the byte-decoded retail curve — `g = dist < 5 ? vol : 25·vol/dist²`, clamped to 1, ONE master multiply, `db = ceil(20·log10 g)`, and a hard 50 dB no-allocate floor (audible radius ≈94.2 m at unity) — with pan as retail's `15·sin(Δbearing)` in whole decibels and a 5-metre integer deadzone. Every AL source is source-relative with `AL_ROLLOFF_FACTOR = 0` and the global distance model is `None`, so AL contributes no attenuation of its own; the old `InverseDistanceClamped` ref-2 m curve was inverse FIRST power (`2/d`), quieter than retail up close and far louder at range with no cutoff at all. Voice eviction now compares the DAT-authored float priority strictly-less in ring order per `SoundManager::PlaySoundInternal` @ `0x0054FEC0` (the row's old `FUN_00550ad0` citation was wrong — that address is inside an `IntrusiveHashTable` constructor). The residual pan-LAW approximation is AP-173; retail's own `s_bPlaySoundOnlyWhenActive` gate is TS-64. | retired | — | — | `SoundManager::GetAttenuation @ 0x00550020`; `SoundManager::PlaySoundInternal @ 0x00550170` and `@ 0x0054FEC0`; `docs/research/2026-08-08-audio-retail-soundmanager-core.md` |
| AP-31 | Scenery placement drift + the 0xA9B1 road-edge tree — WB-upstream divergences from retail, ACCEPTED (**#49/#50**, 2026-05-11) | `src/AcDream.Core/World/SceneryGenerator.cs` (via `WbSceneryAdapter`) | Piecemeal patching against WB upstream is net-negative (the `e279c46` road-check attempt over-suppressed scenery elsewhere, reverted `677a726`); visible impact = a handful of trees a few meters off | The same WB-upstream class could hide a *larger* placement divergence elsewhere; revisit only via a coherent ACME-style per-vertex filter port | `CLandBlock::get_land_scenes`; ACME GameScene.cs:1074 per-vertex road filter |
| AP-33 | Interior-root look-in cells (**#124** sub-pass) draw their statics + DYNAMICS + emitters WHOLE — no per-part/per-object viewcone check; retail viewconeCheck's each vs the installed view (the **#131** portal closure: a server object in a look-in cell drew nowhere — dynamics-last culls cells absent from the main cone, and post-seal it z-fails anyway) | `src/AcDream.App/Rendering/RetailPViewRenderer.cs` (`DrawBuildingLookIns`) | The main viewcone has no entries for look-in cells; over-include is the safe direction (z-correct, repainted outside apertures by the root's shells); look-in cell counts are small (~1-3 cells) | A few wasted draws on content outside the doorway region (repainted); no under-draw direction remains | `viewconeCheck` 0x0054c250; nested `DrawCells` objects pc:432878 |
| AP-34 | The world now shares one delayed alpha queue across Wb GfxObj/Setup entities and scene particles and drains it at retail's landscape/final boundaries. Residual: the modern reconstruction uses one stable scope-global CYpt sort rather than retail's per-`CPartCell` `CShadowPart` sort followed by cell traversal; `EnvCellRenderer` transparent shell batches also remain immediate and outside this queue | `src/AcDream.App/Rendering/RetailAlphaQueue.cs`; `RetailPViewPassExecutor.cs` (`FlushLandscapeAlpha`); `Rendering/Wb/WbDrawDispatcher.cs`; `ParticleRenderer.cs` | The mandatory modern renderer no longer owns retail `CPartCell` shadow lists. The shared queue restores the material consequence that motivated the port—particles and ordinary translucent parts can interleave—without rebuilding a second scene graph; stable sequence retains authored order on equal CYpt | Transparent objects from different cells can exchange order at a narrow overlap compared with retail cell traversal; an alpha-blended EnvCell shell cannot interleave with a particle or Wb entity, so those rare overlaps can still overpaint differently | `RenderDeviceD3D::DrawObjCellForDummies` 0x005A0760; `CShadowPart::insertion_sort` 0x006B5130; `D3DPolyRender::FlushAlphaList` 0x0059D2E0; `PView::DrawCells` 0x005A4840 |
| AP-36 | Dungeon streaming gate triggers on the player's CURRENT cell being a sealed EnvCell (`CurrCell.IsEnv && !SeenOutside`), an approximation of ACE's full landblock `IsDungeon` (all-heights-zero + NumCells>0 + Buildings.Count==0). The retail BEHAVIOR (a dungeon loads no adjacent landblocks) is faithful — only the runtime TRIGGER is the cheap cell predicate instead of classifying the center landblock. **#135 pre-collapse:** at login/teleport the same collapse is triggered EARLY (the instant the streaming center is recentered onto the spawn/dest cell) via `IsSealedDungeonCell` reading the EnvCell **dat** `SeenOutside` flag — because the physics `CurrCell` is null until placement, which waits for hydration; without the early trigger the full 25×25 ocean-grid window loads then unloads (the ~30 s login FPS ramp). **#215 cell identity:** the pre-collapse/recenter decision compares the player's current `Position.objcell_id` landblock with the received destination `objcell_id`; it never reconstructs the source from XYZ because dungeon frame origins may be negative. **#145/#138 teleport-hold suppression:** during a teleport arrival HOLD the player is unplaced, so `CurrCell` is the frozen SOURCE cell, not the destination; the gate is suppressed for the hold (`DungeonStreamingGate.Compute(isTeleportHold:true)` → not-inside-dungeon) so a teleport OUT of a dungeon follows the destination (the PortalSpace observer pin) and `ExitDungeonExpand`s, instead of re-pinning streaming onto the source dungeon (which left the outdoor destination un-hydrated → 600-frame readiness timeout → force-snap to ocean — the #145 "second teleport does nothing" + #138 incomplete-world) | `src/AcDream.App/Streaming/TeleportLandblockTransition.cs` (source/destination cell-ID classification) + `src/AcDream.App/Streaming/DungeonStreamingGate.cs` (`Compute` — per-frame predicate + teleport-hold suppression) + `src/AcDream.App/World/LiveEntityHydrationPorts.cs` (`LiveEntityWorldOriginCoordinator.TryInitialize` — login pre-collapse) + `src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs` (`OnPosition` — first accepted canonical Position) + `GameWindow:AimTeleportDestination`/`IsSealedDungeonCell` (teleport pre-collapse and DAT predicate) + `src/AcDream.App/Streaming/StreamingController.cs` (collapse/expand/`PreCollapseToDungeon`) | The predicate is already computed for sun/sky gating (playerInsideCell) and exactly matches for sealed dungeons vs windowed building interiors (SeenOutside=true → not gated); no landblock re-classification needed. The dat-flag read is the same `EnvCellFlags.SeenOutside` the hydrated `ObjCell.SeenOutside` is built from (`EnvCell.cs:72`/`PhysicsDataCache.cs:224`), so the pre-collapse decision matches the eventual per-frame gate exactly. The cell-ID comparison matches retail's complete `Position` flow. | A dungeon cell that reports SeenOutside (an entrance cell open to the surface) briefly un-collapses and re-streams the window; a hypothetical windowless building back-room (IsEnv && !SeenOutside but HasBuildings) would wrongly collapse its outdoor neighbors; a sealed-dungeon entrance cell that is itself SeenOutside is simply MISSED by the early trigger and falls back to the existing late collapse (no worse than before #135) | ACE `LandblockManager.GetAdjacentIDs` (dungeons→empty) Landblock.cs:577-582; `IsDungeon` Landblock.cs:1264-1277; retail `SmartBox::TeleportPlayer` 0x00453910 |
| AP-43 | Per-object torch (point/spot) lighting AND sun are both gated on the OBJECT's own cell via the same `IndoorObjectReceivesTorches(ParentCellId)` predicate (`(id & 0xFFFF) >= 0x0100`): indoor objects (EnvCell-parented) get torches + NO sun; outdoor objects get the SUN + ambient + NO torches. This is the faithful per-draw port of retail's `useSunlight` gate — `DrawMeshInternal` (0x0059f398) calls `minimize_object_lighting` only `if (Render::useSunlight == 0)`, and `PView::DrawCells` (0x005a4840) calls `useSunlightSet(1)` (0x005a485a) for the outdoor stage and `useSunlightSet(0)` (0x005a49f3) for the interior-cell stage. **#142 (2026-06-20):** the sun gate is now PER-INSTANCE in the shader (binding=6 `instanceIndoor[]` flag in `mesh_modern.vert`, filled by `AppendCurrentLightSet`) — it was previously a per-FRAME global keyed on the PLAYER cell (`UpdateSunFromSky`). The per-frame global is retained for sealed dungeons (correctly kills the sun frame-wide when no sky is visible). **Residual:** the `ebp_2` second seen-outside test in `CellManager::ChangePosition` (0x004559B0) is unaudited — unclear whether it changes the ambient/sun regime for a subset of cells. No observed behavioral impact in tested cells. | `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs` (`IndoorObjectReceivesTorches`, `ComputeEntityLightSet`, `AppendCurrentLightSet`, `_instIndoorSsbo`/`_indoorData`/`InstanceGroup.IndoorFlags`); `src/AcDream.App/Rendering/Shaders/mesh_modern.vert` (binding=6 `instanceIndoor[]` gate on sun loop); per-frame sun `src/AcDream.App/Rendering/WorldRenderFrameBuilder.cs` (`RuntimeWorldFrameEnvironmentPreparation.UpdateSunFromSky`) | Torches: outdoor objects never torch-lit (exact retail). Sun: indoor objects (furniture, NPCs, player in a windowed building) never sun-lit (exact retail per-stage). Ambient: per-player-cell regime unchanged (exact retail `ChangePosition`). | The `ebp_2` unaudited test in `ChangePosition` could affect a narrow class of cells (entrance cells? sub-cells with special flags?) — no symptom observed; audit it if a lighting edge case arises in an unusual cell type | `useSunlight` gate `DrawMeshInternal` 0x0059f398; `useSunlightSet` 0x0054d450; per-stage `PView::DrawCells` 0x005a4840 (`useSunlightSet(1)` 0x005a485a / `useSunlightSet(0)` 0x005a49f3); `minimize_object_lighting` 0x0054d480; `CellManager::ChangePosition` 0x004559B0 (ambient + seen_outside) |
@ -510,7 +509,6 @@ research and is no longer active.
| TS-59 | No outbound Flow report (retail emits a 6-byte bytes-received+interval header whenever the inbound remote interval advances). | `src/AcDream.Core.Net/Transport/ReliableTransport.cs` (interval clock present; no Flow emission) | ACE parses the Flow header and has no handler (PacketHeaderOptional.cs:117-124); the standalone unsequenced form would trip the watermark hole. Retail itself never consumes inbound Flow and has no throttle (`WireRoomLeft` is a folded return-1). | A future server that rate-adapts on client Flow reports sees nothing. | `SharedNet::ProcessNewRemoteInterval @ 0x00543A80`; `ClientFlowQueue::WireRoomLeft @ 0x0052C1C0` (folded) |
| TS-60 | No 140 s dead-link declaration or referral auto-reconnect in the transport; a silent server is only visible through `LinkStatusSnapshot.SecondsSinceLastPacket` (presentational). | `src/AcDream.Core.Net/Transport/ReliableTransport.cs`; `src/AcDream.Core.Net/WorldSession.cs` (`BuildLinkStatus`) | The input (seconds since last inbound) is already exposed; session lifecycle/reconnect is Runtime's ownership domain and deserves its own campaign rather than a transport-embedded side effect. Every ACE transport death is silence, so nothing server-side depends on the client reacting at 140 s. | A dead link idles until the user acts; no automatic recall/referral reconnect where retail would attempt one. | `ClientNet::ProcessConnection @ 0x00545450` tail (the two 140.0 literals) |
| TS-61 | A UDP send failure burns the reliable sequence and its ISAAC word (the encode commits before `_net.Send`); retail keeps the sealed packet at the queue head and retries with the same key. | `src/AcDream.Core.Net/Transport/OutboundFlowQueue.cs` (`SendGameMessage`) | A connectionless-socket `SendTo` failure is effectively unreachable in practice (no route/ICMP errors surface on later receives, not sends, on Windows UDP); recovering it faithfully needs a full outbound packet queue. The N1 review accepted the exposure explicitly. | One `SocketException` on send would desync the outbound cipher permanently (session death; observable as `[net-out-EX]` followed by silence). | `FlowQueue::TransmitNewPackets @ 0x00547C2C` (retry-from-head) |
| TS-56 | Chase-camera mouse input retains acdream's invented post-filter yaw/pitch scalars (`0.004`/`0.003` radians per count), and held-key pitch/zoom retain their non-retail integration shapes. Retail mouse look passes `FilterMouseInput(delta) × configured sensitivity × 1/15` as the replacement scale to `CameraSet::Rotate`, which then applies the shared 8° angle; retail held pitch uses the same angle and zoom scales the viewer offset multiplicatively. | `src/AcDream.App/Input/CameraPointerInputController.cs`; `src/AcDream.App/Input/MouseLookController.cs`; `src/AcDream.App/Rendering/CameraFrameController.cs` | Slice 8 is behavior-preserving ownership work. The named-retail audit proves the mismatch but has not yet extracted the configured mouse-sensitivity default or the exact caller flags needed for a complete feel port; changing only one scalar here would create a mixed input model. | RMB/MMB orbit, held pitch, and zoom can feel slower, faster, or differently accelerated than retail even though callback ordering and filtering are correct. | `CameraSet::Rotate @ 0x00458310`; `CameraSet::MouseLookHandler` call at `0x00458EF9`; `CameraSet::Raise @ 0x00457B00`; `CameraSet::Closer @ 0x004586D0`; `docs/research/2026-06-11-holistic-map/wf2-camera-viewer.md` |
| TS-62 | **Filed 2026-08-02 (physics campaign, continuation-executor slice).** NO Position route in the dormant executor runs a live `ConstrainTo` binding - including the `SetPosition`/`SetPositionSimple` routes. `RuntimeAuthoritativePositionRoute.ConstrainPhase` (None/Before/After) is classified for EVERY accepted route and recorded into the execution trace, but the constrain-before-vs-after distinction exists purely as classified metadata pending a live binding at the production cutover. | `src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs` (`ApplyPositionAction`/`BuildPositionTrace`); `RuntimeAuthoritativePositionRouteClassifier.cs` (`ConstrainPhase`) | Host-cutover work with no Runtime-side owner to bind to yet; the canonical snapshot's Position IS refreshed on every accepted route, so the fact is retained - only the live constrain/smoothing behavior is deferred. The trace carries the exact phase a host must bind. | Until a host wires it, ANY Position continuation applies its raw pose with no constrain-distance clamp or smoothing - a visible pop instead of retail's constrained correction, on exactly the entities created while an authored placement was in flight. | `SmartBox::HandleReceivedPosition` 0x00453FD0, the three `ConstrainTo` sites (~93007 remote-after, ~93024 teleport-after, ~93041 local-ordinary-before) |
| TS-63 | **Filed 2026-08-02 (physics campaign, continuation-executor slice).** `ApplyResidentCellCleanup`'s three branches: (1) claimed-cell + celless + NOT under lost-cell/deferred ownership - retail's genuine `AddObjectToBeDestroyed` case - has no safe Runtime destruction owner yet, so the executor performs a typed ABANDONMENT (`RejectedAuthority`) instead of destroying; (2) claimed + celless + deferred returns `DeferredUnderLostCellOwnership` - retail's destruction bookkeeping for this exact entity is already owned by the lost-cell/deferred `SetPosition` lifetime (a statement, not a parallel mechanism); (3) claimedCell==0 returns `CelllessNoWeenieMarkUnreachable` and is NOT a divergence - every admitted envelope structurally carries a WeenieDescription (`HasValidShape`), so retail's no-weenie destruction alternative is unreachable through this construction. | `src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs` (`ApplyResidentCellCleanup`; the Abandon conversion in `ApplyEnvelope`) | No production caller yet; every branch is typed and test-observable; building a parallel destruction mechanism ahead of the object-table/lost-cell cutover wiring would be the exact workaround class CLAUDE.md forbids - failing closed is the honest interim. | Branch (1): a genuinely claimed-but-celless-undeferred entity aborts the drain and SURVIVES where retail destroys it, until the cutover wiring lands. Branch (3): a future envelope construction without a WeenieDescription would break the premise and needs re-examination. | `SmartBox::HandleCreateObject` 0x00454C80 tail (~93933 destruction mark; ~93942-93943 un-mark/no-weenie) |
| TS-64 | **Retail's sound-preference surface is only partly present.** Retail registers eight `[Sound]` keys in `SoundManager::InitPrefs` @ `0x005503F0`; two are unimplemented in acdream. (a) `s_bPlaySoundOnlyWhenActive` (default **1**) is checked against `Device::m_bIsActiveApp` in every entry point and in both `PlaySoundInternal` overloads, so an unfocused retail client is SILENT; acdream keeps playing when the window loses focus. (b) `s_SoundFeatures == 1` forces pan to dead centre; acdream's `RetailSoundMixer.Mix`/`GetPan` take a `panningEnabled` flag with conformance coverage, but no preference is wired behind it, so panning can never be turned off. The three enable bools (`Sound Disabled`, `Ambient Sound Disabled`, `Interface Sound Disabled`) also have no acdream counterpart — note retail's on-disk polarity is inverted relative to its backing variables, so a future reader must not assume the sense. | `src/AcDream.App/Audio/OpenAlAudioEngine.cs` (no focus gate); `src/AcDream.Core/Audio/RetailSoundMixer.cs` (`panningEnabled`, unwired) | Slice A2 kept its blast radius on the mixing model: window-focus state and a preference surface are host plumbing rather than mixing math, and the mixer parameter exists so wiring them later needs no math change. | Alt-tabbed acdream keeps making noise where retail goes quiet; users cannot disable panning or the individual sound classes. | `SoundManager::InitPrefs @ 0x005503F0`; `SoundManager::PlaySoundInternal @ 0x0054FEC0` and `@ 0x00550170`; `docs/research/2026-08-08-audio-retail-soundmanager-core.md` §1 |

View file

@ -271,9 +271,10 @@ the transformed DAT `SortCenter`; only adjacent compatible entries may batch.
Billboard particle textures are resident bindless `sampler2DArray` handles in
the per-instance vertex ABI, so different textures preserve that sorted order
inside one instanced draw; only a DAT blend-mode boundary splits the run. This
keeps dense particle fields from becoming one GL draw per alternating texture.
`RetailPViewRenderer` drains the landscape scope before the optional depth
clear, and `GameWindow` drains the final scope before private viewports/UI.
keeps dense particle fields from becoming one Vulkan draw per alternating
texture. `WalkFrameDriver` drains the landscape and world scopes at the exact
alpha barriers emitted by `RetailFrameWalk`; the frame orchestrator reaches
private viewports/UI only after the world stream completes.
Sky and sealed off-screen render targets remain independent. No DAT reader,
mesh decoder, or second scene graph was introduced. Retail anchors:
`CPhysicsPart::UpdateViewerDistance` `0x0050E030`,
@ -281,7 +282,10 @@ mesh decoder, or second scene graph was introduced. Retail anchors:
`CShadowPart::insertion_sort` `0x006B5130`,
`D3DPolyRender::AddMeshToAlphaList` `0x0059C230`, and
`D3DPolyRender::FlushAlphaList` `0x0059D2E0`. The modern per-cell-order and
EnvCell-shell residual is tracked explicitly as AP-34.
EnvCell-shell residual is tracked explicitly as AP-34. WorldBuilder does not
choose visible cells, portal slices, or cross-cell order: it receives the
walk's ordered records and GPU clip slots and performs asset preparation plus
order-preserving Vulkan batching only.
**Retail portal-space viewport adapter (2026-07-15).**
`src/AcDream.App/Rendering/PortalTunnelPresentation.cs` uses the extracted

View file

@ -538,6 +538,18 @@ Stages FW0FW6; no long-lived dual path.
**Spec:** [`docs/superpowers/specs/2026-08-30-campaign-w-retail-frame-walk-design.md`](../superpowers/specs/2026-08-30-campaign-w-retail-frame-walk-design.md).
**Plan + ledger:** [`docs/plans/2026-08-30-campaign-fw-frame-walk.md`](2026-08-30-campaign-fw-frame-walk.md).
**2026-08-31 checkpoint — code complete, final connected gate pending.**
`RetailFrameWalk` is now the only production visibility/order owner. Exact
walk portal polygons route shells, statics, and dynamics through the same
Vulkan clip slots (`11e68aad`), fixing the cathedral defect's actual mechanism:
accepted content was previously emitted whole after a Boolean sphere test.
The stale frame-product carrier is deleted, and the retail camera adjustment
laws are complete (`90eba0ec`, `4d037974`). Release hermetic App gates passed
6,729/6,729 before a still-open character-selection client locked the Release
outputs. Campaign FW remains ACTIVE until both standard connected routes and
the complete cathedral/transition/physical-camera visual matrix pass. Safety
rollback before the final batch: `572de1ec`.
### Phase O — DatPath Unification — SHIPPED 2026-05-21
**Tagline:** ONE thing touches the DATs.

View file

@ -12,6 +12,17 @@ world-lifecycle automation protects fresh login, outdoor/world-edge/dungeon
travel, same-location revisit, exact graceful disconnect, and uncapped
fresh-process reconnect with canonical JSON/PNG evidence.
**Active render prerequisite (2026-08-31): Campaign FW — retail frame walk.**
The production cutover and camera port are code-complete: one retail-derived
walk now owns rooting, portal recursion, per-view clipping, draw order, depth
events, and shared-alpha barriers. The cathedral through-wall mechanism was a
whole-mesh submission after Boolean portal admission; exact walk aperture clip
slots now route shells, statics, and dynamics together. The campaign remains
ACTIVE until the standard connected routes and owner visual matrix prove the
positioned cathedral player/NPC transition, particles, seams, doorways, portal
arrival, and retail camera feel. Plan/ledger:
[`2026-08-30-campaign-fw-frame-walk.md`](2026-08-30-campaign-fw-frame-walk.md).
Before new M4 quest/emote/character-creation subsystem bodies enter the App
layer, the active structural prerequisite is the behavior-preserving
`GameWindow` decomposition in

View file

@ -549,6 +549,48 @@ z-tested) where the old apparatus had no entry. The legacy !walkActive
path keeps the old seal draw. Suites: hermetic 6,762/0, Walk lane 213/1,
InstalledDat walk conformance 40/1.
**FW4 FINAL CUTOVER — CODE COMPLETE 2026-08-31.** The cathedral remote
player defect was not a wrong-cell admission bug. Exact installed-DAT replay
at the owner's reported poses proved that retail legitimately reaches
0xF4180112 through a small, bounded four-plane portal view; suppressing that
cell or its dynamics would have hidden valid content. The defect was the
modern leaf contract: acdream used the drawing sphere as a Boolean admission
test and then emitted the complete shell/entity mesh through clip slot zero.
Retail instead loops every `portal_view`, runs `viewconeCheck`, and draws the
accepted content through that exact aperture. Commit `11e68aad` carries each
walk visit's authored pixel polygon into a Vulkan clip slot and routes the
same slot to the cell shell, statics, and dynamics. Multiple surviving views
therefore produce multiple clipped draws; particles remain positionally
contained by their owner turn and alpha barriers. Focused walk/installed-DAT
tests pass 22/22 and the hermetic App suite passed 6,727/6,727 in Release.
The remaining production carrier deletion landed in `69e69408`, `cb22691b`,
and `a2f2eb7d`: the old executor/product frame, `PortalFrame`, `ClipAssembly`,
`WalkLookInViews`, and borrowed-product exchange are gone. The concrete
`RetailPViewRenderer`/`RetailPViewPassExecutor` class names remain as narrow
composition/pass facades around the single `WalkFrameDriver`; they no longer
own a competing visibility product. `PortalVisibilityBuilder` has no
production caller and remains test/research material. The walk's visible set
feeds preparation, particles, lights, and shadows. `PortalDepthMaskRenderer`
is retained deliberately because it now performs the walk's retail far-Z
punches and true-depth exit seals, not the removed visibility decision.
**FW4 cathedral gate PASSED 2026-08-31.** The first closeout round exposed
one concrete cutover regression: `877e935a` published `VisitedCells` (EnvCell
flood/look-ins) as the complete visibility answer but omitted
`VisitedLandscapeCellIds`. The draw stream still carried the exact cathedral
waterfall owners, yet `ParticleVisibilityController` made every outdoor
emitter fail retail's `CObjCell::IsInView` gate. The correction publishes the
union to particle/light/shadow consumers while keeping EnvCell batch
preparation scoped to EnvCells. The live probe now records every
`0xCF418000..13` waterfall owner reaching the deferred alpha queue across the
0x104 -> outdoor -> 0x101/0x100 transition; Walk tests pass 220/1 and the exact
installed-DAT subset passes 21/0. The owner then passed the live matrix:
waterfalls/outdoor particles restored, the player and special NPC hidden by
opaque cathedral walls, no player disappearance at the cell transition, and
no seam/building distortion. Both standard connected routes remain mandatory
before the campaign status changes from ACTIVE.
- Entities gate per view via `Render::viewconeCheck` at their cell's walk
turn; dynamics-last and alpha interleave per the walk's stages
(`RetailAlphaQueue` becomes the stream's alpha stage or is absorbed —
@ -588,6 +630,17 @@ InstalledDat walk conformance 40/1.
side-by-side feel gate vs retail (chase, zoom saturation ≈13 m boom,
collision, slope align).
**CODE COMPLETE 2026-08-31 (`90eba0ec`, `4d037974`).** Retail's
multiplicative distance adjustment, per-component write-refusal gates,
eight-degree raise/lower/rotate step, `FilterMouseInput × sensitivity × 1/15`
mouse scaling, and first-person special cases are ported from the named
decomp. `Closer` in-head correctly stays in-head because its 0.18 m candidate
fails the 0.5 m near gate; `Farther` takes the authored 0.6 m back/0.5 m up
escape; in-head Raise/Lower changes target direction Z by 0.2 and clamps to
±0.8 without moving the eye. Camera tests pass 55/55 and the post-camera
hermetic App suite passed 6,729/6,729 in Release. TS-56 is retired. The
physical-display feel matrix remains part of the final owner gate.
### FW6 — closeout
- Divergence-register reconciliation (added: landblock-stage streaming
@ -611,6 +664,14 @@ Each stage lands as its own commit series; record `git revert` anchors
here as stages close (the Modern Runtime plan's convention). FW3 and FW4
are the cutover stages — their revert anchors are mandatory entries.
- **Night-work safety anchor:** `572de1ec30d981abfb5c88f331e1a20c6213b63b`
(`checkpoint: preserve cathedral look-in investigation state`). Reverting
the 2026-08-31 aperture/camera/cleanup batch means returning exactly to this
commit; no history rewrite is required.
- **Portal-slice clipping:** `11e68aad`.
- **Camera completion:** `90eba0ec`, `4d037974`.
- **Final stale frame-product removal:** `a2f2eb7d`.
## Risks (tracked, from the spec)
- **Perf** — decided by FW3's checkpoint, numbers over hope.