Compare commits
No commits in common. "claude/campaign-w-retail-frame-walk" and "main" have entirely different histories.
claude/cam
...
main
138 changed files with 961 additions and 41976 deletions
15
.gitignore
vendored
15
.gitignore
vendored
|
|
@ -23,7 +23,7 @@ packages/
|
|||
Thumbs.db
|
||||
|
||||
# Reference repos and retail client (large, not our code, separate licenses)
|
||||
# WorldBuilder is exempt ? it's a load-bearing dependency tracked as a git
|
||||
# WorldBuilder is exempt — it's a load-bearing dependency tracked as a git
|
||||
# submodule pointing at our fork (Phase N, see docs/architecture/worldbuilder-inventory.md).
|
||||
references/*
|
||||
!references/WorldBuilder
|
||||
|
|
@ -39,7 +39,7 @@ proveout*.log
|
|||
launch.utf8.log
|
||||
n4-verify*.log
|
||||
|
||||
# A6.P5 (2026-05-25) ? door-stuck reproduction captures (multi-MB);
|
||||
# A6.P5 (2026-05-25) — door-stuck reproduction captures (multi-MB);
|
||||
# the 3-record fixture extracted from these lives at
|
||||
# tests/AcDream.Core.Tests/Fixtures/door-bug/over-penetration-capture.jsonl
|
||||
door-stuck-capture.jsonl
|
||||
|
|
@ -55,7 +55,7 @@ imgui.ini
|
|||
# User-only download cache (per-developer, not source)
|
||||
refs/
|
||||
|
||||
# Python tooling (under tools/) ? bytecode caches
|
||||
# Python tooling (under tools/) — bytecode caches
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
|
|
@ -72,12 +72,11 @@ logs/
|
|||
# Git worktrees for isolated feature work
|
||||
.worktrees/
|
||||
|
||||
# Per-session retail-debugger scratch ? cdb scripts, logs, analysis helpers.
|
||||
# Per-session retail-debugger scratch — cdb scripts, logs, analysis helpers.
|
||||
# The committed reference workflow lives in CLAUDE.md "Retail debugger toolchain";
|
||||
# session-specific traces should not pollute the repo.
|
||||
*.cdb
|
||||
!tools/walk-oracle/*.cdb
|
||||
# tools/cdb/ holds committed reference scripts ? exempt them from the blanket rule above.
|
||||
# tools/cdb/ holds committed reference scripts — exempt them from the blanket rule above.
|
||||
!tools/cdb/*.cdb
|
||||
launch_*.log
|
||||
launch_*.err
|
||||
|
|
@ -96,7 +95,7 @@ steep_roof_trace*
|
|||
substep_trace*
|
||||
sg_built.txt
|
||||
# Stray bash-mangled path artifacts from PowerShell-via-bash escaping
|
||||
C[?-?]*
|
||||
C[-]*
|
||||
|
||||
# Obsidian vault config (personal, not project-wide)
|
||||
.obsidian/
|
||||
|
|
@ -105,7 +104,7 @@ C[?-?]*
|
|||
claude-memory
|
||||
studio-shots/
|
||||
|
||||
# MP1b acdream-bake output ? user-machine artifact, never committed
|
||||
# MP1b acdream-bake output — user-machine artifact, never committed
|
||||
# (docs/superpowers/plans/2026-07-05-mp1b-pak-and-bake.md, Task 5).
|
||||
*.pak
|
||||
|
||||
|
|
|
|||
|
|
@ -325,6 +325,7 @@ 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-32 | Cell shells DRAW +0.02 m above the dat EnvCell origin (`ShellDrawLiftZ`, z-fight vs coplanar terrain); retail draws at the origin verbatim. Split invariant: PHYSICS + visibility graph UNLIFTED (f35cb8b, **#119**-residual), every DRAW-space consumer of portal/cell geometry LIFTED (OutsideView color gate via `Build(drawLiftZ)`, seal/punch fans — **#130**) | `src/AcDream.App/Rendering/PortalVisibilityBuilder.cs` (`ShellDrawLiftZ`); `src/AcDream.App/Rendering/RetailPViewPassExecutor.cs` (`DrawPortalDepthWrite`) | Shell floors coplanar with terrain z-fight in our z-buffered frame; the 2 cm lift is the documented stand-in | A new draw-space consumer of portal/cell polygons that forgets the lift re-opens a 2 cm seam at horizontal aperture edges (the #130 top-edge strip, ~7 px at 2.4 m); a visibility consumer that picks up the LIFTED transform re-opens the #119-residual horizontal-portal side-cull | retail draws cell geometry at the dat EnvCell origin (no lift) |
|
||||
| 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 |
|
||||
|
|
|
|||
|
|
@ -314,8 +314,6 @@ issue is closed, the strip was missed; delete both.
|
|||
| `ACDREAM_PROBE_CELL_CACHE` | indoor walking phase d | `=1` | gates one `[cell-cache]` line per EnvCell first-cached in `PhysicsDataCache.CacheCellStruct` (poly counts, BSP root structure) | print-only; fires at most once per EnvCell (cache is no-op after first population); no DebugPanel mirror | `PhysicsDiagnostics.ProbeCellCacheEnabled` |
|
||||
| `ACDREAM_PROBE_CHILD_CELL` | c4 route 7 | `=1` | gates one `[child-cell]` line per Runtime committed-child canonical-cell write in `RuntimeLiveEntitySessionController`, `RuntimeEntityObjectLifetime`, `RuntimeEntityDirectory` (parent/child guid, old/new cell, cause tag) | print-only | `PhysicsDiagnostics.ProbeChildCellEnabled` |
|
||||
| `ACDREAM_PROBE_CLIPROUTE` | "throwaway apparatus — strip once §4 ships" | `=1` | print-on-change `[clip-route]` / `[clip-route-disp]` / `[clip-route-scis]` lines: outside-slice clip routing, region-SSBO bytes, terrain-UBO head, actual GL/RHI scissor state | print-only | `RenderingDiagnostics.ProbeClipRouteEnabled` |
|
||||
| `ACDREAM_PROBE_WALK_SHADOW` | Campaign FW3.2b-2 flip apparatus — dies with the flip commit | `=1` | runs the PRODUCTION retail frame walk per frame in SHADOW (no draws change) and prints one `[walk-shadow]` line per frame whose visited cell/building sets diverge from the old visibility path (the I5 dual-shadow pattern applied to the FW3 static cutover) | print-only; adds the per-frame CPU cost of the shadow walk while set | `RenderingDiagnostics.ProbeWalkShadowEnabled` |
|
||||
| `ACDREAM_PROBE_WALK_ROOT` | the cathedral Phase I instrumentation (synthesis doc `docs/research/2026-08-30-cathedral-synthesis.md`) — dies with that investigation | `=1` | prints `[walk-root]` rooting lines every ~30th frame AND for 8 frames after each interior/outdoor root flip (viewer/root cell, camera-cell resolution, slice count, the walk's OWN exit-view count, the TRUE root flood separated from the visited union, eye + forward); `[walk-cam]` replayable camera dumps (eye, viewport, full view-projection) every ~300 frames and at flips; `[falls]` lines on EVERY particle submission carrying a real falls owner (0xCF418000-13) with route + phase; `[falls-adm]` admission verdicts for F418 emitters (~every 120th ApplyRetailView); plus the earlier family — `[walk-part]` per-route submissions, `[walk-alpha]` drains, `[walk-dyn]` outside-stage verdicts (~120th product frame), `[walk-portal]` per-portal side/clip decisions (~90th frame), and `[dyn-route]` per-entity ON-CHANGE DynamicLast admission traces (parent cell, look-in exclusion, viewcone verdict, the cell's lifted-plane shape — "0" marks a pass-all zero-plane slice) | print-only; chatty near particle-heavy scenes while set | `RenderingDiagnostics.ProbeWalkRootEnabled` |
|
||||
| `ACDREAM_PROBE_CONTACT_PLANE` | spike-only, 2026-05-20 | `=1` | gates one `[cp-write]` line per write to `CollisionInfo.ContactPlane*`/`LastKnownContactPlane*` fields (field, old→new, caller method via stack walk, source line); only logs on actual value changes | print-only, but performs a stack walk to identify the caller method when firing — real CPU cost per write while on (not just a string format); suppresses no-op writes to bound volume | `PhysicsDiagnostics.ProbeContactPlaneEnabled` |
|
||||
| `ACDREAM_PROBE_ENT` | #138 | `="1"` | Traces the persistent player entity across teleport streaming churn: presence in the render draw-set flat view vs. survival of the dynamics cull, to distinguish "missing from draw set" vs "present but culled" | print-only, "Observation-only — emits no behavior change" (doc comment). `LogPlayerDynOnChange` dedupes by transition to avoid per-frame spam. Marked STRIP-once-root-caused (like the dense-town FPS apparatus). | `EntityVanishProbe.Enabled` (`EntityVanishProbe.cs:23-24`), issue #138-B |
|
||||
| `ACDREAM_PROBE_FLAP` | "throwaway apparatus — strip once the flap mechanism is confirmed" | `=1` | EVERY FRAME (unthrottled, not change-gated) while the camera root is indoor: `[flap]` from `PortalVisibilityBuilder.Build` (portal side-test/traverse/cull/projection) + paired `[flap-cam]` from `PhysicsCameraCollisionProbe`/`[flap-sweep]` (FindCameraCell resolution, eye positions) | print-only, but unthrottled per-frame `StringBuilder` allocation + `Console.WriteLine` on multiple call sites while indoor — heavy log volume/allocation under sustained indoor play; does not alter rendered output | `RenderingDiagnostics.ProbeFlapEnabled` |
|
||||
|
|
|
|||
|
|
@ -518,26 +518,6 @@ Plus polish that doesn't get its own phase number:
|
|||
|
||||
## Phases ahead — agreed order
|
||||
|
||||
### Campaign FW — the retail frame walk (ACTIVE 2026-08-30)
|
||||
|
||||
**Tagline:** retail owns every frame decision; modern code only moves bytes.
|
||||
|
||||
**Filed:** 2026-08-30 after Campaign PV's halt (the PView graft onto the
|
||||
gather/regroup renderer failed; owner verdict). Replaces the ~4.7K-line
|
||||
accumulated visibility/composition layer (`PortalVisibilityBuilder`,
|
||||
`RetailPViewRenderer`/`PassExecutor`, `CellVisibility` BFS, `ClipPlaneSet`
|
||||
gating) and the dispatcher's material regrouping with a verbatim port of
|
||||
retail's frame walk (`SmartBox::RenderNormalMode` rooting,
|
||||
`PView::DrawCells` recursion, `LScape::draw`, building look-ins, stage
|
||||
interleave, depth semantics, camera incl. the retail zoom envelope). The
|
||||
WB-derived asset/residency layer is untouched. Gated by a cdb walk oracle
|
||||
(retail's captured per-frame draw sequence as replayable fixtures), a
|
||||
standing transition checklist, and a dense-Arwic perf stop-rule at FW3.
|
||||
Stages FW0–FW6; 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).
|
||||
|
||||
### Phase O — DatPath Unification — SHIPPED 2026-05-21
|
||||
|
||||
**Tagline:** ONE thing touches the DATs.
|
||||
|
|
|
|||
|
|
@ -1,626 +0,0 @@
|
|||
# Campaign FW — the retail frame walk (implementation plan)
|
||||
|
||||
**Status:** ACTIVE (opened 2026-08-30; spec approved by owner 2026-08-30).
|
||||
**Spec (approved):**
|
||||
[`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)
|
||||
— note: the campaign letter is **FW** (frame walk); the spec predates the
|
||||
rename (the roadmap already owns "Phase W" for the historical Unified Cell
|
||||
Graph program).
|
||||
**Branch:** `claude/campaign-w-retail-frame-walk` off main `e102fb36`.
|
||||
**Predecessor:** Campaign PV, halted 2026-08-30
|
||||
(`docs/plans/2026-08-30-pview-visibility-campaign.md`); its back half is
|
||||
quarantined on `claude/cathedral-rendering-debug-47a417` and nothing merges
|
||||
from it.
|
||||
|
||||
**One-sentence goal:** retail owns every frame decision (rooting, portal
|
||||
views, cone gating, landscape walk, buildings, stage order, depth
|
||||
semantics, degrade, particles, camera); modern code owns only pixel-neutral
|
||||
GPU mechanics; the ~4.7K-line accumulated visibility layer and its patch
|
||||
apparatus are deleted at their stages.
|
||||
|
||||
---
|
||||
|
||||
## Binding rules (all stages, binding on subagents)
|
||||
|
||||
1. **The ownership line is the campaign invariant.** If a modern component
|
||||
makes a decision retail did not make, that is a campaign bug regardless
|
||||
of pixels. Every ported function cites its named retail symbol.
|
||||
2. **Grep named first.** `docs/research/named-retail/acclient_2013_pseudo_c.txt`
|
||||
by `class::method` before any fresh decompilation; pseudocode notes in
|
||||
`docs/research/` before C# (the mandatory workflow).
|
||||
3. **Fail loud, never silently drop.** The PV3 post-mortem rule: any
|
||||
admission/seeding/ordering filter that rejects work must log the
|
||||
rejection under a diagnostic flag or fall open — silent drops produced
|
||||
this week's hollow-world regressions.
|
||||
4. **The transition checklist is a standing self-run gate.** Every
|
||||
candidate build gets, before the owner sees it: portal-in arrival
|
||||
frames, a Holtburg house exit, a teleport-to-Holtburg stopwatch, and a
|
||||
steady-state fps read (drive.ps1 + screenshots). No terrace-only
|
||||
verification, ever again.
|
||||
5. **No long-lived dual path.** Campaign V precedent: a stage that cuts
|
||||
over deletes the code it replaces in the same stage. Short-lived
|
||||
scaffolding inside one stage is fine; a toggle that survives a stage
|
||||
boundary is not.
|
||||
6. **Register discipline.** Deviations add their row in the same commit;
|
||||
ported mechanisms delete rows in the same commit
|
||||
(`docs/architecture/retail-divergence-register.md`).
|
||||
7. **Suites green at every stage close** (hermetic filter
|
||||
`Lane!=InstalledDat&Lane!=Linux&Lane!=Manual&Lane!=Timing&Lane!=Live&Lane!=PreparedPackage`),
|
||||
plus the stage's own gates. Sonnet implements; Opus reviews stage
|
||||
boundaries (architectural + retail-faithfulness lenses).
|
||||
|
||||
## Verified seam map (recon 2026-08-30; cite these, do not re-derive)
|
||||
|
||||
**Frame chain today:** `GameWindow.OnRender` (GameWindow.cs:1785/1809) →
|
||||
`GameFrameGraphSlot.Render` → `RenderFrameOrchestrator.Render`
|
||||
(RenderFrameOrchestrator.cs:206/226) → `VulkanWorldScenePhase`
|
||||
(VulkanCompositionFramePhases.cs:113, owns the backbuffer pass) →
|
||||
`WorldSceneRenderer.Render` (WorldSceneRenderer.cs:128) →
|
||||
`RetailPViewRenderer.DrawInside` (RetailPViewRenderer.cs:112), which builds
|
||||
`PortalVisibilityFrame` internally (line 122) and drives
|
||||
`RetailPViewPassExecutor` through the packed route contract
|
||||
(`RenderFrameCandidateRoute`: LandscapeOutdoorStatic → LandscapeBuildingShell
|
||||
→ LookInObject → LandscapeOutsideDynamic → CellStatic → DynamicLast;
|
||||
`WbDrawDispatcher.PackedOracle.cs:108/171` enforces in-order consumption).
|
||||
|
||||
**What the walk-order submitter REPLACES:** `GroupKey` material bucketing
|
||||
(`ClassifyBatches` WbDrawDispatcher.cs:3292, `ClassifyPackedBatches`
|
||||
PackedOracle.cs:610, `_groups`/`_packedGroups`), `PartitionInstanceGroups`
|
||||
(:2232), and the two distance sorts (`CompareOpaqueSubmissionOrder` :2865,
|
||||
`CompareTransparentSubmissionOrder` :2871). Note: the deferred-alpha path
|
||||
(`DeferTransparentGroups` :2619 → `RetailAlphaQueue`) already reconstructs
|
||||
draw-local order per instance — the transparent side is half-way to
|
||||
walk-order today and is the SSBO-layout pattern to follow.
|
||||
|
||||
**What is REUSED unchanged:** `GlobalMeshBuffer` arena (GlobalMeshBuffer.cs:84),
|
||||
`WbMeshAdapter`/`ObjectMeshManager` mesh prep + self-heal
|
||||
(WbMeshAdapter.cs:328/435), `TextureCache` GpuTextureSlot resolution
|
||||
(WbDrawDispatcher.cs:3421), the ring-section SSBO writers + shared
|
||||
`WorldTransformFrameArena` (Rhi.cs:913/945), `MeshPipelineSet` (Rhi.cs:49),
|
||||
and the terminal MDI recorder `DrawIndirectRangeRhi` (Rhi.cs:798).
|
||||
|
||||
**Caveats that shaped the stages:** three parallel classifiers exist
|
||||
(classic, packed, directional-shadow — WbDrawDispatcher.DirectionalShadows.cs:1021)
|
||||
and must agree until each is cut over; `EntityClassificationCache` and
|
||||
`PackedProjectionClassificationCache` assume group-bucketed submission and
|
||||
retire with their routes; `StageImmediateGroup` lays per-instance SSBOs by
|
||||
group-order cursor, so the walk submitter re-derives layout from stream
|
||||
order (start per-instance like deferred alpha; merge adjacent runs after
|
||||
correctness).
|
||||
|
||||
**Visibility consumers to re-point at the walk output** (the complete
|
||||
production list — radar, audio, and picking do NOT consume visibility):
|
||||
- Particles: `WorldSceneRenderer.cs:263` → `ParticleVisibilityController`
|
||||
→ `ParticleSystem.ApplyRetailView` (Core/Vfx/ParticleSystem.cs:313), fed
|
||||
outdoors by `TerrainModernRenderer.VisibleCellIds`.
|
||||
- Lights: `WorldSceneRenderer.cs:264` → `RuntimeWorldFrameEnvironmentPreparation`
|
||||
(WorldRenderFrameBuilder.cs:543) → `LightManager.BuildPointLightSnapshot`
|
||||
(Core/Lighting/LightManager.cs:284).
|
||||
- EnvCell prepare filter: `RetailPViewPassExecutor.cs:276` →
|
||||
`EnvCellRenderer.PrepareRenderBatches` (EnvCellRenderer.cs:456).
|
||||
- Entity routes: `RenderScenePViewFrameProduct.cs:1330-1587`.
|
||||
- Gated diagnostics only beyond that.
|
||||
|
||||
**Streaming atomicity (what already holds vs what FW adds):** per-cell
|
||||
publication is ALREADY atomic — one dictionary swap of a complete
|
||||
immutable `EnvCellLandblock` (EnvCellRenderer.CommitPublication:347);
|
||||
outdoor terrain commits at LandblockRenderPublisher.AdvanceBeginOne:267.
|
||||
The real partial states are (a) landblock-stage terrain-before-interiors
|
||||
across yielded publication stages (LandblockPresentationPipeline.cs:711/757
|
||||
— deliberate walking pop-in, hidden by the reveal hold during
|
||||
portal/login), and (b) committed cells drawing before every contained
|
||||
static mesh is resident (skip-if-missing, EnvCellRenderer.cs:720). FW keeps
|
||||
both as EXPLICIT, register-rowed adaptations; the walk consumes only the
|
||||
committed registry (`CellVisibility.TryGetCell` — production role is the
|
||||
registry; its BFS is already dead code).
|
||||
|
||||
**The patch apparatus to delete (exact sites):**
|
||||
- `ShellDrawLiftZ` +0.02 lift, three consumers: PortalVisibilityBuilder.cs:353
|
||||
(const + Build's drawLiftZ), RetailPViewPassExecutor.cs:697 (depth fans),
|
||||
LandblockBuildFactory.cs:523 (baked into draw cellTransform).
|
||||
- Punch stencils (#117 adaptation): PortalDepthMaskRenderer.Rhi.cs:59-125.
|
||||
- Look-in seeding heuristics: PortalVisibilityBuilder.BuildFromExterior:713-923
|
||||
+ callers MergeNearbyBuildingFloods (RetailPViewRenderer.cs:411),
|
||||
BuildInteriorRootLookIns (:471), DrawBuildingLookIns (:600-860), and the
|
||||
barrier math (:515-559).
|
||||
- The InViewCells side-channel (RetailPViewRenderer.cs:2187) once consumers
|
||||
read the walk output.
|
||||
|
||||
**Camera facts:** `RetailChaseCamera.cs` — `Distance` default 2.61 m,
|
||||
clamps DistanceMin=2/DistanceMax=40 (:110-113); collision probe + slope
|
||||
align + chase already default-on retail behaviors
|
||||
(Core/Rendering/CameraDiagnostics.cs:28-50). Retail envelope (from the
|
||||
d71f5ef1 diff, which lives ONLY on the quarantined branch — this branch has
|
||||
no AD-116 row): `CameraSet::Farther` @0x00458890 scales the viewer offset
|
||||
multiplicatively and REFUSES the write once |offset.y| ≥ 10 (gates |x|<10,
|
||||
|y|<10, z<450, z>-1.8); the standard arc saturates near offset
|
||||
(0, −10, +8.33) ≈ a 13.0 m boom. `CameraSet::Raise` @0x00457980 rotates at
|
||||
preserved length. TS-56 records the non-retail adjustment scalars
|
||||
(CameraAdjustmentSpeed=40/s etc.).
|
||||
|
||||
**Retail anchors already established:** `SmartBox::RenderNormalMode`
|
||||
@0x00453aa0 (frame rooting), `PView::DrawCells` @0x005A4840,
|
||||
`LScape::draw` @0x00506330, `LScape::grab_visible_cells` @0x00504EC0,
|
||||
`DrawBuilding` pre-punch flush @0x0059F2A0, `ShouldDrawParticles`
|
||||
@0x0050FE60, the untextured skip in `D3DPolyRender::DrawMesh` @0x0059d4a0,
|
||||
`ACRender::polyClipFinish` (two call sites: `PView::GetClip` + debug),
|
||||
`Render::viewconeCheck`.
|
||||
|
||||
---
|
||||
|
||||
## Stages
|
||||
|
||||
### FW0 — the walk oracle (tooling + fixtures)
|
||||
|
||||
**STATUS: COMPLETE 2026-08-30.** Ten trace fixtures + README
|
||||
(`docs/research/2026-08-30-fw-walk-oracle/`, commit `71b11817`), the
|
||||
replay helper + 19 fixture goldens (`9f4c0f95`), and the port-ready
|
||||
pseudocode model + full decomp appendix
|
||||
(`docs/research/2026-08-30-fw-walk-pseudocode.md`, `a6885aa2`). Findings
|
||||
that reshaped the campaign: the frame roots at the CAMERA's cell;
|
||||
outdoor frames draw the #456 far building every frame and bury it by
|
||||
depth (no hiding mechanism exists to port); retail DOES draw depth-only
|
||||
invisible portal-polygon panels (`DrawPortalPolyInternal` — punch far-Z
|
||||
/ seal own-depth) — the AD-117 stamps re-invented a real mechanism at
|
||||
the wrong site. **Known gap:** the first capture round did not record
|
||||
the camera pose; exact FW1 replay needs pose-stamped traces. A prepared
|
||||
template (`tools/walk-oracle/fw0-capture-pose.cdb.template`) makes the
|
||||
re-capture a short owner-assisted session; scheduled for when FW1's walk
|
||||
is ready to gate (structural conformance proceeds meanwhile on the
|
||||
existing traces).
|
||||
|
||||
**Goal:** retail's actual per-frame draw walk, captured and replayable,
|
||||
BEFORE any port is judged.
|
||||
|
||||
- Build the cdb capture harness (`tools/walk-oracle/`): breakpoint script
|
||||
logging, per frame, the sequence of cell/building draw entries with
|
||||
stage boundaries — hook `PView::DrawCells`, `CEnvCell` draw,
|
||||
`CLandBlock`/`LScape::draw`, `DrawBuilding`; dump cell ids (`this`
|
||||
offsets verified with one-shot `dt` first). cdb safety rules from the PV
|
||||
campaign are binding: never inline `-c` attach to a client that matters,
|
||||
every `j` branch ends in `gc`, `qd` threshold auto-detach, watch hit-rate
|
||||
lag.
|
||||
- Capture positions (owner assists — retail sessions): Sanctuary terrace
|
||||
center + terrace EDGE (normal camera — the #456 acceptance pose),
|
||||
cathedral portal-in arrival, a Holtburg doorway (the flap scene), a
|
||||
Holtburg street + house exit, one deep dungeon corridor. Record camera
|
||||
pose per capture.
|
||||
- Also capture the same positions in acdream (world state: cell registry
|
||||
contents) so W1 replays are apples-to-apples.
|
||||
- Pin the port function list as a research note with pseudocode stubs:
|
||||
`docs/research/2026-08-30-fw-walk-pseudocode.md` (grow it through FW1).
|
||||
- **Deliverable/gate:** trace fixtures checked into
|
||||
`docs/research/2026-08-30-fw-walk-oracle/`, replayable by a test
|
||||
helper; harness scripts committed; no production code touched.
|
||||
|
||||
### FW1 — `RetailFrameWalk` + `PViewSet` (pure CPU, conformant)
|
||||
|
||||
**Goal:** the ported walk as a standalone module that reproduces the
|
||||
oracle traces exactly.
|
||||
|
||||
- New `src/AcDream.App/Rendering/Walk/`: `RetailFrameWalk` (rooting per
|
||||
`SmartBox::RenderNormalMode`; EnvCell recursion per `PView::DrawCells`;
|
||||
landscape walk per `LScape::draw`/`grab_visible_cells`; building
|
||||
look-ins per `DrawBuilding` + `ConstructView(CBldPortal)`),
|
||||
`PViewSet` (`ConstructView`/`set_view`/`PView::GetClip` — the chain
|
||||
polygon is the only CPU-clipped geometry; one view per chain, never
|
||||
merged), `IWalkWorldView` (the read seam: committed cells via the
|
||||
registry, buildings via `BuildingRegistry`, camera pose).
|
||||
- Output: an ordered walk event stream (cell id, view, stage) matching the
|
||||
oracle trace format, plus the frame's visible-cell set.
|
||||
- Pseudocode first (workflow step 3), C# port line-by-line (step 4), named
|
||||
symbols in comments.
|
||||
- **Gates:** oracle-trace replay tests pass position-for-position;
|
||||
synthetic-world unit tests for rooting/recursion/cone edge cases; the
|
||||
existing replay fixture suite (Issue95/113/120/124/127/130/131/176-177/
|
||||
181, SanctuaryPortalSeam, HouseExitWalkReplay, TowerAscentReplay,
|
||||
CornerFloodReplay) re-expressed against the walk — behavior differences
|
||||
vs the old builder are adjudicated against the ORACLE, not against the
|
||||
old builder. No production wiring; hermetic suites green.
|
||||
|
||||
**FW1 STATUS (2026-08-30, @`77f5342b`):** NINE of the ten pose-stamped
|
||||
fixtures reproduce retail frame-exactly (foundry-deep all 39 frames,
|
||||
doorway-still, street-outdoor, terrace-center, terrace-edge — the #456
|
||||
acceptance pose — cathedral-arrival, holtburg-walkout, -transitions,
|
||||
-walkabout); foundry-entry is exact through F66 with the F67–F79
|
||||
standing segment parked on ONE live number (building 0036's root-plane
|
||||
viewpoint — probe `tools/walk-oracle/fw1-f67-viewpoint-probe.cdb`,
|
||||
goal-sanctioned retail-session stop filed with the user). Load-bearing
|
||||
adjudications, all decomp-cited: the two-arm `get_degrade` threshold
|
||||
rule (ideal→max at the live `deg_mul≈+0.99`), deg_mul's DYNAMIC swing
|
||||
under capture load (doorway-still pins mul=0 — an environment pin like
|
||||
the viewport), znear=0.1 confirmed, and the Ghidra-arbitrated portal
|
||||
walker truth table (BN FPU pseudo-C mis-renders branch sense — three
|
||||
separate misreads this stage; Ghidra first, always).
|
||||
**Gate amendment:** the old-replay-fixture re-expression is retired as
|
||||
an FW1 gate — the ten traces are direct retail evidence and strictly
|
||||
supersede fixtures that encode the OLD builder's behavior; the old
|
||||
suite's scenario classes (doorway flap, dungeon seams, tower ascent,
|
||||
corner flood) are covered by the traces and re-verified live at the
|
||||
FW3/FW4 connected gates, where the old fixtures retire with the old
|
||||
builder. Production classes: `RetailFrameWalk`, `WalkPView` (the
|
||||
PViewSet role), `WalkBuildingPortals`, `WalkLandscape`,
|
||||
`WalkVisibilityMath`, `WalkScreenClip`, `WalkCopyView` under
|
||||
`src/AcDream.App/Rendering/Walk/`.
|
||||
|
||||
### FW2 — `OrderedDrawStream` + `OrderPreservingSubmitter`
|
||||
|
||||
**Goal:** walk-order submission through the existing RHI, proven
|
||||
pixel-equivalent on static content.
|
||||
|
||||
- `OrderedDrawStream`: append-only (mesh ref, transform, material state,
|
||||
stage, cell) commands in walk order. `OrderPreservingSubmitter`: binds
|
||||
`GlobalMeshBuffer` once, reuses ring-section writers + `MeshPipelineSet`
|
||||
+ `DrawIndirectRangeRhi`; emits per-instance first (the deferred-alpha
|
||||
SSBO pattern), then merges ADJACENT same-state commands into MDI runs —
|
||||
a merge across a state or stage boundary is forbidden by construction
|
||||
(assert it).
|
||||
- Depth semantics: port retail's depth compare/write configuration for the
|
||||
world passes verbatim in this stage (current set uses Less — verify
|
||||
against retail's raster state and document the finding either way).
|
||||
- Diagnostic harness: a dev scene (offscreen target) drawn twice — old
|
||||
path vs walk path — framebuffer-compared (the Slice-7 comparison
|
||||
pattern, with the pixel-diff repeat floor from
|
||||
`feedback_pixel_diff_needs_repeat_floor`).
|
||||
- **Gates:** submitter unit tests (order preservation, merge legality);
|
||||
framebuffer equivalence on static-content scenes; hermetic suites green.
|
||||
|
||||
**FW2 STATUS (2026-08-30, @`e65644cb`):** `OrderedDrawStream` +
|
||||
`WbDrawDispatcher.OrderedStream` (the submitter partial — per-instance-
|
||||
first emission, pure-CPU merge-run builder, fail-loud stage/pipeline/
|
||||
cull boundary validation) and `WorldDepthContract` are landed with 27
|
||||
RecordingGpuDevice gates; hermetic 6,714/0. Depth verified verbatim
|
||||
from the decomp (`zfuncVal` 0x2 = D3DCMP_LESS via `SetDepthBufferMode`
|
||||
@0x005a2d10; LESSEQUAL is GameSky-local). **Gate adjudication:** the
|
||||
offscreen pixel A/B rides FW3's cutover toggle — a walk-driven scene
|
||||
first exists there, `IGpuRenderTarget` has no readback verb today, and
|
||||
opaque content under Less is order-independent, so the RecordingGpuDevice
|
||||
order/merge referees are FW2's automated gate and the pixel proof joins
|
||||
FW3's (offline pixel gate + owner visual gate). Deferred to FW3 and
|
||||
recorded in the code: the building-detail overlay replay, portal-punch
|
||||
emission, and the `_drawCullModes`/alpha-scope sequencing constraint.
|
||||
|
||||
### FW3 — static world cutover + THE perf checkpoint
|
||||
|
||||
**Goal:** terrain, EnvCell shells, and buildings draw via the walk in
|
||||
production; the statics gather path is deleted; perf is measured before
|
||||
deeper commitment.
|
||||
|
||||
- Root the production frame at the walk: `WorldSceneRenderer` drives
|
||||
`RetailFrameWalk` + submitter for the static routes
|
||||
(LandscapeOutdoorStatic, LandscapeBuildingShell, CellStatic); dynamic
|
||||
routes (LookInObject, LandscapeOutsideDynamic, DynamicLast) stay on the
|
||||
packed path within this stage only (the route-contract split makes the
|
||||
seam clean).
|
||||
- Retire the three `ShellDrawLiftZ` sites in this stage — walk order +
|
||||
retail depth semantics replace the tie-break; prove it at the #130
|
||||
doorway-strip scene before deleting, and delete the register row in the
|
||||
same commit.
|
||||
- Terrain/sky/weather draw at their walk turns (the stage interleave from
|
||||
`RetailPViewPassExecutor`'s ported knowledge moves inward).
|
||||
- **Perf checkpoint (the spec's stop rule):** dense Arwic uncapped,
|
||||
Release, exact binary: CPU/GPU p50/p99 + fps vs the current production
|
||||
profile (519.7 fps / 1.869 / 1.096 ms baseline). Within 20% → proceed;
|
||||
worse → STOP, owner decision with numbers on the table.
|
||||
- **Gates:** transition checklist self-run; hermetic suites; owner visual
|
||||
gate (statics look right indoors + outdoors, no seam strip at the
|
||||
doorway, no z-fighting at shells); perf numbers recorded in this plan.
|
||||
|
||||
**FW3 SLICE PLAN (2026-08-30, from the cutover-surface survey):**
|
||||
|
||||
- **FW3.1 — production walk world data.** Materialize the walk's world
|
||||
model at landblock commit time, registry-owned, through the LEGAL
|
||||
`IDatReaderWriter` seam (never raw `DatCollection` —
|
||||
`RuntimeDatAccessArchitectureTests`): (a) `WalkCell` fields onto/beside
|
||||
`LoadedCell` (portal side/exact from the RAW `Flags` already preserved
|
||||
on `CellPortalInfo`; plane from the existing `ClipPlanes` parallel
|
||||
list; the 0xFFFF→0xFFFFFFFF exit-sentinel widening); (b) a production
|
||||
`WalkBuilding` build in `LandblockBuildFactory`/`BuildingLoader` — the
|
||||
BIGGEST gap: today's `Building` drops ModelId/Frame/portal
|
||||
flags/stab lists at load; (c) the walk landscape assembly (retail
|
||||
z-slabs from the height table, the SideCellCount LOD pyramid,
|
||||
`CellBuildings[]`, viewer-centred grid) fed from landblock residency;
|
||||
(d) the production frame context: camera/rays/CyPlane/ObjectToClip
|
||||
derived from the LIVE `WorldCameraFrame` + real viewport (the capture
|
||||
client's 1024×720 constants are fixture pins, not production values).
|
||||
Conformance: the ten oracle fixtures re-run against the PRODUCTION
|
||||
builders (same DATs → identical walk output as the test adapter).
|
||||
- **FW3.2 — walk-driven static routes.** `WorldSceneRenderer` drives
|
||||
`RetailFrameWalk` → `OrderedDrawStream`
|
||||
(Terrain/BuildingShell/CellStatic/LookInStatic/PortalPunch stages) →
|
||||
`SubmitOrderedStream` for static content; the old builder STOPS
|
||||
EMITTING `LandscapeOutdoorStatic`/`LandscapeBuildingShell`/`CellStatic`
|
||||
ranges (the `CompletePackedProductionFrame` count assertion forbids
|
||||
emit-but-skip); dynamic routes (`LookInObject`,
|
||||
`LandscapeOutsideDynamic`, `DynamicLast`) stay packed — which requires
|
||||
`ClipFrameAssembler` + `ViewconeCuller` to SURVIVE this stage (their
|
||||
slices key the dynamic routes). Walk-path SELECTION publication
|
||||
(static picking today rides `publishSelection: true` in the packed
|
||||
classifier — an unlisted consumer; the walk submitter must feed
|
||||
`RetailSelectionScene` or statics go un-clickable). Portal-punch
|
||||
emission via `PortalDepthMaskRenderer` at walk turns; building-detail
|
||||
overlay replay; sky/weather move from per-slice to the walk's
|
||||
landscape turn; the `_drawCullModes`/alpha-scope sequencing constraint
|
||||
resolved (own cull scratch or strict sequencing). Route-keyed particle
|
||||
owners re-sourced from the walk.
|
||||
- **FW3.2b-2 — the production rooting (DESIGN, 2026-08-30; execute
|
||||
INLINE, not via subagent — the integration-slice rule).** The
|
||||
dual-compute split for this stage: the OLD visibility
|
||||
(`PortalVisibilityBuilder` + look-in frames + clip assembly +
|
||||
viewcone) KEEPS RUNNING to feed everything dynamic; the WALK draws
|
||||
everything static. No old static DRAWS remain (no double draw):
|
||||
1. `RetailPViewRenderer.DrawInside` keeps steps 1–8 and 10–11
|
||||
(visibility, merge/look-in seeding, clip frame, prepare — with
|
||||
`prepareCells` UNIONED with the walk's flood/look-in cells —
|
||||
viewcone, route build) but `RenderScenePViewFrameProduct` STOPS
|
||||
emitting `LandscapeOutdoorStatic`, `LandscapeBuildingShell`, and
|
||||
`CellStatic` ranges. Per the plan's route split, LOOK-IN OBJECTS
|
||||
(`LookInObject`) are dynamic-side and STAY PACKED this stage —
|
||||
the old look-in machinery keeps drawing look-in cell content and
|
||||
its punches; the WALK's building turns therefore draw shell
|
||||
content + punches for NON-look-in... NO — resolve the overlap the
|
||||
other way: the walk's building turns run their punch+look-in
|
||||
machinery ONLY for the walk's own conformance events; production
|
||||
look-in DRAWS (punch fans + content) stay entirely on the old
|
||||
path this stage, and the walk's `BuildingShell` stage content +
|
||||
`OnPunchGeometry`/look-in DC draws are SUPPRESSED in the driver's
|
||||
production wiring for look-in-owned buildings… REJECTED — that is
|
||||
a dual path. FINAL: the walk owns ALL of it — building shells,
|
||||
punches, look-in STATIC content (stage LookInStatic) — and the
|
||||
`LookInObject` route survives ONLY for the DYNAMICS inside
|
||||
look-in cells (`BuildLookInRoutes` filtered to dynamic
|
||||
classifications). The old `DrawBuildingLookIns` static half and
|
||||
`DrawLookInPortalPunch` retire with the static routes.
|
||||
2. `WalkFrameDriver`'s leaf renderer maps to:
|
||||
sky → `RetailPViewPassExecutor` sky call; terrain slice →
|
||||
`_terrain.Draw(camera, frustum, clipPlanes: slice.Planes,
|
||||
ndcClipAabb: slice.NdcAabb)`; cell shell →
|
||||
`EnvCellRenderer.Render(pass, singleCellSet)` (+ transparent
|
||||
ordered per cell); punch fan → `PortalDepthMaskRenderer.
|
||||
DrawDepthFan(worldVerts, viewProjection, slicePlanes,
|
||||
forceFarZ: true)`; `ClearInteriorDepth` →
|
||||
`IWorldPassScope.ClearInteriorDepth` (keep production's
|
||||
slices>0 gate); `DrawExitSeals` → the existing seal-fan loop
|
||||
(`DrawPortalDepthWrite` seal flavor over the root flood's exit
|
||||
portals); alpha barrier → `RetailAlphaQueue.FlushFartherThan`.
|
||||
3. Static records: cell statics via `RenderSceneQuery.
|
||||
CopyCellStaticsTo(cellId)`; outdoor statics via a per-landscape-
|
||||
cell index built once per frame from
|
||||
`CopyIndexTo(OutdoorStatic)`; building shells via the
|
||||
`IsBuildingShell` entities keyed by anchor cell.
|
||||
4. Consumers re-pointed: particle owners
|
||||
(`RenderFrameRouteOwnerSelector.Replace(LandscapeOutdoorStatic…)`
|
||||
sites) re-source from the walk's populated cells; diagnostics
|
||||
source counts tolerate the route removal; `EmitRenderSignature`'s
|
||||
terrain/scenery counts re-source or annotate.
|
||||
5. Sky/weather: sky moves into the walk's LS turn (once); weather
|
||||
stays in the old late-dynamics slice loop this stage (it is
|
||||
dynamic-adjacent and the plan's FW4 moves it fully).
|
||||
6. Gates before the owner sees a build: hermetic + InstalledDat
|
||||
suites; the connected lifecycle route; the offline pixel gate
|
||||
(outdoor statics); dense-Arwic pre/post pair (physical display).
|
||||
|
||||
**FW3.2b-2 STATUS (2026-08-30, @`4918677b`): THE STATIC CUTOVER IS
|
||||
COMMITTED.** The walk drives every production static draw
|
||||
(`DrawWalkDrivenStatics` + the dynamics-remainder phases); the three
|
||||
static routes are deleted from the product builder; particle owners
|
||||
re-source from the walk's per-cell-turn visited sets (retail's own
|
||||
gate — `ShouldDrawParticles` @0x0050FE60). Landed evidence: the walk
|
||||
shadow probe ran 11k+ live frames faultless with walkCells ⊂ oldCells
|
||||
every frame. The old static path survives ONLY behind `!walkActive`
|
||||
for the executor-fake test path (15 retail-ordering regression tests;
|
||||
unreachable by production) — **its deletion is FW4 scope**.
|
||||
Transitional risks recorded in code: two-pass walk cost (FW3.4
|
||||
measures), interior slice-count reconciliation between the old clip
|
||||
assembly and the walk's exit-view survival, outdoor merged-flood
|
||||
punch coverage now riding the walk's building-BSP punches (owner
|
||||
visual gate verifies). Suites: hermetic 6,750/0; Walk 201/1;
|
||||
InstalledDat conformance 40/1 untouched.
|
||||
|
||||
- **FW3.3 — ShellDrawLiftZ retirement.** THREE sites (the register row
|
||||
AP-32 lists only two — `LandblockBuildFactory.cs` bakes the lift into
|
||||
the drawn cell transform and is unlisted): delete all three, flip the
|
||||
#130 proof (`UnliftedGate_LeavesTheStripAtTheDrawnTopEdge` is
|
||||
explicitly written to fail-by-gap until the lift is gone), delete
|
||||
AP-32 in the same commit.
|
||||
**FW3.3 STATUS (2026-08-30, @`05240d2c`): RETIRED + connected-PASSED.**
|
||||
ShellDrawLiftZ deleted at every site (AP-32 deleted same commit); the
|
||||
#130 proof flipped as authored (strip test deleted, the 147-combo
|
||||
coverage sweep passes in the one dat space, unchanged tolerances).
|
||||
Three lifecycle-gate PASSES today on exact binaries; owner-confirmed
|
||||
live: no seams at the doorway class. PERF FLAG for FW3.4: Holtburg
|
||||
town 19-22 ms CPU p50 / ~1.8 MB-frame alloc on the walk path (capped
|
||||
diagnostic read; interiors 3.3 ms) - candidates: collapse the
|
||||
two-pass walk, batch per-cell shells, cache the outdoor-statics
|
||||
index, cut per-segment rebinds.
|
||||
|
||||
**FW3.4 PERF PAIR (2026-08-30, same session/conditions, exact
|
||||
binaries):** dense Arwic uncapped, PRE (@87853359, old static path)
|
||||
vs POST (@3a05d659, walk-driven): CPU p50 23.6 -> 31.6 ms (+33.5%),
|
||||
p95 36.3 -> 44.3 ms (+21.9%), alloc p50 133 KB -> 1,920 KB/frame
|
||||
(14x). THE +/-20% STOP RULE TRIGGERS. Sources are the flagged known
|
||||
costs (two walk passes/frame, per-cell single-segment submissions
|
||||
with full section rebinds, per-cell record-array copies in the world
|
||||
data). FW3.4a fixes them (single walk pass; prepare-once/draw-ranges
|
||||
- the deferred-alpha architecture; pooled record segments), then the
|
||||
pair re-measures against the SAME baseline; the owner decision only
|
||||
triggers if it still exceeds 20% after the known fixes.
|
||||
|
||||
**FW3.4a RESULT (2026-08-30, @`1da178d9`): THE STOP RULE CLEARS.**
|
||||
Post-refactor dense Arwic uncapped vs the SAME baseline: CPU p50
|
||||
23.6 -> 22.9 ms (-3.2%), p95 36.3 -> 34.7 ms (-4.5%) - the walk path
|
||||
is now FASTER than the old static path. The refactor's own crash
|
||||
round (VK_ERROR_DEVICE_LOST) taught the binding contract: leaf draws
|
||||
and alpha flushes rebind set-0 between ranges, so DrawOrderedRange
|
||||
rebinds per call; only the ring WRITES are once-per-frame. FOLLOW-UP
|
||||
FILED: alloc p50 still ~1.5 MB/frame on the walk path (baseline
|
||||
133 KB) - the per-frame driver/leaf/populator object graph in
|
||||
DrawWalkDrivenStatics; hoist to renderer fields with per-frame
|
||||
Reset (FW3.4 closeout or FW6 polish; CPU-neutral today, GC-hitch
|
||||
hygiene).
|
||||
|
||||
- **FW3.4 — perf checkpoint + gates.** BASELINE CORRECTION: the
|
||||
519.7 fps / 1.869 / 1.096 ms numbers are the Aerlinthe
|
||||
ordinary-production profile, NOT dense Arwic. Before the cutover
|
||||
lands, capture a fresh dense-Arwic pre-cutover baseline with the same
|
||||
harness (`tools/run-connected-r6-soak.ps1 -Uncapped -DenseTown`), and
|
||||
apply the ±20% stop rule to THAT pair. Transition checklist: the
|
||||
lifecycle gate + dense-town soak exist; a Holtburg house-exit stop and
|
||||
a teleport stopwatch metric must be ADDED to the lifecycle route
|
||||
(`drive.ps1` from the spec does not exist — the checklist runs on the
|
||||
existing gate scripts).
|
||||
|
||||
### FW4 — entities, particles, dynamics into walk turns; delete the apparatus
|
||||
|
||||
**Goal:** everything draws at its walk turn; the patch apparatus and the
|
||||
old visibility layer are gone.
|
||||
|
||||
**THE CATHEDRAL FALLS — FIXED, USER-VERIFIED 2026-08-30 (`280c0549`).**
|
||||
After six failed staging rounds, a seven-report independent review
|
||||
(corpus + synthesis: `docs/research/2026-08-30-cathedral-*.md`), the
|
||||
reverts of slices 4+5 (`7f500b97` — the straddle rule was invented and
|
||||
slice 5 had misidentified cell-0x106 TORCHES as the falls, causing the
|
||||
in-cathedral particle regression), and Phase I instrumentation proving
|
||||
every submission datum healthy (route/phase/admission/flood all
|
||||
retail-correct), the OWNER's pointer to the old pipeline's user-verified
|
||||
#132 fix (`e102fb36`) supplied the missing mechanism: retail's falls
|
||||
containment is POSITIONAL — an outdoor emitter's polys join the one
|
||||
alpha list AT ITS OWNER CELL'S TURN in the far-to-near landscape walk,
|
||||
so each nearer building's pre-punch FlushAlphaList drains the farther
|
||||
content against still-true depth BEFORE the punch stamps far-Z. The walk
|
||||
path had the barriers but batched all static-owner submission after
|
||||
every punch. `WalkFrameEventKind.StaticParticles` now submits each
|
||||
landscape cell's and building shell's owners at that turn (meshes
|
||||
marked/flushed first); the batched path is deleted for both root kinds.
|
||||
Torches confirmed unregressed. CARRIED from the same gate: indoor RAIN
|
||||
(our weather confined by the slice scissor AABB instead of retail's
|
||||
polygon-exact view planes — diagnosed, unfixed) and the STALKS (drawn
|
||||
never-drawn marker anchors — the degrade admission re-land, synthesis
|
||||
plan step 10).
|
||||
|
||||
**FW4 SLICE 1 — the interior outside-view source cutover (2026-08-30,
|
||||
pulled forward from the FW3 visual gate).** The owner's stairwell/grass
|
||||
transition report (grass briefly covering floor openings at doorway
|
||||
crossings, intermittent — the #119 family) probe-pinned to the FW3 dual
|
||||
path: the walk decided WHETHER terrain draws (`_interiorPView.OutsideView.
|
||||
ViewCount > 0`) while the old `PortalVisibilityBuilder` assembly decided
|
||||
WHERE (slice clip planes + count + scissor), and the walk's punch fans
|
||||
indexed the OLD slice array with WALK view indices. At boundary frames
|
||||
(`ACDREAM_PROBE_WALK_ROOT` captures: the slices=2 short-flood family at
|
||||
OUT→IN, a 1-frame outdoor blip mid-crossing) the old builder's exit views
|
||||
went fat/degenerate, terrain splashed over interior pixels, and the
|
||||
depth-clear preserves COLOR — any cell absent from the walk's flood never
|
||||
repainted its pixels. Retail cannot produce this: it has ONE visibility
|
||||
structure. The cutover: `ClipFrameAssembler.ReassembleOutsideViewFromWalk`
|
||||
materializes the walk's own `outside_view` (pixel points → standard NDC →
|
||||
the existing `ClipPlaneSet.From` machinery) into the assembly's
|
||||
outside-view block right after Collect, before the single
|
||||
`PrepareClipFrame` publication (moved below the walk block); the
|
||||
`Landscape` event now carries the walk's active-view count (rides the
|
||||
record's existing `OutsideViewCount` field — trace mapping compares kind
|
||||
only, ZERO oracle-fixture churn) and the driver fans exactly that many
|
||||
terrain slices — `activeTerrainSliceCount` is deleted end to end. Outdoor
|
||||
roots keep the assembler's single full-screen slice (asserted ==1;
|
||||
identical content by construction). Suites: hermetic 6,762/0 (4 new
|
||||
materializer tests pinning the y-flip + plane-sign conventions), Walk
|
||||
lane 209/1, InstalledDat walk conformance 40/1 — all unchanged or grown.
|
||||
Remaining desync surfaces deliberately left for the rest of FW4: exit
|
||||
SEALS + cell slices still assemble from the old per-cell views (identical
|
||||
dat portal polygons; only the visible SET can differ), and interior-root
|
||||
look-in seeding still reads the old outside view.
|
||||
|
||||
**FW4 SLICE 2 — the walk-flood seal cutover (2026-08-30, pulled forward
|
||||
from the FW3 visual gate's cathedral round).** The owner's cathedral
|
||||
report (waterfall particles shining through from the terrace — toggling
|
||||
with which side of the #456 seam the CAMERA resolves to) probe-pinned via
|
||||
the `[walk-part]`/`[walk-alpha]` phase tags: the falls emitters ride the
|
||||
cell-owners route and submit POST-depth-clear on interior-rooted frames
|
||||
(retail's own timing — cell-scope alpha drains at the end-of-frame flush
|
||||
against seal-owned depth), but our seal SET still came from the old
|
||||
apparatus's flood, which misses exit portals at the #456 seam band (its
|
||||
never-drawn panel family) — unsealed aperture depth let the end-of-frame
|
||||
drain z-pass across the whole opening. `DrawWalkExitPortalMasks` now
|
||||
stamps THE WALK'S OWN ordered interior flood
|
||||
(`WalkFrameDriver.InteriorFloodCells`, captured at
|
||||
`OnInteriorFloodDrawTurn` — retail pc:432785-432786's reverse
|
||||
cell_draw_list), with the old assembly's per-cell slice clips where
|
||||
present and unclipped seals (benign: exact dat aperture polygon,
|
||||
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.
|
||||
|
||||
- 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 —
|
||||
decide at implementation with the retail stage list as the arbiter);
|
||||
particles draw at their owner cell's turn (`ShouldDrawParticles`),
|
||||
unclipped.
|
||||
- Re-point the consumers at the walk's visible-cell output: particles
|
||||
(`ParticleVisibilityController`), lights (`BuildPointLightSnapshot`
|
||||
filter), EnvCell prepare filter. Directional-shadow caster selection
|
||||
consumes the same set.
|
||||
- DELETE in this stage (registry rows updated in the same commits):
|
||||
look-in seeding + callers + barrier math; punch stencils
|
||||
(`PortalDepthMaskRenderer` #117); the InViewCells side-channel;
|
||||
`PortalVisibilityBuilder`'s production role; `RetailPViewRenderer` /
|
||||
`RetailPViewPassExecutor` shells (their stage knowledge now lives in the
|
||||
walk); the packed route machinery + `PackedOracle` + both
|
||||
classification caches; `CellVisibility`'s dead BFS.
|
||||
- **Gates:** the full re-expressed replay suite; transition checklist;
|
||||
hermetic suites; the owner visual matrix — cathedral #456 acceptance
|
||||
(far building, monsters, stalks hidden at the terrace edge at NORMAL
|
||||
camera; no seam strip; no z-fighting; candle/falls/lake/portal-flood
|
||||
intact), doorway flap, dungeon wall/floor seams, portal-in, house exit,
|
||||
teleport timing.
|
||||
|
||||
### FW5 — camera completion
|
||||
|
||||
**Goal:** the camera is retail's, end to end.
|
||||
|
||||
- Port `CameraSet::Farther`/`CameraSet::Raise` verbatim into
|
||||
`RetailChaseCamera`: the multiplicative offset scaling with the
|
||||
write-refusal gates (|x|<10, |y|<10, z<450, z>−1.8 — NOT a simple
|
||||
distance clamp), the preserved-length pitch. The 40 m dev zoom goes
|
||||
away with it (owner-approved in the spec round).
|
||||
- Port retail's zoom/pitch adjustment rates if recoverable from decomp
|
||||
(retires TS-56); if not recoverable, TS-56 stays with a note.
|
||||
- **Gates:** camera unit tests against ported constants; owner
|
||||
side-by-side feel gate vs retail (chase, zoom saturation ≈13 m boom,
|
||||
collision, slope align).
|
||||
|
||||
### FW6 — closeout
|
||||
|
||||
- Divergence-register reconciliation (added: landblock-stage streaming
|
||||
exposure rows if not already rowed, order-preserving MDI merging as
|
||||
intentional architecture; retired: lift, #117 stencils, look-in-era
|
||||
rows, TS-56 if ported).
|
||||
- Docs: architecture doc render section rewritten around the walk;
|
||||
`worldbuilder-inventory.md` updated (WB = asset layer only);
|
||||
`launch-options.md` rows for any probes added/removed (probes die with
|
||||
their investigations).
|
||||
- Memory: `project_render_pipeline_digest.md` rewritten current-truth-on-top
|
||||
around the walk; campaign memory closed out.
|
||||
- Roadmap + milestones updated; the quarantined PV branch gets a
|
||||
disposition note (kept as archive; nothing merges).
|
||||
- Final gates: full hermetic suites, both standard connected routes,
|
||||
the complete owner visual matrix, merge to main.
|
||||
|
||||
## Rollbacks
|
||||
|
||||
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.
|
||||
|
||||
## Risks (tracked, from the spec)
|
||||
|
||||
- **Perf** — decided by FW3's checkpoint, numbers over hope.
|
||||
- **Streaming scope creep** — only the two register-rowed exposure
|
||||
adaptations may be touched; anything more is out of scope.
|
||||
- **Oracle fidelity** — traces need owner-assisted retail sessions (FW0);
|
||||
captured once, checked in.
|
||||
- **Classifier drift during staging** — FW3's split runs walk statics
|
||||
beside packed dynamics for one stage; the route contract seam is the
|
||||
guard, and FW4 deletes the duality.
|
||||
- **Camera ergonomics** — the dev zoom disappears at FW5; a separate dev
|
||||
flycam is deliberately deferred (the Fly camera in `CameraController`
|
||||
already exists for development framing).
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
# HANDOVER — Campaign FW / cathedral defects (2026-08-30, end of session)
|
||||
|
||||
For the next session taking over. Read this top to bottom, then the
|
||||
campaign memory (`claude-memory/project_frame_walk_campaign.md`), then the
|
||||
synthesis (`docs/research/2026-08-30-cathedral-synthesis.md`).
|
||||
|
||||
## Where you are
|
||||
|
||||
- **Worktree:** `C:\Users\erikn\source\repos\acdream\.claude\worktrees\peaceful-blackburn-5333f0`
|
||||
- **Branch:** `claude/campaign-w-retail-frame-walk` (pushed; NOT merged to main)
|
||||
- **HEAD at handover:** the commit after `4686a1ac` — `git log --oneline -15`
|
||||
shows tonight's chain. Suites at HEAD: hermetic App 6,762/0, InstalledDat
|
||||
walk conformance 40/1, walk lane green.
|
||||
- **Launch (owner-gated visual checks):** PowerShell, env
|
||||
`ACDREAM_DAT_DIR="$env:USERPROFILE\Documents\Asheron's Call"`,
|
||||
`ACDREAM_LIVE=1`, `ACDREAM_TEST_HOST=127.0.0.1`, `ACDREAM_TEST_PORT=9000`,
|
||||
`ACDREAM_TEST_USER=testaccount`, `ACDREAM_TEST_PASS=testpassword`, probe
|
||||
`ACDREAM_PROBE_WALK_ROOT=1`, then
|
||||
`dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Release`.
|
||||
ALWAYS build first; the owner closes the client for you (never
|
||||
Stop-Process — graceful-close rules in CLAUDE.md).
|
||||
- **The defect scene:** cathedral, landblock `0xF418`. Owner telelocs:
|
||||
terrace `0xF4180104 [31.9 57.3 169.8]`; the seam pair 0x0104↔0x0101 at
|
||||
y≈72; remote player parked at `0xF4180112 [36.3 18.6 169.8]` (south
|
||||
hall). The remote player is a second account/client the owner runs.
|
||||
|
||||
## Campaign status (FW stages)
|
||||
|
||||
- FW0–FW3 landed (see the plan `docs/plans/2026-08-30-campaign-fw-frame-walk.md`
|
||||
status blocks). FW1 residue: foundry F67–79 (probe script
|
||||
`tools/walk-oracle/fw1-f67-viewpoint-probe.cdb`, needs an owner retail
|
||||
session). FW4 is ACTIVE and partially executed via tonight's slices.
|
||||
FW5/FW6 not started.
|
||||
- The production renderer is still a HYBRID: the walk owns statics,
|
||||
terrain, sky, seals-set, punch fans, and (as of tonight) static-owner
|
||||
particle turns; the LEGACY `PortalVisibilityBuilder` still builds
|
||||
`pvFrame` every frame and feeds per-cell clip slices, the look-in
|
||||
frames + look-in dynamics, the DynamicLast per-cell viewcone, and the
|
||||
outside-dynamics slice loop. FW4's goal is deleting that duality — the
|
||||
through-wall defect below is a direct consequence of it.
|
||||
|
||||
## Tonight's ledger (chronological, all pushed)
|
||||
|
||||
USER-VERIFIED WINS:
|
||||
1. **Cathedral falls bleed FIXED** (`280c0549`) — the #132 positional
|
||||
invariant ported walk-natively: `WalkFrameEventKind.StaticParticles`
|
||||
submits each landscape cell's / building shell's emitter owners AT ITS
|
||||
WALK TURN; the batched pre-clear submission is deleted. Mechanism
|
||||
credit: the OWNER's pointer to the old pipeline's `e102fb36` fix.
|
||||
2. **Indoor/seam rain FIXED** (`75eab42f`) — retail gates the weather pass
|
||||
on `SmartBox::is_player_outside` @0x00451e80 (Ghidra:
|
||||
`(player objcell & 0xFFFF) < 0x100`); one boolean at the executor's
|
||||
weather site. GameSky draws at DEPTHTEST_ALWAYS (pc 0x00507063) — the
|
||||
depth-disabled weather port was correct.
|
||||
3. **In-cathedral torch regression FIXED** by reverting slices 4+5
|
||||
(`7f500b97`).
|
||||
4. Earlier same-day: FW4 slices 1/2/3/6 (`37febd1f`,`c40aecfc`,`5f7ccdea`,
|
||||
`bb9212c7`) — kept per the tri-review verdicts, with reworks listed in
|
||||
the synthesis.
|
||||
|
||||
THE REVIEW CORPUS (read before ANY new cathedral theory):
|
||||
`docs/research/2026-08-30-cathedral-review-{prompt,fable,opus,audit,
|
||||
datformat,datgeometry,decomp}.md` + `2026-08-30-cathedral-synthesis.md`.
|
||||
The decomp report is the retail frame SSOT (one-frame seal latch; TWO
|
||||
PViews; FIFO no-sort alpha lists; full 0f DrawBuilding flush;
|
||||
ShouldDrawParticles update-time; CEnvCell::IsInView = constant
|
||||
PARTIALLY_INSIDE). The DAT-geometry report is the F418 ground truth
|
||||
(falls = landblock stabs 0xCF418000-13; exits are FULL-SIZE, no "thin
|
||||
bands"; the seam = COINCIDENT double exit-to-outside planes at y=48 and
|
||||
the 0x104/0x101 boundary at y=72).
|
||||
|
||||
## THE OPEN DEFECT — through-wall remote player (NOT FIXED)
|
||||
|
||||
**Symptom:** a remote player parented at `0xF4180112` renders through
|
||||
opaque walls when the observer stands in `0xF4180101` AND from outdoors.
|
||||
From `0xF4180104` he is correctly hidden. Retail never shows him (no
|
||||
sightline chain exists through the complex). Owner-reported ~21:46; two
|
||||
fix attempts tonight; still visible after both.
|
||||
|
||||
**Attempt 1 (context):** slice 3 (`5f7ccdea`) had already moved the
|
||||
outside-stage predicate's flood to walk VisitedCells — related plumbing,
|
||||
not aimed at this.
|
||||
|
||||
**Attempt 2 (the [dyn-route] trace + the root-flood gate — latest
|
||||
commit):** the trace (`[dyn-route]` lines, probe `ACDREAM_PROBE_WALK_ROOT`)
|
||||
proved: the LEGACY builder manufactures REAL 3–5-plane cell views for
|
||||
0x112 from sightline-less roots (`vis=1 cone=3+4 / 3+5` — NOT the
|
||||
zero-plane pass-all trapdoor), and routing flip-flops between
|
||||
`lookin-excluded` and DynamicLast as the observer moves. Fix shipped:
|
||||
`RenderScenePViewBuildInput.RootFloodCells` (the walk's
|
||||
`InteriorFloodCells` as a set) — an interior-parented dynamic rides
|
||||
DynamicLast ONLY if its parent is in the walk's own root flood; non-walk
|
||||
frames fall back to the legacy drawableCells meaning
|
||||
(`BuildAndCompare` default likewise). Verified working in the post-fix
|
||||
log: every 0x112 dynamic now logs `rootflood-excluded`.
|
||||
|
||||
**AND THE PLAYER STILL RENDERS.** Post-fix log
|
||||
(`%LOCALAPPDATA%\Temp\fw4-rootflood-gate.log`): 0x112 dynamics show BOTH
|
||||
`rootflood-excluded` AND `lookin-excluded` states — meaning the remaining
|
||||
draw path is the **LookInObject route**: `DrawBuildingLookInDynamics`
|
||||
(RetailPViewRenderer, runs inside `DrawLandscapeDynamicsPhase`, which for
|
||||
interior roots runs in the pre-clear closure) draws look-in-cell dynamics
|
||||
from the OLD apparatus's look-in frames (`_lookInFrames`, built by
|
||||
`BuildInteriorRootLookIns` / `MergeNearbyBuildingFloods`) — **the very
|
||||
frames whose invented visibility the trace exposed**. Two doors let him
|
||||
through; only DynamicLast was closed.
|
||||
|
||||
**Leading hypothesis for the next session (UNVERIFIED — trace first):**
|
||||
the old look-in machinery not only draws him with invented clip regions,
|
||||
it also PUNCHES far-Z apertures for its invented frames
|
||||
(`DrawLookInPortalPunch`, forceFarZ) — erasing true wall depth exactly
|
||||
where the invented aperture sits, so the pre-clear-drawn player passes
|
||||
depth there. Check also whether look-in DYNAMICS draw before/after wall
|
||||
shells and with which clip slots.
|
||||
|
||||
**The retail-shaped fix direction (synthesis steps 4+7 completed
|
||||
properly):** look-in dynamics must be keyed to THE WALK's look-in cells
|
||||
(the walk's `VisitedCells − InteriorFloodCells` per frame, or better: a
|
||||
per-look-in walk turn like `StaticParticles`), and the old apparatus's
|
||||
look-in frames/punches must lose their production role (FW4's deletion
|
||||
list). An interim honest gate: `DrawBuildingLookInDynamics` skips cells
|
||||
the WALK did not visit as look-ins this frame — that alone should kill
|
||||
the invented-frame draws while keeping legitimate look-ins. Do NOT
|
||||
band-aid at the symptom (no-workarounds rule); the trace must first
|
||||
confirm the punch/route hypothesis: extend `[dyn-route]`-style logging
|
||||
into `DrawBuildingLookInDynamics` (which cells, which frames, whether
|
||||
0x112 appears, and whether look-in punches fire for it).
|
||||
|
||||
## Also open (diagnosed, unfixed, in priority order after the above)
|
||||
|
||||
1. **The stalks** — dark shapes at the east cliff = DRAWN never-drawn
|
||||
marker anchors (GfxObj 0x010001EC, DIDDegrade 0x1100002E). The retail
|
||||
degrade-admission port exists as `fce250ec` on the QUARANTINED branch
|
||||
`claude/cathedral-rendering-debug-47a417` — NOT an ancestor of this
|
||||
branch. Re-land it (synthesis step 10).
|
||||
2. FW1 foundry F67–79 residue (needs an owner retail cdb session).
|
||||
3. Remaining synthesis Phase II items: seals from walk views (step 6),
|
||||
full-flush DrawBuilding barrier + FIFO no-sort alpha queue (step 8),
|
||||
terrain in_view culling for interior roots (step 9 — slice 6 currently
|
||||
uses the full camera frustum), then FW4's deletions, FW5 camera, FW6.
|
||||
4. Walk-path alloc tail ~1.5 MB/frame (FW6 hygiene).
|
||||
|
||||
## Instrumentation available (all under ACDREAM_PROBE_WALK_ROOT=1)
|
||||
|
||||
`[walk-root]` (continuous rooting: root flood vs visited split, walk ov),
|
||||
`[walk-cam]` (replayable cameras), `[walk-portal]` (per-portal side/clip),
|
||||
`[walk-part]`/`[walk-alpha]` (submission routes + drains with pre/post
|
||||
phase tags), `[walk-dyn]` (outside-stage verdicts), `[dyn-route]`
|
||||
(DynamicLast admission, on-change), `[falls]`/`[falls-adm]` (falls owner
|
||||
watch 0xCF418000-13 + admission). Documented in `docs/launch-options.md`.
|
||||
RenderDoc 1.45 is at `%LOCALAPPDATA%\Programs\RenderDoc_1.45_64\`
|
||||
(key-triggered capture does NOT work over this RDP session — use the
|
||||
target-control python API to trigger, or the in-app overlay locally; an
|
||||
old acdream capture exists at `%LOCALAPPDATA%\Temp\RenderDoc\`).
|
||||
|
||||
## Binding rules for the successor (hard-learned tonight)
|
||||
|
||||
1. **Never identify render content from aggregate owner sets** — trace
|
||||
ids to the DAT (`0x4…`=interior allocator, `0xC…`=landblock statics,
|
||||
`0x8…`=scenery, raw=server entities).
|
||||
2. **Phase-level probes cannot see position-within-phase** — order inside
|
||||
a stage is its own dimension.
|
||||
3. **The owner's old-pipeline fix history is an oracle** — check
|
||||
`e102fb36`-era work (`C:\Users\erikn\.codex\worktrees\16ee\acdream`,
|
||||
`docs/research/2026-08-29-cathedral-seam-progress.md`) before
|
||||
re-deriving.
|
||||
4. **Ghidra arbitration is mandatory for BN branch senses** (multiple
|
||||
misreads; MCP on port 8081, patchmem.gpr must be open).
|
||||
5. The oracle traces (`docs/research/2026-08-30-fw-walk-oracle/`,
|
||||
`posed/` = the fixture set) are frame truth; `posed/terrace-center` =
|
||||
DI f4180104, flood n=1, ov=1.
|
||||
6. One fix at a time, trace-pinned first, owner-gated after — six blind
|
||||
rounds preceded tonight's three verified fixes.
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
# Cathedral review — code + probe-log audit (companion report)
|
||||
|
||||
Delivered 2026-08-30 by the code/log audit agent (separate from the
|
||||
fable/opus mechanism reviews; this one audited the six commits, the exact
|
||||
HEAD frame flow, and every `fw3-*`/`fw4-*` probe log of the day). The full
|
||||
text below is verbatim from the agent's report.
|
||||
|
||||
## Executive synthesis
|
||||
|
||||
1. All six FW4 slices behave as written on frames where the walk yields
|
||||
exit views — the post-slice-6 log shows the falls emitting pre-clear
|
||||
and draining over full-screen terrain depth on those frames.
|
||||
2. Two defect mechanisms visible in the FINAL log are untouched by all
|
||||
six commits:
|
||||
- **Anomaly 1 — `ov==0` frames:** 66 of 26,601 interior frames at the
|
||||
terrace produced ZERO walk exit views → `RetailFrameWalk.DrawInside`
|
||||
skips `DrawLandscape` entirely → no terrain color, no terrain depth,
|
||||
no sky that frame. Everything drawn that frame (pre-clear alpha
|
||||
included — 17 frames had the falls straddler set drain against an
|
||||
EMPTY depth buffer) composites over stale color. These are the seam
|
||||
frames. Slices 1–5 fixed staging; slice 6 fixed clipping WHEN the
|
||||
landscape turn runs; nothing addressed the no-views case.
|
||||
- **Anomaly 2 — cell `0xF418010D` never floods:** the connecting
|
||||
portal from `f418010c` classifies `inview=1` (a FACING SURFACE, not
|
||||
an armed opening) at the terrace viewpoint (`[walk-portal] init
|
||||
cell=f418010c i=1 dest=f418010d d=-23..-50 pside=0 inview=1`), so
|
||||
the walk never traverses into it: never sealed, always fails the
|
||||
outside-stage flood gate (`outside=0` in every sampled `[walk-dyn]`
|
||||
line since the probe existed), and its r≈10 m weenies (the STALKS:
|
||||
f428a/f428b etc.) ride DynamicLast — post-clear meshes that z-pass
|
||||
over every vista pixel no seal covers, on EVERY interior frame.
|
||||
3. Combined with the fable review's oracle-trace finding (retail has ZERO
|
||||
interior-rooted frames at the terrace — `terrace-center.log` and
|
||||
`terrace-edge.log` contain no `DI` lines at all; the whole cathedral
|
||||
arrives as `ov=0 n=1` building look-ins under an OUTDOOR root), the
|
||||
remaining user-visible defects reduce to:
|
||||
- ROOT DIVERGENCE A: our camera-cell resolution roots INTERIOR at the
|
||||
terrace where retail roots OUTDOOR. An outdoor root has no clear, no
|
||||
seals, no ov==0 case — the entire problem class does not exist in
|
||||
retail's frame shape at these poses.
|
||||
- ROOT DIVERGENCE B: the walk's portal side classification at
|
||||
`f418010c → f418010d` (facing-surface vs opening — `PortalSide` vs
|
||||
the sign of `d`, `WalkPView.cs:103-120`) — the same family as the
|
||||
FW1 foundry F67–79 residue.
|
||||
4. UNVERIFIED (flagged, not papered over): whether the old apparatus's
|
||||
flood contains f418010d (needed for the stalk meshes' DynamicLast
|
||||
viewcone admission); the exact dat portal/panel geometry behind the 66
|
||||
`ov==0` frames; the color-attachment load behavior behind slice 6's
|
||||
"stale color" claim.
|
||||
|
||||
## Full audit text
|
||||
|
||||
### PART 1 — The six same-day commits (times: 18:21 / 19:22 / 19:28 / 19:37 / 19:58 / 20:06)
|
||||
|
||||
- `37febd1f` slice 1: interior outside-view slices from the walk
|
||||
(`ClipFrameAssembler.ReassembleOutsideViewFromWalk`; PrepareClipFrame
|
||||
moved below the walk block; Landscape event carries ViewCount; the
|
||||
per-view TerrainSlice fan; `activeTerrainSliceCount` deleted;
|
||||
`ACDREAM_PROBE_WALK_ROOT` added). 4 new materializer tests.
|
||||
- `c40aecfc` slice 2: exit seals stamp the walk's own flood
|
||||
(`WalkFrameDriver.InteriorFloodCells`; `DrawWalkExitPortalMasks` — the
|
||||
walk decides WHICH cells seal, the OLD assembly still decides the
|
||||
per-cell CLIP, unclipped fallback). Phase-tag probe apparatus added.
|
||||
No test changes.
|
||||
- `5f7ccdea` slice 3: `outsideStageFlood = walkDriver.VisitedCells`
|
||||
passed to BOTH `DynamicDrawsInOutsideStage` call sites (renderer +
|
||||
frame product). `[walk-dyn]` probe. No test changes.
|
||||
- `f3a03efc` slice 4: interior roots keep outside-stage owners
|
||||
(straddlers included) in the pre-clear union, record them in
|
||||
`_preClearParticleOwnerScratch`, both DrawDynamicsLast arms subtract.
|
||||
Outdoor keeps ExceptRoute(DynamicLast). No test changes.
|
||||
- `0d6cd5c0` slice 5: the falls re-identified as EnvCell stab statics;
|
||||
`SphereStraddlesExitPlane` extracted; straddling flood-cell statics'
|
||||
owners submit pre-clear, post-replay cell union subtracts.
|
||||
`[walk-emit]`/`[walk-portal]` probes. No test changes.
|
||||
- `bb9212c7` slice 6: one TerrainSlice(0) fan; interior roots draw
|
||||
terrain+sky UNCLIPPED (view-culled only); outdoor keeps the sliced
|
||||
path. Latent: the bounds check at `WalkLeaf.cs:81-86` still throws if
|
||||
the reassembly yields 0 slices while a Landscape turn fired. No test
|
||||
changes.
|
||||
|
||||
Test posture: slice 1 shipped tests; slices 2–6 shipped zero test
|
||||
changes (probe + production only).
|
||||
|
||||
### PART 2 — HEAD frame flow for an interior-rooted production frame
|
||||
|
||||
Setup: BeginFrame (phase:=pre) → OLD PortalVisibilityBuilder.Build →
|
||||
BuildInteriorRootLookIns (old apparatus) → AssembleClipFrame +
|
||||
AppendLookInClipFrames (old per-cell slices) → walk Collect (no GPU;
|
||||
flood → optional Landscape turn iff `OutsideView.ViewCount > 0` →
|
||||
building turns → OnInteriorFloodDrawTurn: Clear/Seals events +
|
||||
InteriorFloodCells + flood cell turns) → ReassembleOutsideViewFromWalk →
|
||||
the ONE PrepareClipFrame → prepareCells = old ∪ look-in ∪ walk →
|
||||
ViewconeCuller.Build (outside planes = walk-derived; per-cell planes =
|
||||
OLD) → BuildAndBorrow with outsideStageFlood = walk VisitedCells →
|
||||
routes: LookInObject (old-assembly slices), LandscapeOutsideDynamic
|
||||
(walk slices + cone), DynamicLast (indoor dynamics; viewcone from OLD
|
||||
per-cell planes — a cell with NO plane set returns false).
|
||||
|
||||
Replay (pre-clear only when a Landscape turn recorded): Sky (unclipped,
|
||||
interior) → TerrainSlice(0) (unclipped, depth write ON) → interleaved
|
||||
static/building/look-in mesh ranges + AlphaBarrier partial drains +
|
||||
punch fans (stencil far-Z, clipped by the walk view pinned at emission)
|
||||
→ ClearInteriorDepth event → closure: [static-owner particles +
|
||||
slice-5 straddler statics + DrawLandscapeDynamicsPhase (EARLY-OUTS
|
||||
ENTIRELY if OutsideViewSlices.Length == 0) ] → FlushLandscapeAlpha (the
|
||||
pre-clear drain, full far→near over true landscape depth) →
|
||||
ClearInteriorDepth (depth aspect only, color preserved; phase:=post) →
|
||||
ExitSeals (walk InteriorFloodCells × old-assembly clips; TRUE depth,
|
||||
ALWAYS+write, no color; cells not in the walk flood never seal) →
|
||||
flood CellShell + statics (the interior repaint).
|
||||
|
||||
Post-replay: cell-owner particles (walk flood minus look-ins minus
|
||||
straddlers; post, deferred) → DrawDynamicsLast (post-clear meshes,
|
||||
old-plane viewcone; particles minus pre-set) → unattached-interior →
|
||||
MarkVisibleCells(old ∪ look-in ∪ walk) → final alpha EndFrame drain
|
||||
(far→near, depth LESS/no write vs clear+seal+repaint depth) →
|
||||
ParticleVisibilityController admission = the union of both apparatuses.
|
||||
|
||||
Structural observations: the walk decides the seal CELL SET, the OLD
|
||||
assembly still decides seal clips, look-in slices, and the DynamicLast
|
||||
per-cell viewcone; the walk's thin exit views still gate all dynamics
|
||||
staging; if ViewCount == 0 no Landscape turn fires at all while clear +
|
||||
seals + repaint still run.
|
||||
|
||||
### PART 3 — Probe-log findings
|
||||
|
||||
Log-era map: `fw3-walk-root-probe.log` 18:09 = pre-slice-1 AND NOT the
|
||||
cathedral (all roots 0xA9B4… — the stairwell investigation);
|
||||
`fw3-falls-probe.log` 19:23 ≈ slice-2; `fw4-seal-gate` 19:27;
|
||||
`fw4-slice3-gate` 19:32; `fw4-slice4-gate` 19:44; `fw4-portal-probe`
|
||||
19:50 (slice-4 + uncommitted slice-5 probes); `fw4-slice5-gate` ≈19:59;
|
||||
`fw4-slice6-gate` 20:23 = the ONLY post-slice-6 run.
|
||||
|
||||
- The falls emitters are `4f418012..15` + `f4180106` (InteriorEntityId-
|
||||
Allocator statics of cell 0xF4180106), riding a per-look-in-cell
|
||||
`DrawCellParticles` union; `viewer=0xF4180106` appears in ZERO
|
||||
[walk-root] lines (the 8-frame post-flip sampler never caught it).
|
||||
- Post-slice-6 frames WITH exit views: falls pre-clear, no
|
||||
cell-owners/dyn-owners post — the staging repairs work as written.
|
||||
- ANOMALY 1: 66/26,601 interior frames with NO landscape turn (no
|
||||
SkyPreScene, no outdoor static-owners, no unattached-outdoor) — the
|
||||
ov==0 shape; all 66 carry `dyn-owners n=2 ids=[f4243,f4287]
|
||||
phase=post`, and 17 carry a pre-clear falls drain against EMPTY depth.
|
||||
- ANOMALY 2: cell f418010d in ZERO walkCells lists across all 7
|
||||
cathedral logs; `init cell=f418010c i=1 dest=f418010d d=-23..-50
|
||||
pside=0 inview=1`; its dynamics f428a/f428b always `outside=0`; their
|
||||
particles appear in NO dyn-owners set — the visible stalks are the
|
||||
MESHES, drawn post-clear.
|
||||
- ANOMALY 3: 17 seam FLIPs at nearly identical eye positions
|
||||
(interior 0xF4180104 eye=(31.09,61.34,172.05) vs outdoor 0xF418000B
|
||||
eye=(30.90,63.04,172.05)).
|
||||
- ANOMALY 4: `[walk-emit]` empty in ALL logs — the interior-unattached
|
||||
route carried no emitters (the falls are attached statics).
|
||||
- ANOMALY 5: the surviving exit views are thin (n=4 clips on portals
|
||||
j=1/2/4; j=0 often n=0) — consistent with the authored polys being
|
||||
narrow bands.
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
# Cathedral review — DAT-format facts (companion report)
|
||||
|
||||
Delivered 2026-08-30 by the DAT-format verification agent (decompiled the
|
||||
actual `Chorizite.DatReaderWriter 2.1.7` NuGet binary with ilspycmd and
|
||||
cross-checked against `docs/research/named-retail/acclient.h`). Key facts
|
||||
for the cathedral synthesis; flagged caveats preserved.
|
||||
|
||||
## Sourcing caveats
|
||||
|
||||
- Our DAT parser is the CONSUMED NuGet `Chorizite.DatReaderWriter 2.1.7`
|
||||
(no vendored source; facts below come from decompiling the exact linked
|
||||
DLL).
|
||||
- `references/WorldBuilder` is an uninitialized submodule in THIS worktree
|
||||
(read from the main worktree at a different SHA — flagged).
|
||||
- `references/ACViewer` does not exist in either worktree (claims about
|
||||
ACViewer: UNVERIFIED).
|
||||
|
||||
## EnvCell record (read order, decompiled Unpack)
|
||||
|
||||
Flags (int) → self-Id (uint, duplicate) → NumSurfaces (byte) → NumPortals
|
||||
(byte) → NumVisibleCells (ushort) → Surfaces[](ushort) → EnvironmentId
|
||||
(ushort) → CellStructure (ushort) → Position (Frame) → CellPortals[] →
|
||||
VisibleCells[] (ushort) → [HasStaticObjs 0x2] StaticObjects (count +
|
||||
Stab{Id,Frame}[]) → [HasRestrictionObj 0x8] RestrictionObj.
|
||||
|
||||
`EnvCellFlags`: SeenOutside=0x1, HasStaticObjs=0x2, HasRestrictionObj=0x8.
|
||||
**0x4 is unclaimed.**
|
||||
|
||||
Retail cross-check: `CEnvCell : CObjCell` (acclient.h:32072-32091) maps
|
||||
1:1; retail's `stab_list` lives on the BASE `CObjCell`
|
||||
(acclient.h:30914-30932) and is the **PVS list of CELL IDS** — NOT placed
|
||||
objects. The DatReaderWriter `Stab{Id,Frame}` placed-object struct is a
|
||||
coincidental homonym (zero matches for `Stab` in the named-retail header).
|
||||
|
||||
## CellPortal / BuildingPortal
|
||||
|
||||
`CellPortal { PortalFlags Flags; ushort PolygonId; ushort OtherCellId;
|
||||
ushort OtherPortalId; }` — `PortalFlags: ExactMatch=1, PortalSide=2`.
|
||||
**No 0x4 member anywhere** (package, our code, WorldBuilder, and retail's
|
||||
own `CCellPortal` decode exactly two semantic fields: portal_side,
|
||||
exact_match). NOTE: this contradicts the earlier campaign note
|
||||
"CCellPortal DAT flag 0x4 = leads outside" — the outside link is the
|
||||
`OtherCellId == 0xFFFF` sentinel, not a flag bit.
|
||||
|
||||
- ExactMatch consumers: `PortalVisibilityBuilder.cs:1168-1183` (skips
|
||||
reciprocal clip — decomp:433689), Walk factories
|
||||
(`WalkBuildingFactory.cs:76`, `WalkCellFactory.cs:94`).
|
||||
- PortalSide convention: `(Flags & 0x2) == 0 → normal points INTO the
|
||||
owning cell` (`CellPortal.cs:23-24`, `PortalInfo.cs:22-44`);
|
||||
`WalkCellFactory.cs:93`: `PortalSide = (Flags & 0x2) != 0 ? 0 : 1`.
|
||||
- `OtherCellId == 0xFFFF` = the outdoor-exit sentinel (18+ consumers);
|
||||
the walk world remaps it to `0xFFFFFFFFu`.
|
||||
|
||||
`BuildingPortal` (BuildingInfo.Portals) is a DISTINCT dat type — carries
|
||||
its own **packed StabList of cell ids** (retail `CBldPortal` with
|
||||
num_stabs/stab_list + `sidedness`). The walk's `WalkBuildingFactory.cs:81`
|
||||
reads it directly; the older `Wb.BuildingLoader` path DISCARDS it and
|
||||
recomputes by BFS.
|
||||
|
||||
## LandBlockInfo
|
||||
|
||||
`NumCells → Objects[Stab] → Buildings[BuildingInfo{ModelId, Frame,
|
||||
NumLeaves, Portals[BuildingPortal]}] → [flag] RestrictionTable`.
|
||||
Confirmed field-for-field; consumer `LandblockLoader.cs:69-103`.
|
||||
|
||||
## Water / waterfall (part d)
|
||||
|
||||
- `LandDefs::WaterType` is COMPUTED client-side from terrain paint (types
|
||||
0x10-0x14 WaterRunning..WaterDeepSea); AcDream already computes it for
|
||||
physics (`TerrainSurface.cs:92-128`). It is NOT packed in the records.
|
||||
- `SurfaceTexture.Textures` is a multi-frame list (the structural shape of
|
||||
a flip-book animation) but EVERY consumer in both repos freezes one
|
||||
fixed index (`Textures[0]`, WB `[^1]`) — **no water/texture animation is
|
||||
implemented anywhere**; whether retail animates via this list is
|
||||
UNVERIFIED.
|
||||
- Which object IS "the waterfall" near the cathedral: UNVERIFIED from
|
||||
source — two old bug comments treat it as OUTDOOR landscape-alpha
|
||||
content (`PortalVisibilityBuilder.cs:730-736`,
|
||||
`RetailPViewRenderer.cs:714-726`), consistent with a translucent stab —
|
||||
but note the live probes identified the terrace falls PARTICLE owners as
|
||||
EnvCell StaticObjects of cell 0xF4180106 (ids 4f418012-15), which is
|
||||
the `HasStaticObjs` list above; both can be true (mesh stab outdoors +
|
||||
emitter stabs in cells).
|
||||
|
||||
## Notable corrections this report makes to campaign lore
|
||||
|
||||
1. "DAT flag 0x4 = leads outside" (PV campaign memory) — REFUTED: no 0x4
|
||||
exists; outside = OtherCellId 0xFFFF.
|
||||
2. "stab list" ambiguity — retail stab_list = PVS cell ids on CObjCell;
|
||||
placed objects are a different structure; docs/comments conflating the
|
||||
two should be read carefully.
|
||||
3. Building portals carry authored per-portal cell StabLists in the dat —
|
||||
the old Wb path recomputes what the dat already provides.
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
# Cathedral review — F418 authored DAT geometry (companion report)
|
||||
|
||||
Delivered 2026-08-30 by the DAT-geometry agent: fresh byte-exact parsers
|
||||
over the installed `client_cell_1.dat`/`client_portal.dat` (every record
|
||||
parsed with 0 bytes left over). Ground truth for the synthesis. Condensed;
|
||||
citations and caveats preserved from the agent's full text.
|
||||
|
||||
## The landblock (LandblockInfo 0xF418FFFE)
|
||||
|
||||
24 EnvCells (0xF4180100–0x0117), 88 objects, 9 buildings. The cathedral
|
||||
complex is authored in the BUILDINGS list: 0x01001FB0 (north hall, cells
|
||||
0x100–0x102), 0x01001FB2 (the 0x103–0x105 colonnade block), 0x01001FB3
|
||||
(the main cathedral, cells 0x106–0x111, 13 building portals), 0x01001FB7
|
||||
(south hall 0x112–0x115), 0x01001FD3 (sealed annex 0x116–0x117), plus
|
||||
four portal-less shells.
|
||||
|
||||
## THE FALLS — identity corrected (slices 4/5 chased the wrong owners)
|
||||
|
||||
The waterfall emitters are **LANDBLOCK OBJECT-LIST STABS** (outdoor), not
|
||||
EnvCell statics and not weenies:
|
||||
- Objects[0..6]: Setup **0x0200085A** ×7 — EAST falls jets (x≈169–170,
|
||||
z=158.6), script 0x330008A6 → emitter 0x320004A4 (v=22.5 m/s jets).
|
||||
- Objects[7..13]: Setup **0x02000859** ×7 — NORTH falls jets (y≈120.8),
|
||||
script 0x330008A5 → emitters 0x320004A3/A2 (arc + plunge, accel −40).
|
||||
- Objects[14..19]: Setup **0x020003C5** ×6 — lake-base mist at z=99.9
|
||||
(incl. an authored exact-duplicate pair).
|
||||
Their runtime ids live in the **0xC namespace**
|
||||
(`LandblockStaticEntityIdAllocator`): east 0xCF418000–06, north
|
||||
0xCF418007–0D, mist 0xCF41800E–13.
|
||||
|
||||
**The probe-observed `0x4F418012–15` owners are NOT the falls** — the 0x4
|
||||
namespace is `InteriorEntityIdAllocator`; simulating the hydration filter
|
||||
maps them to cell 0xF4180106's OWN stabs: **two 0x020005C1 TORCHES**
|
||||
(flame emitters 0x320003AB/0x320003A5), one bare prop (0x01000DF0), one
|
||||
plant (0x020007A3). Slice 5's "the falls are EnvCell stab statics" was a
|
||||
misidentification; the `cell-owners n=5` submissions were the torches.
|
||||
|
||||
The falls billboards' degrade tables keep them visible to **392 m** —
|
||||
they are authored to be seen across the whole vista.
|
||||
|
||||
## THE "STALKS" — premise corrected
|
||||
|
||||
Setup 0x0200085A (the PV campaign's "monster stalks") is **not a visible
|
||||
stalk**: its sole part GfxObj 0x010001EC is retail's runtime-hidden
|
||||
marker (GfxObj flags 0xA; DIDDegrade 0x1100002E = never drawn at any
|
||||
distance), shared as the invisible anchor by 13 of this landblock's
|
||||
setups (falls jets, torch/sound anchors). The visible dark shapes at the
|
||||
east cliff are these anchors DRAWN — i.e., a never-drawn-degrade skip
|
||||
missing on the path that renders them (the marker-degrade port fce250ec
|
||||
is stranded on the quarantined PV branch per the audit report). Cell
|
||||
0xF418010D (the audit's stalk suspect) is actually a fully-interior
|
||||
undercroft/crypt with 81 candle-prop stabs and NO exit portals — its
|
||||
non-flooding is CORRECT behavior, not the stalk mechanism.
|
||||
|
||||
## EXIT-PORTAL GEOMETRY — the "thin seam bands" claim REFUTED
|
||||
|
||||
All exit portals at the defect cells are FULL-SIZE authored openings with
|
||||
sills at the cell floor (z=169.80):
|
||||
- 0xF4180104: four exits — two 10.72 × 10.20 m facade trapezoids (y=48
|
||||
south, y=72 north) and two 24.0 × 9.2 m side ARCHES (x≈31 west, x≈41
|
||||
east; the 0.57 m X-thickness in an AABB is just the arch bow, the polys
|
||||
stand in the Y-Z plane). One interior ceiling portal → 0x105.
|
||||
- 0xF4180106: one 10.72 × 10.20 m exit (y=48 north) + two 1.67 × 5.89 m
|
||||
door arches (→0x10F, →0x110).
|
||||
|
||||
Slice 6's "authored polys are thin bands far narrower than the real
|
||||
opening" is therefore WRONG about the authoring — if the walk's exit
|
||||
views come out thin or empty (the ov==0 frames), the loss happens in OUR
|
||||
clipping/traversal, not in the dat.
|
||||
|
||||
**Two real seam facts to carry instead:**
|
||||
1. **0x104's south exit poly and 0x106's north exit poly are COINCIDENT**
|
||||
— the same 10.72 × 10.20 m rectangle in the same y=48 plane (buildings
|
||||
01001FB2 and 01001FB3 abut), EACH marked exit-to-outside (0xFFFF)
|
||||
rather than cell-to-cell. This shared double-exit plane at y=48 is
|
||||
"the seam".
|
||||
2. The openings' sills sit ~9.8 m above the outdoor terrace (floor 169.8
|
||||
vs terrain 160): the lake (z≈100) only becomes visible within ~2–3 m
|
||||
of an opening — the downward vista is sill-clipped by geometry, not by
|
||||
portal truncation.
|
||||
|
||||
## Portal flags (from all 24 cells)
|
||||
|
||||
Bit 0x1=ExactMatch, 0x2=PortalSide, and **0x4 co-occurs always and only
|
||||
with other_cell==0xFFFF** (observed flags 0x5/0x7 on exits) — so the old
|
||||
"flag 0x4 = leads outside" lore is observationally right as a CORRELATE
|
||||
in this landblock even though the DatReaderWriter enum doesn't name it
|
||||
(the datformat report found no 0x4 member; semantic asserted from perfect
|
||||
correlation, not decomp).
|
||||
|
||||
## Water
|
||||
|
||||
The lake/falls SURFACES are outdoor TERRAIN paint: WaterShallowSea
|
||||
(type 18) in the north basin (z 96–108) and terrace water court (z=160),
|
||||
FauxWaterRunning (type 22) on the cliff faces. No water objects besides
|
||||
the six mist emitters.
|
||||
|
||||
## Cell 0xF4180104 / 0x0106 contents
|
||||
|
||||
- 0x104: 16 stabs — plants + FOUR looping ambient-SOUND anchors
|
||||
(0x0200093D/3E/3F/09AF — how the falls' roar is heard indoors; no
|
||||
particles) + prop.
|
||||
- 0x106: 5 stabs — the two torches, sound marker, prop, plant.
|
||||
- 0x105: a ceiling-linked upper cell (horizontal portals only).
|
||||
|
||||
## Caveats (the agent's own)
|
||||
|
||||
The 0x4F418012–15 mapping mirrors `LandblockBuildFactory`'s hydration
|
||||
filter in Python (high confidence, not executed C#); flag-0x4 semantics
|
||||
asserted from correlation; terrain-name indices aligned against ACE's
|
||||
LandDefs enum.
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
# Cathedral review — retail interior-root frame reconstruction (companion report)
|
||||
|
||||
Delivered 2026-08-30 by the decomp-reconstruction agent (BN pseudo-C +
|
||||
live Ghidra arbitration on 8081 + acclient.h + symbols.json). THE retail
|
||||
mechanism reference for the synthesis. Condensed; all pc:/address
|
||||
citations preserved.
|
||||
|
||||
## Frame skeleton (interior root)
|
||||
|
||||
1. `SceneTool::BeginScene` @0x0043dad0: **full Clear(7) — COLOR+depth+
|
||||
stencil — every frame** (pc:68673-68686). `Flip` bumps m_nFrameStamp
|
||||
at frame end (pc:428642).
|
||||
2. `RenderNormalMode` @0x00453aa0: interior camera → optional
|
||||
LScape::update_viewpoint (only when viewer_cell->seen_outside,
|
||||
pc:92667-71) → DrawInside(viewer_cell) → **unconditional final drain
|
||||
FlushAlphaList(0f)** (pc:92687).
|
||||
3. TWO PView singletons (pc:427800/427813): `indoor_pview` (draw_landscape
|
||||
=1) for the root; `outdoor_pview` (0) for building look-ins — look-in
|
||||
floods can never re-trigger landscape/clear/seals (their
|
||||
outside_view.view_count is always 0; ClipPortals never raises it when
|
||||
draw_landscape=0, pc:433666-84).
|
||||
4. `PView::DrawInside` @0x005a5860: push root view (full viewport) →
|
||||
`ConstructView(root, 0xffff)` — PURE CPU flood, draws nothing; exit
|
||||
portals merge clipped apertures into outside_view (pc:433662-76,
|
||||
cliplandscape=1 @0x00820f4c).
|
||||
|
||||
## PView::DrawCells @0x005a4840 — ALL of this inside `ov > 0` (pc:432715):
|
||||
|
||||
1. `Render::PortalList = &outside_view`; `LScape::draw` (pc:432718-19).
|
||||
2. **Full outdoor alpha drain** FlushAlphaList(0f) (pc:432720).
|
||||
3. `m_nFrameStamp += 1` (pc:432722) — re-arms part dedupe so a part can
|
||||
draw in BOTH the outdoor and interior passes of one frame (straddlers
|
||||
draw twice; "emit once" is anti-retail).
|
||||
4. **Gated z-only clear** (pc:432725-32, Ghidra-verified):
|
||||
`forceClear(const 0) || portalsDrawnCount != 0` — the counter is
|
||||
incremented ONLY by own-depth portal stamps (seals, arg2==0,
|
||||
pc:424569-70) → **a ONE-FRAME LATCH: last frame's seals arm this
|
||||
frame's clear.** Steady-state vista frames clear every frame; the
|
||||
FIRST such frame does not.
|
||||
5. **Exit seals** (pc:432737-807): per flooded cell (far→near), per view,
|
||||
EVERY portal with other_cell 0xffffffff:
|
||||
`DrawPortalPolyInternal(poly, 0)` — REAL projected depth,
|
||||
DEPTHTEST_ALWAYS + z-write ON (pc:424584), invisible (vertex alpha 0,
|
||||
pc:424686), CULL_NONE, CPU-clipped to the current view (pc:424577).
|
||||
(Sibling mode arg2==1 = the far-Z PUNCH z≈0.999999, look-ins only;
|
||||
does not increment the counter.)
|
||||
|
||||
Then UNCONDITIONALLY (even when ov==0):
|
||||
6. Lighting restore (pc:432811-12).
|
||||
7. **Cell shells** far→near: DrawEnvCell dedupes per frame stamp — the
|
||||
shell draws ONCE, WHOLE; the per-view loop is vestigial for it; no
|
||||
view planes applied; correctness = pure z-test (pc:427885-88).
|
||||
8. **Cell objects** far→near: PortalList = the cell's views →
|
||||
DrawObjCellForDummies → shadow parts sorted by CYpt (viewer distance
|
||||
to scaled sort_center, insertion_sort @pc:701156-75) →
|
||||
CPhysicsPart::Draw — statics, dynamics, AND particle-emitter parts
|
||||
through ONE machinery.
|
||||
9. Final drain (pc:92687) vs interior depth + seal-stamped apertures.
|
||||
|
||||
## Clip vs cull (the definitive table)
|
||||
|
||||
- CPU polygon-clipped: ONLY portal-chain polys during the flood
|
||||
(GetClip @0x005a4320) and seal/punch fans at draw (their own
|
||||
polyClipFinish vs the current view, pc:424577, + a ±12 local-XY trivial
|
||||
reject pc:424506-50).
|
||||
- Sphere-vs-view CULLED, whole-drawn: all meshes (statics, dynamics,
|
||||
particles' parts) — DrawMesh per view: viewconeCheck(sphere)
|
||||
(pc:429295-96); PARTIAL draws WHOLE under that view; a mesh in N views
|
||||
submits N times. Building PORTAL pass is cone-exempt (punches always
|
||||
attempted; GetClip <3 points is the real stop, pc:429302-10).
|
||||
- Cached-flag culled: terrain cells via in_view stamped by
|
||||
draw_check_blocks @0x00505f80 iterating EVERY view of PortalList
|
||||
(blocks wholly outside every exit view skip at pc:267936). Terrain
|
||||
polys then draw whole + immediate (landPolysDraw @0x006b7040 — no view
|
||||
clip, no alpha list).
|
||||
- No test at all: EnvCell shell geometry; seal z-behavior (ALWAYS).
|
||||
- `alwaysDrawObjects = 1` (@0x00820ed4): DrawSortCell runs for EVERY cell
|
||||
of a drawn block — the per-part per-view cone check is the real object
|
||||
gate (no pixel leak, just CPU).
|
||||
|
||||
## The alpha lists (retail)
|
||||
|
||||
TWO static FIFO lists, 3000 entries each (Clip @0x00871b10, Alpha
|
||||
@0x008af370). NO depth key stored, NO sort at flush — order = submission
|
||||
order; far-to-near correctness comes from the cell walk + per-cell CYpt
|
||||
insertion sort. Overflow DROPS the subset (pc:424983). Entry routing:
|
||||
s_AlphaDelayMask (default 0xE) vs the surface mask; bit3 → Clip list.
|
||||
|
||||
Flush sites (grep-complete):
|
||||
| # | Site | Arg | Depth in place |
|
||||
|---|------|-----|----------------|
|
||||
| 1 | DrawBuilding @0x0059f30b | 0f FULL | outdoor-so-far (pre-punch) |
|
||||
| 2 | DrawBlock @0x005a1a07 | 0.75 valve, fires per outdoor cell (BN's `flush>1` guard is a MISREAD — Ghidra: fires when flush<=1.0) | mid-landscape |
|
||||
| 3 | DrawCells @0x005a4872 | 0f FULL | complete outdoor depth (pre-clear) |
|
||||
| 4 | RenderNormalMode @0x00453b8b | 0f FULL (final) | interior + seals |
|
||||
| 5 | CreatureMode::Render (chargen only) | 0f | its own viewport |
|
||||
|
||||
## Depth at final drain (interior root)
|
||||
|
||||
- Aperture/vista pixels: the SEAL's portal-plane depth (outdoor per-pixel
|
||||
depth NO LONGER EXISTS — cleared; outdoor COLOR survives under the
|
||||
stamp). Interior alpha fragments behind the portal plane FAIL there.
|
||||
- Outdoor particles (falls): drawn in the landscape pass, drained at
|
||||
site #3 vs outdoor depth. Cone-PARTIAL spill outside the aperture is
|
||||
buried because EVERY interior pixel paints after every outdoor drain
|
||||
point — protection is pure ORDERING, not depth.
|
||||
|
||||
## ShouldDrawParticles @0x0050fe60 — an UPDATE-time gate
|
||||
|
||||
Called per emitter from ParticleEmitter::UpdateParticles (physics-side),
|
||||
NOT during rendering: `ExaminationObject → 1; CYpt > degrade_distance →
|
||||
0; cell NULL → 0; cell->IsInView()==0 → 0`. On false: SetNoDraw(1),
|
||||
degraded_out=1 (particles stop updating AND drawing).
|
||||
- CLandCell::IsInView @0x00532cb0 = the PREVIOUS frame's stamped in_view
|
||||
(one-frame-stale gate).
|
||||
- **CEnvCell does NOT override it** — the slot resolves to the ICF-folded
|
||||
constant @0x005269f0 `return 1` (PARTIALLY_INSIDE, PDB-verified): for
|
||||
interior emitters the cell test is VACUOUS; distance is the only
|
||||
update-time gate; their draw is flood-gated instead.
|
||||
|
||||
## Can content outside the flood/exit views draw? NO.
|
||||
|
||||
Interior: pure flood membership. Outdoor blocks/cells: in_view under ≥1
|
||||
exit view. Outdoor parts: per-exit-view sphere-cone in DrawMesh. Building
|
||||
portal pass cone-exempt but clip-stopped. No distance bypass exists.
|
||||
|
||||
## Corrections to campaign docs
|
||||
|
||||
1. DrawBlock per-cell flush polarity RESOLVED (fires; 0.75 pressure
|
||||
valve) — BN misread.
|
||||
2. The m_nFrameStamp bump separates the landscape and interior passes of
|
||||
ONE interior-root frame; look-ins NEVER bump (outdoor_pview ov==0).
|
||||
3. NEW: CEnvCell::IsInView = constant PARTIALLY_INSIDE (ICF fold,
|
||||
symbols.json 19-name cluster) — not in any FW doc.
|
||||
|
||||
## UNVERIFIED (the agent's own list)
|
||||
|
||||
The 0xF418 falls emitters' owner (land cell vs EnvCell static — dat
|
||||
question; note the DAT-geometry companion resolves this: landblock
|
||||
object-list stabs); z-write during alpha drains; insertion_sort
|
||||
direction byte-proof; the ±12 reject polarity; GameSky depth behavior.
|
||||
|
|
@ -1,436 +0,0 @@
|
|||
# Cathedral rendering review — FABLE (2026-08-30)
|
||||
|
||||
Independent report-only review of the cathedral-terrace defect (falls
|
||||
particles + marker stalks over terrain; landblock `0xF418`, camera in
|
||||
`0xF4180104`/`0xF4180106`), baseline `37febd1f`, HEAD `bb9212c7`, branch
|
||||
`claude/campaign-w-retail-frame-walk`.
|
||||
|
||||
**Evidence basis and honesty note.** Everything below marked with a citation
|
||||
was read directly by this reviewer (oracle trace files, decomp pseudocode
|
||||
docs + their byte-verification notes, commit messages/diffs, git topology,
|
||||
ISSUES/register/memory docs, id-allocator source). Three deeper evidence
|
||||
threads (fresh decomp re-reads of the alpha/particle path, a raw DAT parse
|
||||
of `0xF418`, and the HEAD code-flow + probe-log audit) were dispatched but
|
||||
had not returned when this report was due; every claim that depended on
|
||||
them is explicitly marked **unverified**. Per the prompt's rule, unverified
|
||||
is stated as unverified — nothing below is a plausible guess dressed as
|
||||
fact.
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive verdict (≤10 lines)
|
||||
|
||||
The six fixes failed because they accepted a false premise about WHERE this
|
||||
scene's content sits in retail's frame. The posed oracle traces show that at
|
||||
both defect poses retail's interior flood is EXACTLY ONE cell (`DC ov=1 n=1:
|
||||
f4180104` / `f4180106`) — every other cathedral cell, including the falls'
|
||||
stab-owner cells, draws as a *building look-in inside the landscape scope*,
|
||||
pre-depth-clear, its alpha drained against full landscape depth
|
||||
(`posed/terrace-center.log` F5, `posed/cathedral-arrival.log` F1-2;
|
||||
`PView::DrawCells` @0x005a4840 model §5). Our pipeline instead treats the
|
||||
whole complex as interior-flood content drawing post-clear, then tried to
|
||||
rescue it with seals (slice 2) and a non-retail exit-plane-straddle
|
||||
classifier (slices 4/5) that under-covers exactly the fully-outside falls
|
||||
emitters. Fix = route every object's particles to its owner cell's actual
|
||||
walk turn (look-in ⇒ landscape scope ⇒ pre-clear drain), delete the straddle
|
||||
classifier, and re-land the quarantined marker-degrade port for the stalks.
|
||||
|
||||
---
|
||||
|
||||
## 2. Retail frame reconstruction (for an interior root with exit portals)
|
||||
|
||||
### 2.1 The frame skeleton — verified from the FW0 model + live traces
|
||||
|
||||
Source of truth used: `docs/research/2026-08-30-fw-walk-pseudocode.md`
|
||||
(§1, §2, §3, §5, §9 — its seal loop, deferred z-clear, DEPTHTEST_ALWAYS,
|
||||
punch-Z constants are recorded there as byte-verified against the raw
|
||||
pseudo-C at `@0059a49af`, `@005a48a9`, `@0059be02`, `@0059bf4b`), the
|
||||
extraction appendix `2026-08-30-fw-walk-pseudocode-appendix.md` (notably
|
||||
the DrawBuilding/DrawMeshInternal full-sequence note at appendix line 811),
|
||||
and the live cdb traces in `docs/research/2026-08-30-fw-walk-oracle/posed/`.
|
||||
|
||||
Order of one interior-rooted frame (`SmartBox::RenderNormalMode`
|
||||
@0x00453aa0 → `PView::DrawInside` @0x005a5860 → `ConstructView` →
|
||||
`PView::DrawCells` @0x005a4840):
|
||||
|
||||
1. **Flood** (`ConstructView(CEnvCell)` @0x005a57b0): view-driven — a
|
||||
neighbor cell enters `cell_draw_list` only if its portal polygon
|
||||
survives `PView::GetClip` @0x005a4320 against the current view (the ONLY
|
||||
CPU polygon clip in the frame). Exit portals
|
||||
(`other_cell_id == 0xFFFFFFFF`) raise `outside_view`.
|
||||
2. **If `outside_view.view_count > 0`** (the traces' `ov`):
|
||||
a. `Render::PortalList = &outside_view`; **`LScape::draw`** @0x00506330
|
||||
— the whole landscape walk THROUGH the exit views: sky, terrain
|
||||
blocks far-to-near (view-CULLED per cell/block by
|
||||
`draw_check_blocks`' in_view union — **never view-clipped, never
|
||||
scissored**; `set_view` installs NO GPU state), buildings far-to-near,
|
||||
and — per building — the **look-in machinery**:
|
||||
`DrawBuilding` @0x0059f2a0 → BSP portal pass 1 punches apertures
|
||||
(`DrawPortalPolyInternal` flag=1: far-Z 0.999998987, DEPTHTEST_ALWAYS,
|
||||
z-write ON, alpha 0) → pass 2 recurses `ConstructView(CEnvCell)` +
|
||||
`DrawCells` (**`ov=0` — no landscape, no clear, no seals**) drawing
|
||||
look-in EnvCells + their objects into the punched aperture
|
||||
(appendix line 811). `DrawBuilding` itself calls
|
||||
**`FlushAlphaList(0f)`** before each building; `DrawBlock`
|
||||
@0x005a17c0 has the mid-walk pressure valve
|
||||
`FlushAlphaList(flush=0.75)` (appendix lines 700, 1027).
|
||||
b. **`FlushAlphaList(0f)`** — the pre-clear boundary drain: EVERYTHING
|
||||
queued during the landscape scope (terrain alpha, building alpha,
|
||||
look-in cell content alpha — and the particle polys of objects drawn
|
||||
in that scope) drains against **full landscape depth**. This is the
|
||||
drain that occludes the falls with terrain/water/trees.
|
||||
c. **Gated depth clear** (`if (forceClear || portalsDrawnCount != 0)
|
||||
Clear(z)`, model §5; `portalsDrawnCount` counts ONLY flag-0 seals —
|
||||
appendix line 811). Color is preserved; only depth clears.
|
||||
d. **Exit seals**: for the FLOODED cells' exit portals only,
|
||||
`DrawPortalPolyInternal(poly, 0)` — the portal's own projected depth,
|
||||
DEPTHTEST_ALWAYS, z-write ON, invisible (model §2, §5; byte-verified
|
||||
per model §9).
|
||||
3. **Interior passes** (post-clear): `DrawEnvCell` far-to-near (BSP shells),
|
||||
then `DrawObjCellForDummies` (the flooded cells' objects) — depth
|
||||
accumulates fresh.
|
||||
4. **Final drain**: `FlushAlphaList(0f)` back in `RenderNormalMode` — the
|
||||
flooded cells' alpha/particles drain against interior depth + the seals.
|
||||
|
||||
### 2.2 The four prompt questions
|
||||
|
||||
**(a) clipped vs culled vs whole.** CPU-clipped: ONLY the portal chain's own
|
||||
polygon (`GetClip` @0x005a4320 — the model calls it "the only CPU polygon
|
||||
clip in the frame"). View-culled (coarse sphere-vs-cone,
|
||||
`Render::viewconeCheck` @0x0054c250, PARTIAL draws whole): objects.
|
||||
Cell/block-granular culled (in_view interval grids): terrain blocks/cells.
|
||||
Drawn whole with no per-view test: everything that passes those gates —
|
||||
shells, meshes, particles. No GPU clip planes/scissor exist anywhere in this
|
||||
frame (`set_view` = CPU globals only, model §6).
|
||||
|
||||
**(b) order relative to clear and seals.** PRE-clear: sky, terrain, water
|
||||
(terrain-hosted — see §3, unverified detail), building shells, building
|
||||
punches, **look-in EnvCell shells + look-in cell objects + their
|
||||
emitters**, and all their alpha via the boundary drain. POST-clear: seals
|
||||
first, then ONLY the root flood's EnvCell shells + objects + their
|
||||
emitters, drained at the final flush. Seals draw exactly the flooded
|
||||
cells' exit-portal polygons at own depth, color-write effectively off
|
||||
(alpha 0), depth ALWAYS + write ON.
|
||||
|
||||
**(c) alpha list.** ONE list. Flush sites established from the docs:
|
||||
per-building `FlushAlphaList(0f)` in `DrawBuilding` (appendix 811); the
|
||||
`DrawBlock` pressure valve at `flush=0.75` (~2250 meshes, appendix 1032);
|
||||
the `DrawCells` pre-clear boundary `FlushAlphaList(0f)` (model §5); the
|
||||
`RenderNormalMode` end-of-frame `FlushAlphaList(0f)` (model §1). Depth in
|
||||
place: landscape depth at the first three; interior + seal depth at the
|
||||
final one. **Unverified:** an exhaustive caller enumeration of
|
||||
`D3DPolyRender::FlushAlphaList` @0x0059d2e0 from the decomp itself, and
|
||||
the exact particle-poly insertion path (the dispatched decomp read had not
|
||||
returned; the MP2-era digest additionally records
|
||||
`CPhysicsObj::ShouldDrawParticles` @0x0050FE60 = authored degrade range +
|
||||
`cell->IsInView()`, from `claude-memory/project_render_pipeline_digest.md`).
|
||||
|
||||
**(d) depth at final-drain time.** Aperture pixels: the seal's own depth
|
||||
(near the camera) — anything beyond the exits z-fails, so interior
|
||||
particles cannot repaint the vista. Vista pixels visible through the
|
||||
aperture ARE aperture pixels (the opening's polys); pixels covered by
|
||||
interior geometry hold that geometry's depth. The falls are never in this
|
||||
drain in retail at these poses — they drained pre-clear with the landscape.
|
||||
**Unverified residual:** whether any visible vista pixel can lie OUTSIDE
|
||||
the union of authored exit-portal polygons at the live chase camera (slice
|
||||
6's observation implies yes for the WALK's clipped views; whether that
|
||||
reflects portal-poly authoring or our clip is open — §8 Q1).
|
||||
|
||||
### 2.3 The live traces at the defect poses (decisive, read directly)
|
||||
|
||||
`posed/terrace-center.log` F5 (camera cell `f4180104` — the owner's
|
||||
teleloc cell):
|
||||
|
||||
```
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104 ← the interior flood is ONE cell
|
||||
LS
|
||||
BLD f518002e … BLD f4180014 BLD f418000a BLD f418000c BLD f418000b (14 buildings)
|
||||
```
|
||||
|
||||
`posed/cathedral-arrival.log` F1-2 (camera cell `f4180106`):
|
||||
|
||||
```
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106 ← flood = root cell only
|
||||
LS
|
||||
BLD f3180020 … BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100 ← look-in punch during LS
|
||||
… BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104 ← 0xF4180104 IS A LOOK-IN
|
||||
BLD f418000a
|
||||
```
|
||||
|
||||
`posed/terrace-edge.log` F5 (outdoor root, camera cell `f418000b`): look-in
|
||||
punches `n=1: f4180106`, `n=1: f4180101`, `n=1: f4180104` interleaved with
|
||||
the building walk — no clear, no seals, everything drains against full
|
||||
depth (why the owner sees a correct scene outdoors).
|
||||
|
||||
**Conclusion:** at the defect poses, `0xF4180104` and `0xF4180106` are in
|
||||
DIFFERENT buildings' interiors (`f418000b` vs the `f418000a/c` group —
|
||||
consistent with the "seam" being a building boundary), retail's interior
|
||||
flood never crosses it, and ALL non-root cathedral cells + their stabs
|
||||
compose in the landscape scope, pre-clear. The "toggles with which side of
|
||||
the seam the camera is on" symptom is the root flip between these two
|
||||
one-cell floods (and outdoor).
|
||||
|
||||
---
|
||||
|
||||
## 3. DAT geometry findings
|
||||
|
||||
**Verified:**
|
||||
|
||||
- The falls' observed particle owners `0x4F418012`–`0x4F418015` decode per
|
||||
`src/AcDream.Core/World/InteriorEntityIdAllocator.cs:48`
|
||||
(`0x40000000 | X<<20 | Y<<12 | counter`) to **interior EnvCell-stab
|
||||
statics of landblock F418**, counters 0x12–0x15 — NOT landblock objects
|
||||
(0xC0000000+ namespace) and NOT scenery (0x80000000+). The slice-5 commit
|
||||
message (`0d6cd5c0`) reached the same identification ("EnvCell STAB-LIST
|
||||
statics … ids minted by InteriorEntityIdAllocator").
|
||||
- Retail cell topology at the poses (from the traces, §2.3): `0xF4180104`
|
||||
belongs to building `f418000b`'s look-in set; `0xF4180100/0101` to
|
||||
`f418000c`'s; `0xF4180106` is punched separately (arrival trace);
|
||||
interior floods do not cross between them at the captured camera states.
|
||||
- The prior campaign's offline sightline probe (recorded in
|
||||
`claude-memory/project_pview_visibility_campaign.md` and the PV plan)
|
||||
established: the exit portal of the terrace cell and the building's
|
||||
never-drawn panel are the SAME surface, and the "#456 seam band" IS the
|
||||
terrace cell's portal[4] polygon — i.e. at least one exit aperture is a
|
||||
thin band, corroborating slice 6's live finding that scissoring terrain
|
||||
to the walk's exit views starves most of the vista of depth/color.
|
||||
|
||||
**Unverified (the DAT parse thread had not returned):** the per-cell portal
|
||||
tables (flags/0x4 exits/other_cell ids) for `0x0104/0x0105/0x0106/0x010D/
|
||||
0x010F`; WHICH EnvCell's stab list carries counters 0x12–0x15 (the counter
|
||||
is allocated in ascending cell order in `LandblockBuildFactory`
|
||||
`src/AcDream.App/Streaming/LandblockBuildFactory.cs:494-503`, so the DAT
|
||||
stab tables resolve it mechanically); the exit-portal polygon vertex
|
||||
extents vs the visible opening; whether the lake water is terrain-hosted;
|
||||
the cathedral shell GfxObj roster (`0x01001FB2/B0/B3` — asserted by the
|
||||
owner's prior analysis and consistent with the PV panel model
|
||||
(`0x01001FB0/B2/B3/B7` panels, PV plan §"established model" item 3), but
|
||||
not independently re-read here).
|
||||
|
||||
---
|
||||
|
||||
## 4. Divergence table (ranked by causal weight for THIS defect)
|
||||
|
||||
| # | Divergence | Ours | Retail | Evidence |
|
||||
|---|---|---|---|---|
|
||||
| D1 | **Particle emission site for cell statics/dynamics** | flood-membership + `SphereStraddlesExitPlane` classifier decides pre- vs post-clear emission (slices 4/5) | the owner object's actual walk turn decides: drawn during LS (shadow-cell overlap or look-in) ⇒ landscape scope ⇒ pre-clear drain; drawn in the root flood ⇒ final drain | traces §2.3; model §5; appendix 811; commit msgs `f3a03efc`/`0d6cd5c0` |
|
||||
| D2 | **Interior "flood" membership for the cell-owners route** | the production cell set treats the connected cathedral complex as interior content (slice-2/3 messages: "the cell-owners route", "the OLD apparatus flood"); walk conformance proves the WALK floods n=1, but consumers were still being cut over one at a time | flood = what survives GetClip from the root; at these poses exactly ONE cell; everything else is landscape-scope | traces §2.3; commit msgs `c40aecfc`/`5f7ccdea`; plan FW3.2b-2 "prepareCells UNIONED" |
|
||||
| D3 | **Dynamics-last** (register row AD-19, `docs/architecture/retail-divergence-register.md:147`): ALL dynamics in one z-buffered final pass | per-landcell painter-ordered draws inside the landscape pass; interior per cell turn | register row (self-declared); `DrawBlock` 0x005a17c0 / DrawSortCell pc:430124 |
|
||||
| D4 | **No degrade/LOD admission on this branch** — the marker stalks (Setup `0x0200085A` → GfxObj `0x010001EC`, ladder `0x1100002E` = {self at MaxDist 0, id 0 to ∞}) are DRAWN | retail selects `gfxobj[deg_level]` via `get_degrade` @0x0051E4B0 and **never draws** this model at any distance (gates in `DrawBuilding` @0x0059F2A0 and `CPhysicsPart::Draw` @0x0050D7A0) | `fce250ec` exists ONLY on `claude/cathedral-rendering-debug-47a417` (`git branch -a --contains`; `git merge-base --is-ancestor fce250ec HEAD` = false; `git grep IsNeverDrawnByDegrade -- src` = empty at HEAD) |
|
||||
| D5 | **Seal/vista coverage geometry**: post-clear alpha can z-pass wherever the visible vista is not covered by a stamped seal; slice 6 proved the walk's exit views at this seam are "thin BANDS far narrower than the real opening" | retail is not exposed to this because the leaking content class isn't post-clear in the first place (D1/D2) | commit msg `bb9212c7`; PV sightline probe (campaign memory) |
|
||||
| D6 | (regression candidate) straddle-classified emitters now emit pre-clear on interior roots — an interior emitter drained at the landscape flush is subsequently over-painted by the post-clear interior repaint | retail: root-cell emitters drain at the final flush, after interior geometry | mechanism prediction from `f3a03efc`/`0d6cd5c0` + model §5; matches the owner's new "particles WITHIN the cathedral are wrong" report — **unverified until probed** |
|
||||
|
||||
The pre-existing #451 fix (owner-accepted 2026-08-27, `docs/ISSUES.md:249-289`)
|
||||
had already implemented the D1-correct contract for exterior particles
|
||||
("submits attached and ownerless exterior particles inside `LScape::draw`
|
||||
for every PView root"). The FW2/FW3 window (`git diff --stat
|
||||
e102fb36..37febd1f -- src/AcDream.App/Rendering` = ~7,081 insertions:
|
||||
the whole `Walk/` tree, `WalkFrameDriver`, the static cutover) is the
|
||||
regression window that displaced it.
|
||||
|
||||
---
|
||||
|
||||
## 5. Per-commit verdicts
|
||||
|
||||
**`37febd1f` (slice 1 — interior outside-view slices from the walk): KEEP.**
|
||||
Correct single-source-of-truth move (retail has ONE visibility structure);
|
||||
fixed a real dual-path desync (the #119-family grass flash) with a real
|
||||
mechanism (old-apparatus fat/degenerate exit views + walk-indexed punch
|
||||
fans). Not causal for the cathedral defect either way. Its per-view terrain
|
||||
slice FAN was still non-retail — superseded by slice 6, which it should be
|
||||
read together with.
|
||||
|
||||
**`c40aecfc` (slice 2 — exit seals stamp the walk's flood): KEEP the code,
|
||||
REJECT the diagnosis (wrong-mechanism).** Stamping the walk's own flood's
|
||||
exit portals is exactly retail (model §5 seal loop, pc:432785-6, and the
|
||||
walk flood is conformance-proven against the traces). But the commit's
|
||||
premise — falls emitters submitting POST-clear is "retail's own timing" —
|
||||
is refuted by the traces: retail composes those cells pre-clear as
|
||||
look-ins. Seals could never fix an over-draw whose correct rendering is
|
||||
"drawn pre-clear with landscape depth"; at best they would z-reject the
|
||||
falls and make them wrongly INVISIBLE through the aperture. The plan's
|
||||
slice-2 paragraph (`docs/plans/2026-08-30-campaign-fw-frame-walk.md`
|
||||
FW4 slice 2) carries the same false premise and should be corrected.
|
||||
|
||||
**`5f7ccdea` (slice 3 — outside-stage predicate reads the walk flood):
|
||||
KEEP.** Feeding predicates from the walk's VisitedCells (root flood +
|
||||
look-in floods — "all of whose objects retail draws inside LScape::draw,
|
||||
pre-clear") is the correct retail-shaped direction and removed one more
|
||||
old-apparatus consumer. Insufficient alone: it gates the DYNAMIC split,
|
||||
and the falls turned out to be cell statics (slice 5's own finding).
|
||||
|
||||
**`f3a03efc` (slice 4 — straddler dynamics' particles pre-clear, once):
|
||||
WRONG-MECHANISM — revert once D1 lands.** Retail has no exit-plane-straddle
|
||||
classifier for emission timing; the cited rule ("retail submits an
|
||||
emitter's polys at its object's FIRST draw — the landscape stage for
|
||||
anything overlapping outdoor shadow cells") is right, but the
|
||||
implementation substitutes a plane-straddle test for draw-site membership.
|
||||
It under-covers emitters wholly OUTSIDE the exit plane (they never
|
||||
"straddle" ⇒ stay post-clear ⇒ still leak) and over-covers interior
|
||||
straddlers into the pre-clear drain where the interior repaint erases them
|
||||
(divergence D6 — the likely source of the new in-cathedral particle
|
||||
wrongness). Drawn-once bookkeeping (each owner emits exactly once) is worth
|
||||
preserving in whatever replaces it.
|
||||
|
||||
**`0d6cd5c0` (slice 5 — straddling cell-statics' particles pre-clear):
|
||||
WRONG-MECHANISM — revert once D1 lands.** Its identification work is the
|
||||
best of the six (falls = EnvCell stab statics; the shadow-cell rule
|
||||
correctly cited at `DrawBlock` pc:430056-430064), but it ports the rule as
|
||||
the same geometric straddle test instead of the actual membership question
|
||||
("is this object drawn during the landscape scope this frame?" — for the
|
||||
falls cells at these poses: yes, as building look-ins, regardless of any
|
||||
plane test). Same under/over-coverage failure modes as slice 4.
|
||||
|
||||
**`bb9212c7` (slice 6 — interior roots draw landscape unclipped): KEEP.**
|
||||
Matches retail exactly: landscape content is view-CULLED, never
|
||||
view-CLIPPED (model §3: the views feed only `draw_check_blocks`' in_view
|
||||
union; `set_view` installs no GPU state; no scissor exists). Its probe
|
||||
finding — falls submitted pre-clear with depth LESS still passed because
|
||||
the scissored terrain had left the vista without depth — is the
|
||||
load-bearing discovery of the round and independently confirms the thin-band
|
||||
aperture geometry. It cannot, by itself, fix emitters that still submit
|
||||
post-clear on other frames/poses, and it does not address D6.
|
||||
|
||||
---
|
||||
|
||||
## 6. Why the symptom survived all six fixes
|
||||
|
||||
1. **A wrong premise was locked in at slice 2 and never re-examined against
|
||||
the traces the campaign itself captured.** "The falls submit post-clear —
|
||||
retail's own timing" is contradicted by `posed/terrace-center.log` /
|
||||
`posed/cathedral-arrival.log`: retail's interior flood at these poses is
|
||||
ONE cell and the falls' owner cells are landscape-scope look-ins. Every
|
||||
subsequent fix (seals, predicates, straddle emission) tried to make a
|
||||
post-clear world safe instead of putting the content in the pre-clear
|
||||
world where retail has it.
|
||||
2. **The corrective classifier is not retail's rule.** The straddle test
|
||||
(slices 4/5) approximates "overlaps outdoor shadow cells" with "sphere
|
||||
crosses the exit plane" — a strictly narrower predicate that misses
|
||||
fully-outside emitters (the falls hang OVER the lake) and wrongly
|
||||
captures interior straddlers.
|
||||
3. **Mixed visibility sources during the staged cutover.** Slices 2 and 3
|
||||
each discovered another consumer still reading the OLD apparatus's flood
|
||||
(seal set, outside-stage predicate). The FW3.2b-2 design explicitly runs
|
||||
old visibility for dynamics beside the walk for statics
|
||||
(`docs/plans/2026-08-30-campaign-fw-frame-walk.md`); every such seam is
|
||||
a place where the walk's correct n=1 flood is not what production
|
||||
consumes. AD-19 (dynamics-last) keeps the weenie stalks post-clear
|
||||
regardless.
|
||||
4. **Two of the leaking content classes cannot be fixed by ANY
|
||||
ordering/seal work:** the marker stalks should never be drawn at all
|
||||
(D4 — the degrade admission port is quarantined off-branch), and
|
||||
pre-slice-6 the vista had no depth to occlude against (D5) — so even
|
||||
correct pre-clear emission "passed" its z-test into stale color.
|
||||
5. **Each fix was validated against the previous fix's failure, not against
|
||||
a falsifiable retail prediction.** The probes pinned real facts
|
||||
(phase=pre/post, straddler classification) but the acceptance question
|
||||
was "did the leak stop" rather than "does the frame now match §2.1's
|
||||
shape at this pose" — so five plausible partial mechanisms shipped in
|
||||
one day without any landing the frame-shape divergence (D1/D2).
|
||||
|
||||
---
|
||||
|
||||
## 7. Recommended fix plan (mechanism-first)
|
||||
|
||||
**Step 0 — probes before code (falsifiable predictions):**
|
||||
|
||||
- **P1:** With `ACDREAM_PROBE_WALK_ROOT` at the terrace pose on HEAD, the
|
||||
falls owners `0x4F418012-15` will log submission OUTSIDE the landscape
|
||||
scope (post-clear route or final drain) on defect frames, while the
|
||||
walk's own event stream visits their owner cells at BUILDING LOOK-IN
|
||||
turns (not in the interior flood, which stays n=1). If instead they
|
||||
already log landscape-scope submission + pre-clear drain on defect
|
||||
frames, D1 is falsified — investigate the drain's depth state
|
||||
(attachment, compare op) instead.
|
||||
- **P2:** The DAT stab tables will place stabs #0x12-0x15 in cell(s) OTHER
|
||||
than the frame's root cell at the defect poses (look-in-reached cells).
|
||||
If they are stabs of the root cell itself, the look-in theory is
|
||||
falsified.
|
||||
- **P3:** Forcing the falls emitters to submit at their owner cell's
|
||||
look-in turn (landscape scope, drained at the pre-clear boundary flush)
|
||||
removes the over-draw at EVERY camera on interior roots, while the falls
|
||||
stay visible through the opening and terrain-occluded exactly as on
|
||||
outdoor roots.
|
||||
- **P4:** Reverting slices 4/5's straddle emission (in favor of the
|
||||
walk-turn rule) makes the new "particles within the cathedral are wrong"
|
||||
report disappear (root-cell emitters return to the final drain).
|
||||
- **P5 (optional, owner-assisted):** a cdb capture at the terrace with a
|
||||
breakpoint on the particle-poly alpha-list insertion will show falls
|
||||
polys entering the list BEFORE `DrawCells`' depth clear on
|
||||
interior-rooted retail frames.
|
||||
|
||||
**Step 1 — the emission-site port (fixes the falls).** An object's
|
||||
particles submit when and where the object itself draws in the walk:
|
||||
look-in cell content and outdoor shadow-cell content submit during the
|
||||
landscape turn and drain at the pre-clear boundary flush; root-flood cell
|
||||
content submits at its cell turn and drains at the final flush. Retail
|
||||
pins: appendix 811 (look-in `DrawCells` inside the landscape scope),
|
||||
`DrawBlock` pc:430056-430064 (shadow-cell object draw), model §5 (the two
|
||||
drain sites). Delete `SphereStraddlesExitPlane`-based emission (slices
|
||||
4/5) in the same commit; keep the emit-once ledger.
|
||||
|
||||
**Step 2 — finish the flood-consumer cutover (fixes the class).** Every
|
||||
remaining consumer of the old apparatus's cell set (cell-statics particle
|
||||
owners, dynamics routes, prepare unions) reads the walk's per-turn visited
|
||||
sets. AD-19's interior-root half retires when dynamics draw at their
|
||||
cell's walk turn; if that is deferred, dynamics must at minimum gate on
|
||||
the walk's visited set so a non-visited cell's weenies cannot draw at all.
|
||||
|
||||
**Step 3 — re-land the marker-degrade admission (fixes the stalks being
|
||||
visible at all).** `fce250ec`'s mechanism (never-drawn degrade ladders:
|
||||
`get_degrade` @0x0051E4B0; `gfxobj[deg_level] != 0` gates) is a retail
|
||||
port stranded on the quarantined branch. Re-land it cleanly on this branch
|
||||
(fresh commit, not a cross-merge from quarantine), covering static AND
|
||||
dynamic classifiers. If stalk-like shapes remain afterwards, identify by
|
||||
guid before theorizing (they'd be weenies with a different model —
|
||||
campaign-memory rule).
|
||||
|
||||
**Step 4 — verification gates.** The ten oracle fixtures stay green (the
|
||||
walk itself is already conformant); add a fixture-level assertion that the
|
||||
DRIVER's submission stream places look-in-cell content in the landscape
|
||||
scope (the current conformance compares walk events, not submission
|
||||
scope — that gap is how D1 shipped); then the owner matrix: terrace both
|
||||
cells, seam crossing, outdoor falls, interior candles, doorway flap,
|
||||
Holtburg exit.
|
||||
|
||||
---
|
||||
|
||||
## 8. Open questions
|
||||
|
||||
- **Q1:** Why are the walk's exit views at this seam thin bands — is the
|
||||
full visible opening authored as multiple portals (0/1/2/4) of which
|
||||
only portal 4 survives GetClip at these camera states, or is our
|
||||
clip/winding at fault, or does the chase camera sit outside the cell
|
||||
volume? (Slice 6 measured the symptom; the cause is unestablished. The
|
||||
PV probe's "panel ∪ solid covers every sightline" was measured at ≤6 m
|
||||
camera rises only.)
|
||||
- **Q2:** Which EnvCell(s) own falls stabs #0x12-0x15, and do the falls
|
||||
additionally exist as dynamics (slice 3 probed entities "f4243/f4285"
|
||||
as [walk-dyn] — the relationship between those ids and the stab owners
|
||||
is unestablished). **DAT parse owed.**
|
||||
- **Q3:** The exact HEAD drain/depth state of `ParticleRenderer` under
|
||||
Vulkan (depth attachment, compare op, write mask) at each drain site —
|
||||
required to close P1's alternative branch. **Code audit owed.**
|
||||
- **Q4:** Whether `ShouldDrawParticles` (@0x0050FE60) is evaluated
|
||||
per-frame or per-view in our port, and against which cell set at HEAD.
|
||||
- **Q5:** The gated clear's `portalsDrawnCount` semantics across frames
|
||||
(seals increment it AFTER the clear check — does the first interior
|
||||
frame after an outdoor frame skip the clear in retail?), and whether our
|
||||
`slices>0` production gate is equivalent.
|
||||
- **Q6:** Whether the seal fans at HEAD still apply the old assembly's
|
||||
per-cell slice clips ("where present", slice 2) and whether that can
|
||||
shrink a seal below its authored polygon.
|
||||
- **Q7:** The uncharacterized in-cathedral particle wrongness (owner
|
||||
report #3) — D6 is a prediction, not a diagnosis; one probe frame with
|
||||
`[walk-part]`/`[walk-alpha]` at a candle would settle it.
|
||||
|
||||
---
|
||||
|
||||
*Reviewer: Fable. All decomp addresses cite the named 2013 build
|
||||
(`docs/research/named-retail/`); trace lines cite files under
|
||||
`docs/research/2026-08-30-fw-walk-oracle/posed/`; commit claims cite the
|
||||
SHAs in §5; everything not directly read is marked unverified above.*
|
||||
|
|
@ -1,673 +0,0 @@
|
|||
# Cathedral rendering review — independent report (Opus)
|
||||
|
||||
**Reviewer:** Opus 5, independent. **Mode:** report-only (no edits, no builds,
|
||||
no client launches, no commits). **Reviewed:** `37febd1f` (baseline) →
|
||||
`bb9212c7` (HEAD), branch `claude/campaign-w-retail-frame-walk`, worktree
|
||||
`peaceful-blackburn-5333f0`.
|
||||
**Date:** 2026-08-30.
|
||||
|
||||
Evidence classes used, in order of authority: (1) the named retail decomp
|
||||
(`docs/research/named-retail/acclient_2013_pseudo_c.txt` + Ghidra MCP at
|
||||
`127.0.0.1:8081`); (2) the installed DATs, read with the already-built
|
||||
`tools/A8CellAudit/bin/Release/net10.0/A8CellAudit.exe`; (3) the live probe
|
||||
logs in `%LOCALAPPDATA%\Temp\fw3-*.log` / `fw4-*.log`; (4) our source at both
|
||||
SHAs. Everything I could not verify is labelled **UNVERIFIED** inline.
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive verdict
|
||||
|
||||
1. **The ordering fix the last three slices were chasing already landed.** On
|
||||
HEAD the falls' four emitters submit *and drain* pre-clear
|
||||
(`fw4-slice6-gate.log:12786-12801`). Phase is no longer the binding
|
||||
constraint, so slices 4–6 were repairing an already-repaired axis.
|
||||
2. **The falls are `{4f418012..4f418015}` — four STAB STATICS of EnvCell
|
||||
`0xF4180106`**, reached on the failing frames as a **building look-in**,
|
||||
not as a root-flood cell. Slices 3 and 4 named the wrong objects
|
||||
(`f4243`/`f4285` are ~9.8 m-radius *dynamics*).
|
||||
3. **Slice 6's factual premise is refuted by the DAT.** `0xF4180104`'s exit
|
||||
portals are the cell's **full side faces** (4-vert rectangles at ±12.00 m,
|
||||
7-vert arches at ≈±5.03 m) — not "thin bands".
|
||||
4. **The only depth-destroying operation in the pre-clear scope is the
|
||||
portal PUNCH** (far-Z, `ALWAYS`+write). Retail bounds it with an
|
||||
unconditional **full** `FlushAlphaList(0f)` at *every* building
|
||||
(@0x0059f30b); ours is a distance-filtered *partial* flush. That is the
|
||||
highest-ranked unexamined suspect — but I could not prove it offline.
|
||||
5. **Four other decomp-provable divergences** are live at HEAD: unconditional
|
||||
clear+seals (retail gates both on `ov>0`), a clipped punch (retail punches
|
||||
the *unclipped* polygon), terrain drawn with no per-landcell `in_view`
|
||||
cull, and a sorted alpha drain (retail's has **no sort**).
|
||||
6. **The "stalks" have a completed, decomp-cited fix that is simply not on
|
||||
this branch**: `fce250ec` (marker-class degrade admission) is **not an
|
||||
ancestor of `bb9212c7`**. Nothing on this branch suppresses them.
|
||||
7. **Why six fixes failed:** three different object identities, zero depth
|
||||
measurements, two false retail citations, six commits in 105 minutes with
|
||||
no gate between them. The failure is a *method* failure, not a
|
||||
*mechanism-knowledge* failure.
|
||||
8. **Recommendation:** stop shipping fixes. Instrument the depth buffer and
|
||||
the punch census first (§7 Stage 0). Then land R1–R7, each gated.
|
||||
9. **Commit verdicts:** keep 1/2/3 (with corrections), revert 4, revert 5,
|
||||
keep 6's *shape* but fix its cull.
|
||||
10. **I could not determine the final residual pixel mechanism offline** and
|
||||
say so rather than adding a seventh guess.
|
||||
|
||||
---
|
||||
|
||||
## 2. Retail frame reconstruction
|
||||
|
||||
Everything in this section is read from the decomp at the cited address.
|
||||
|
||||
### 2.1 The four (and only four) alpha drains
|
||||
|
||||
`grep "FlushAlphaList"` over the whole pseudo-C returns exactly five hits —
|
||||
the definition plus **four call sites**:
|
||||
|
||||
| Address | Site | Argument | Meaning |
|
||||
|---|---|---|---|
|
||||
| `0x00452bf0` | a `SmartBox` sibling (not the normal-mode path) | `0f` | drain all |
|
||||
| `0x00453b8b` | `SmartBox::RenderNormalMode` epilogue | `0f` | **frame-end drain** |
|
||||
| `0x0059f30b` | `RenderDeviceD3D::DrawBuilding`, **before** the portal pass | `0f` | **pre-punch drain** |
|
||||
| `0x005a1a07` | `RenderDeviceD3D::DrawBlock`, after `DrawSortCell` | global `flush` | pressure valve |
|
||||
| `0x005a4872` | `PView::DrawCells`, after `LScape::draw` | `0f` | **pre-clear drain** |
|
||||
|
||||
`D3DPolyRender::FlushAlphaList` @0x0059d2e0 (Ghidra decompile, full body read):
|
||||
|
||||
```c
|
||||
if (((float)alphaedMeshCountClip < param_1 * 3000.0f) &&
|
||||
((float)alphaedMeshCountAlpha < param_1 * 3000.0f)) return false; // 0x453B8000 = 3000.0f
|
||||
... walk alphaedMeshListClip [0..count) -> RenderMeshSubset ... ; alphaedMeshCountClip = 0;
|
||||
... walk alphaedMeshListAlpha [0..count) -> RenderMeshSubset ... ; alphaedMeshCountAlpha = 0;
|
||||
```
|
||||
|
||||
Three load-bearing facts:
|
||||
|
||||
- **There are TWO lists**, `alphaedMeshListClip` and `alphaedMeshListAlpha`
|
||||
(both capacity `0xbb8` = 3000), drained clip-first.
|
||||
- **There is NO SORT.** The drain is a straight forward walk of the arrays.
|
||||
`D3DPolyRender::AddMeshToAlphaList` @0x0059c230 stores only
|
||||
`{MeshBuffer*, subsetIndex, CSurface*, flags, worldMatrix}` — **no depth
|
||||
key exists**. Retail's alpha order *is* the walk's submission order.
|
||||
- `flush(0f)` always drains (threshold 0 ⇒ `count < 0` false). `flush(0.75)`
|
||||
drains only above 2250 queued.
|
||||
|
||||
The `DrawBlock` valve at `0x005a19ee-005a1a07` compares the **global `flush`
|
||||
against `1.0f`**, not the queue size, and only calls through when the compare
|
||||
fails. At any `flush ≤ 1.0` it never fires, and at `flush > 1.0` the
|
||||
threshold `flush*3000` exceeds the 3000-entry cap, so it can never drain
|
||||
either. **The mid-walk valve is effectively inert.** (The `test ah,0x41`
|
||||
polarity is BN-unlifted; I read the idiom, not the raw bytes — **polarity
|
||||
UNVERIFIED**, but both readings give "inert", so it is not load-bearing.)
|
||||
|
||||
What defers, from `D3DPolyRender::DrawMesh` @0x0059d4a0: the decision is
|
||||
**per mesh SUBSET**, keyed on `s_AlphaDelayMask & mesh->isStippledOrAlphaedMask[subset]`,
|
||||
with an early bail when `render_device+0x7e0 != 0`. It is a *surface
|
||||
property*, never a distance.
|
||||
|
||||
### 2.2 `PView::DrawCells` @0x005a4840 — byte-order confirmed
|
||||
|
||||
```c
|
||||
if (this->outside_view.view_count > 0) { // 005a4852
|
||||
Render::useSunlightSet(1); // 005a485a
|
||||
Render::PortalList = this; // 005a485f
|
||||
LScape::draw(this->lscape); // 005a486b
|
||||
D3DPolyRender::FlushAlphaList(0f); // 005a4872 <-- PRE-CLEAR DRAIN
|
||||
render_device->m_nFrameStamp += 1; // 005a4886 <-- dedupe RE-ARM
|
||||
if (forceClear == 0) { cond = (portalsDrawnCount == 0); portalsDrawnCount = 0; } // 005a4893-9e
|
||||
if (forceClear != 0 || !cond)
|
||||
render_device->Clear(4, black, 1.0f); // 005a48a9 <-- Z CLEAR
|
||||
for (cell = cell_draw_list[n-1] .. [0]) // 005a48c9 far->near
|
||||
if (cell->structure->drawing_bsp != 0) {
|
||||
positionPush; for each view: setup_view;
|
||||
for each portal: if (other_cell_id == 0xffffffff)
|
||||
DrawPortalPolyInternal(portal.poly, 0); // 005a49b7 <-- SEALS (own depth)
|
||||
}
|
||||
} // 005a4852 block ENDS here
|
||||
Render::useSunlightSet(0); Render::restore_all_lighting(); // 005a49f3
|
||||
for (cell far->near) if (drawing_bsp) { per view: setup_view; DrawEnvCell(cell); } // 005a4a05
|
||||
for (cell far->near) { PortalList = cell top view; DrawObjCellForDummies(cell); } // 005a4ae3
|
||||
```
|
||||
|
||||
**The depth clear AND the exit seals are both strictly inside
|
||||
`outside_view.view_count > 0`.** There is no drain after `DrawEnvCell` and
|
||||
none after `DrawObjCellForDummies` — the interior scope's alpha survives to
|
||||
`RenderNormalMode`'s frame-end drain @0x00453b8b (verified: that epilogue
|
||||
runs immediately after the root branch, before the target/rendering
|
||||
callbacks).
|
||||
|
||||
### 2.3 Buildings, punches, and the answer to (a) "clipped vs culled vs whole"
|
||||
|
||||
`RenderDeviceD3D::DrawBuilding` @0x0059f2a0, read verbatim:
|
||||
|
||||
```c
|
||||
outdoor_pview->outdoor_portal_list = b->portals;
|
||||
CPhysicsPart::UpdateViewerDistance(part0);
|
||||
if (part0->gfxobj[part0->deg_level] != 0) { // 0059f2d3 — degrade gate
|
||||
... set detail surface state ...
|
||||
D3DPolyRender::FlushAlphaList(0f); // 0059f30b — FULL drain, unconditional
|
||||
CPhysicsPart::Draw(part0, 1); // 0059f31d — PORTAL pass
|
||||
ObjBuildingOrBuildingPart = 1;
|
||||
CPhysicsPart::Draw(part0, 0); // 0059f336 — SHELL pass
|
||||
ObjBuildingOrBuildingPart = 0;
|
||||
}
|
||||
```
|
||||
|
||||
`PView::ConstructView(CBldPortal)` @0x005a59a0 — the punch itself:
|
||||
|
||||
```c
|
||||
side = sidedness(viewer, poly.plane, F_EPSILON=0.000199999995f); // 005a59c6
|
||||
if (portal_side ? side==NEGATIVE : side==POSITIVE) {
|
||||
PView::GetClip(this, side, poly, &clip_view, &npts, pass); // 005a5a23
|
||||
if (npts != 0 && (dest = CEnvCell::GetVisible(other_cell_id)) != 0
|
||||
&& Render::copy_view(dest->portal_view[dest->num_view-1], &clip_view, npts) != 0) {
|
||||
if (arg5 != 2) DrawPortalPolyInternal(esi /* the RAW polygon */, arg5 == 1); // 005a5a7b
|
||||
framePop();
|
||||
if (arg5 != 1) PView::ConstructView(this, dest, other_portal_id); // 005a5a9c
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
```
|
||||
|
||||
Two things this settles:
|
||||
|
||||
- **Pass 1 punches (flag 1 = far-Z), pass 2 recurses.** Pass 2 draws no
|
||||
polygon at all.
|
||||
- **The punched polygon is `esi` — the ORIGINAL, UNCLIPPED authored
|
||||
polygon.** `clip_view`/`npts` are used only to build the destination cell's
|
||||
view. Retail never hardware-clips or scissors the punch.
|
||||
|
||||
Answering the prompt's question (a) for an interior root with exit portals:
|
||||
|
||||
| Content | Treatment |
|
||||
|---|---|
|
||||
| terrain (`DrawLandCell`) | **view-CULLED** per block and per 24 m landcell by `LScape::draw_check_blocks` @0x00505f80 / `landcell_check` @0x005050a0 against every active view; the surviving cells draw **whole**. Never clipped. |
|
||||
| sky (`GameSky::Draw`) | drawn once at the head of `LScape::draw`, **whole**. |
|
||||
| building shells | **whole**, per building, after that building's punch pass; gated only by `gfxobj[deg_level] != 0`. |
|
||||
| EnvCell shells (`DrawEnvCell`) | **whole**, per cell per installed view. |
|
||||
| objects / cell statics / dynamics | `Render::viewconeCheck` @0x0054c250 sphere-vs-view; PARTIAL draws **whole**. |
|
||||
| **portal-chain polygons only** | the *only* CPU-clipped geometry, via `PView::GetClip` @0x005a4320 → `ACRender::polyClipFinish`. And the clip result is used for the **view**, not for the drawn punch. |
|
||||
|
||||
### 2.4 Where particles live (this was the missing piece)
|
||||
|
||||
`ParticleEmitter::EmitParticle` @0x0051d126:
|
||||
|
||||
```c
|
||||
Particle::Init(...);
|
||||
CPhysicsObj::AddPartToShadowCells(this->physobj); // 0051d126
|
||||
```
|
||||
|
||||
**Every emitted particle is registered as a `CPhysicsPart` in its owner
|
||||
object's SHADOW CELLS.** It therefore draws at those cells' turns through
|
||||
`DrawBlock` (pass 1 updates in-view cells' shadow objects and
|
||||
insertion-sorts shadow parts by depth; pass 2 draws them via `DrawSortCell`
|
||||
→ `DrawObjCell`) and through `DrawObjCellForDummies` for interior cells —
|
||||
i.e. through the same `CPhysicsPart::Draw` → `DrawMesh` path as any mesh,
|
||||
and so through the same per-subset alpha-delay branch.
|
||||
|
||||
`CPhysicsObj::ShouldDrawParticles` @0x0050fe60 is **not** a draw-order gate.
|
||||
Its single caller is `ParticleEmitter::UpdateParticles` @0x0051d1a6 — the
|
||||
**simulation** side. Body:
|
||||
|
||||
```c
|
||||
if (m_bExaminationObject == 0) {
|
||||
if (!(CYpt < arg2 /*degrade_distance*/)) return 0;
|
||||
if (cell == 0) return 0;
|
||||
if (cell->vtable->IsInView() == 0) return 0;
|
||||
}
|
||||
return 1;
|
||||
```
|
||||
|
||||
### 2.5 Answers to (b), (c), (d)
|
||||
|
||||
**(b) Draw order relative to the clear and the seals**, interior root, `ov>0`:
|
||||
|
||||
```
|
||||
sky ─┐
|
||||
terrain (culled cells) │
|
||||
per building: │ all inside LScape::draw → ALPHA QUEUED
|
||||
FlushAlphaList(0f) │ (and drained at every building)
|
||||
punch (far-Z, raw) │
|
||||
look-in DrawCells │
|
||||
shell │
|
||||
outdoor objects+particles┘
|
||||
────────── FlushAlphaList(0f) @005a4872 ────────── drain vs TRUE landscape depth
|
||||
────────── m_nFrameStamp += 1 ────────── per-part dedupe RE-ARMED
|
||||
────────── Clear(z) @005a48a9 ────────── gated on portalsDrawnCount
|
||||
────────── exit seals @005a49b7 ────────── own-depth panels at apertures
|
||||
DrawEnvCell per flood cell ─┐
|
||||
DrawObjCellForDummies per flood cell (objects+their │ ALPHA QUEUED
|
||||
particles) ─┘
|
||||
────────── FlushAlphaList(0f) @00453b8b ────────── drain vs interior depth + seals
|
||||
```
|
||||
|
||||
**(c) How the list drains.** Two arrays, no sort, insertion order, drained at
|
||||
the four sites above; each drain zeroes its counts. The depth in place at
|
||||
each: *pre-punch* = everything drawn so far this landscape walk; *pre-clear*
|
||||
= complete landscape depth including punched apertures and whatever look-in
|
||||
content refilled them; *frame-end* = cleared depth ∪ seals ∪ interior shells
|
||||
∪ interior objects.
|
||||
|
||||
**(d) Depth at final drain.** At the **aperture** the seals own it (portal
|
||||
surface's own projected depth, written `DEPTHTEST_ALWAYS` + z-write after the
|
||||
clear). At the **vista** (everything the landscape painted) depth is `1.0` —
|
||||
the clear erased it — and only **colour** survives. That is precisely why
|
||||
retail *must* drain the landscape alpha before the clear: any landscape-scope
|
||||
alpha that survived into the interior scope would z-pass across the whole
|
||||
vista. Retail's guarantee is structural: after `FlushAlphaList(0f)` at
|
||||
`005a4872` **the list is empty**, so nothing can.
|
||||
|
||||
---
|
||||
|
||||
## 3. DAT geometry findings (installed DATs, read live)
|
||||
|
||||
Run: `tools/A8CellAudit/bin/Release/net10.0/A8CellAudit.exe portals <cell>`
|
||||
(already built; no compilation performed).
|
||||
|
||||
### 3.1 Landblock `0xF418` inventory
|
||||
|
||||
- **88 outdoor objects** `0xCF418000`–`0xCF418057`. **Ordinals 1–7 are model
|
||||
`0x0200085A`** — the spawn-marker Setup that retail never draws (see §6).
|
||||
- **9 buildings** `0xCF418058`–`0xCF418060`. Four carry portals:
|
||||
`0x01001FB0`→cells 0100-0102, **`0x01001FB2`→0103,0104**,
|
||||
**`0x01001FB3`→0106-010B**, `0x01001FB7`→0112-0115.
|
||||
- **24 EnvCells** `0xF4180100`–`0xF4180117`.
|
||||
|
||||
### 3.2 The cells that matter
|
||||
|
||||
**`0xF4180104`** (the owner's teleloc), `localAABB min=(-5.70,-12.00,9.80)
|
||||
max=(5.02,12.00,20.00)`:
|
||||
|
||||
| portal | other | flags | polyId | verts | plane N | centroid·N |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 0 | `0xFFFF` EXIT | 5 | 36 | **4** | (0, 1, 0) | **−12.000** |
|
||||
| 1 | `0xFFFF` EXIT | 5 | 39 | **7** | (−1, 0, 0.06) | −5.028 |
|
||||
| 2 | `0xFFFF` EXIT | 5 | 37 | **4** | (0, −1, 0) | **−12.000** |
|
||||
| 3 | `0x0105` | ExactMatch, PortalSide | 40 | 8 | (0, 0, −1) | 5.100 |
|
||||
| 4 | `0xFFFF` EXIT | 5 | 38 | **7** | (1, 0, 0.06) | −5.035 |
|
||||
|
||||
**This refutes slice 6's stated premise.** Portals 0 and 2 are 4-vertex
|
||||
rectangles sitting exactly on the cell's ±Y faces at ±12.00 m — i.e. **the
|
||||
entire 10.7 m × 10.2 m end faces**. Portals 1 and 4 are 7-vertex arches on
|
||||
the ±X faces at ≈±5.03 m. This is an open-sided gallery, not a set of thin
|
||||
seam bands. The "seam band" the PV campaign observed is a **screen-space**
|
||||
artifact of how our clip slices are built, not an authored polygon shape.
|
||||
|
||||
**`0xF4180106`** — the falls' owner cell, `localAABB min=(-5.49,-12.00,9.80)
|
||||
max=(5.23,-6.00,20.00)`: 3 portals — interior →`0x0110` (poly 78, 9 verts),
|
||||
interior →`0x010F` (poly 77, 9 verts), and **one exit** (poly 76, **4 verts**,
|
||||
N=(0,1,0), centroid·N = 3.000, flags 7). One 4-vertex outdoor aperture.
|
||||
|
||||
**`0xF418010F`**: 2 portals, both interior (→`0x0107`, →`0x0106`), **no
|
||||
exits**.
|
||||
|
||||
### 3.3 The exit-portal flag
|
||||
|
||||
Across all 24 cells of `0xF418`: every `other == 0xFFFF` portal has flags
|
||||
**5 or 7** (bit `0x4` always set); every interior portal has flags **1 or 3**
|
||||
(bit `0x4` never set). Bit `0x4` ≡ "leads outside" — exactly as
|
||||
`claude-memory/project_pview_visibility_campaign.md` records. Our tree
|
||||
detects exits purely by the `0xFFFF` sentinel and never reads bit `0x4`
|
||||
(`src/AcDream.App/Rendering/Walk/WalkCellFactory.cs:93`); the two are
|
||||
redundant here, so this is correct-by-accident but not a defect.
|
||||
|
||||
### 3.4 The falls' identity — PINNED
|
||||
|
||||
`%LOCALAPPDATA%\Temp\fw3-falls-probe.log`, aggregated:
|
||||
|
||||
```
|
||||
10303 [walk-part] cell-owners n=6 ids=[4f418012,4f418013,4f418014,4f418015,f4180106,f418010f]
|
||||
192 [walk-part] cell-owners n=5 ids=[4f418012,4f418013,4f418014,4f418015,f4180106]
|
||||
```
|
||||
|
||||
`{4f418012, 4f418013, 4f418014, 4f418015}` **never appear without cell
|
||||
`f4180106`**. `InteriorEntityIdAllocator.Base` mints
|
||||
`0x40000000 | (lbX<<20) | (lbY<<12)` = `0x4F418000` for landblock `0xF418`
|
||||
(`src/AcDream.Core/World/InteriorEntityIdAllocator.cs:47-48`), and the
|
||||
`0xC…` sibling namespace covers landblock outdoor statics. So the falls are
|
||||
**four EnvCell static objects of `0xF4180106`** — confirmed interior-cell
|
||||
statics, not landblock objects and not unattached emitters.
|
||||
|
||||
Corroborating: the `[walk-emit]` interior-unattached dump added at slice 5
|
||||
**never printed a single line** in any of the three gate logs
|
||||
(`grep -c walk-emit` = 0 in `fw4-emit-probe.log`, `fw4-slice5-gate.log`,
|
||||
`fw4-slice6-gate.log`) — the loop body is over an empty list. The
|
||||
unattached-emitter theory is dead.
|
||||
|
||||
**Cross-check against retail:** `terrace-edge.log` (retail's own walk at the
|
||||
#456 pose) punches `DC pv=009d4b08 ov=0 n=2: f4180106 f418010f` every frame,
|
||||
and `cathedral-arrival.log` roots at `DI f4180106` on all 39 frames — so
|
||||
retail reaches this exact cell through the same two routes.
|
||||
|
||||
**UNVERIFIED:** the Setup/GfxObj ids of the four falls objects. The audit
|
||||
tool has no stab-dump verb and I did not build one. Also UNVERIFIED: where
|
||||
the lake **water surface** geometry lives (landcell terrain vs a separate
|
||||
surface). Both are cheap to establish and both matter to the fix plan.
|
||||
|
||||
---
|
||||
|
||||
## 4. Divergence table (ranked)
|
||||
|
||||
Rank = (how load-bearing for this defect) × (confidence it is real).
|
||||
|
||||
| # | Divergence | Retail | acdream @ `bb9212c7` | Evidence |
|
||||
|---|---|---|---|---|
|
||||
| **D1** | **Per-building alpha barrier is partial, not full** | `FlushAlphaList(0f)` — drains the *entire* list before every punch pass | `AlphaBarrier` → `FlushLandscapeAlphaFartherThan(d)` — drains only entries with `ViewerDistance >= d` | decomp `0x0059f30b`; `WalkFrameDriver.cs:655`; `RetailPViewPassExecutor.WalkLeaf.cs:246-247`; `RetailPViewPassExecutor.cs:649-650`; `RetailAlphaQueue.cs:261-278` |
|
||||
| **D2** | **Clear + seals fire unconditionally** | both strictly inside `if (outside_view.view_count > 0)` | emitted unconditionally at `OnInteriorFloodDrawTurn`, doc-commented as deliberate | decomp `0x005a4852`…`0x005a49b7`; `WalkFrameDriver.cs:690-704` ("both unconditional … whether or not a landscape turn just ran") |
|
||||
| **D3** | **The punch is clipped, with a silent fall-open** | `DrawPortalPolyInternal(esi, 1)` on the **raw** polygon; no view clip, no scissor | clipped to `slices[activeViewIndex].Planes`; when the index is out of range it silently falls back to *no* planes | decomp `0x005a5a7b`; `RetailPViewPassExecutor.WalkLeaf.cs:145-158` |
|
||||
| **D4** | **Interior-root terrain has no `in_view` cull** | `draw_check_blocks`/`landcell_check` mark per-block and per-24 m-cell visibility against the exit views; only marked cells draw | `_terrain.Draw(camera, frustum, clipPlanes: default, ndcClipAabb: full)` — every frustum-visible landblock slot draws whole | decomp `0x00505f80` / `0x005050a0`; `RetailPViewPassExecutor.WalkLeaf.cs:99-110`; `TerrainModernRenderer.cs:202-242` |
|
||||
| **D5** | **The alpha drain sorts** | no sort — insertion order is the walk order; `AddMeshToAlphaList` stores no depth key | `SortRetailOrder()` radix-sorts by viewer distance at every flush | decomp `0x0059d2e0`, `0x0059c230`; `RetailAlphaQueue.cs:162, 269` |
|
||||
| **D6** | **Marker degrade admission absent** | `gfxobj[deg_level] == 0` ⇒ part not drawn; spawn markers author a nothing-ladder and are never drawn at any distance | no per-entity degrade selection on this branch | `git merge-base --is-ancestor fce250ec bb9212c7` → **NO**; walk degrade applies only to `WalkBuilding` (`WalkBuildingFactory.cs:87-120`) |
|
||||
| **D7** | **"Emit exactly once" invariant** | `m_nFrameStamp += 1` *after* the pre-clear drain deliberately re-arms the per-part dedupe so an object drawn outdoors draws **again** in the interior pass | slices 4/5 subtract pre-clear owners from both last-pass arms so each owner emits exactly once per frame | decomp `0x005a4886`; `RetailPViewRenderer.cs:2184-2185, 2322-2324` |
|
||||
| **D8** | **Straddle heuristics instead of shadow-cell membership** | an object's parts live in every cell its sphere overlaps (`AddPartToShadowCells`), and draw at each of those cells' turns | `SphereStraddlesExitPlane(cell, c, r)` geometric test over flood cells | decomp `0x0051d126`; `RetailPViewRenderer.cs:597-626`; `WalkPView.cs` (slice 5) |
|
||||
| **D9** | **Particle visibility is one frame stale and now over-wide** | `cell->IsInView()` is the *current* frame's flag | `TerrainModernRenderer.VisibleCellIds` + `InViewCells` are marked at frame end and consumed next frame; slice 6 widened the terrain set to the whole frustum | `WorldSceneRenderer.cs:263-264, 441-447`; `TerrainModernRenderer.cs:225-234` |
|
||||
| D10 | Exit flag `0x4` never read | flag `0x4` ≡ leads-outside | detected only via the `0xFFFF` sentinel | `WalkCellFactory.cs:93`; A8CellAudit flags 5/7 vs 1/3 — **redundant here, benign** |
|
||||
|
||||
D1–D3 are the ones that can plausibly produce "alpha paints across pixels
|
||||
whose true occluder is in front of it". D4 and D9 are the ones that can
|
||||
plausibly produce the **new** post-slice-6 report (#3, "particles within the
|
||||
cathedral are also wrong"). D6 is the whole of the "stalks" half.
|
||||
|
||||
---
|
||||
|
||||
## 5. Per-commit verdicts
|
||||
|
||||
### `37febd1f` — FW4 slice 1, interior outside-view slices from the walk
|
||||
**KEEP.** The diagnosis is sound and the mechanism is retail's: retail has
|
||||
exactly one visibility structure, and having the walk decide *whether*
|
||||
terrain draws while `PortalVisibilityBuilder` decided *where* is a state
|
||||
that retail cannot produce. `ClipFrameAssembler.ReassembleOutsideViewFromWalk`
|
||||
is the right seam. The punch-fan index aliasing repair is real.
|
||||
**Correction owed:** it left the *silent* fall-open at
|
||||
`RetailPViewPassExecutor.WalkLeaf.cs:146-148` — an out-of-range
|
||||
`activeViewIndex` produces an unclipped punch with no log. That violates the
|
||||
campaign's own binding rule 3 ("fail loud, never silently drop") and is the
|
||||
same bug class the commit was fixing.
|
||||
|
||||
### `c40aecfc` — FW4 slice 2, exit seals stamp the walk's own flood
|
||||
**KEEP the mechanism; the stated diagnosis was wrong.** Sealing from
|
||||
`cell_draw_list` is exactly `0x005a49b7`, and taking that list from the walk
|
||||
rather than the old apparatus is correct. But the commit message's causal
|
||||
claim — "the falls … submit POST-depth-clear on interior roots … the drain
|
||||
z-passed across the unsealed opening" — is falsified by its own successor
|
||||
evidence: after slices 4–5 the falls submit and drain *pre*-clear and the
|
||||
symptom is unchanged.
|
||||
**Corrections owed:** (i) seals must be gated on `ov>0` (D2); (ii) retail's
|
||||
seal is the authored polygon under the cell's `setup_view`, not a hardware
|
||||
slice clip — the "old assembly's per-cell slice clips where present" path is
|
||||
an invented hybrid; (iii) the commit says it seals "unclipped where the old
|
||||
apparatus had no entry (benign)" — that inconsistency means two different
|
||||
seal geometries exist in one frame depending on an unrelated data structure's
|
||||
contents.
|
||||
|
||||
### `5f7ccdea` — FW4 slice 3, outside-stage predicate reads the walk flood
|
||||
**KEEP; wrong target.** Feeding `DynamicDrawsInOutsideStage` the walk's
|
||||
`VisitedCells` instead of the old flood is right for the same one-visibility-
|
||||
structure reason. But the commit identifies "the same two emitter entities
|
||||
(f4243/f4285)" as the falls. The probes show `f4243`/`f4285` are **dynamic
|
||||
entities of radius ≈9.8-10 m** parented at `f4180104`/`f4180106`
|
||||
(`fw4-slice3-gate.log`, `fw4-slice6-gate.log`: `[walk-dyn] … id=f4243
|
||||
parent=f4180104 r=9.8 outside=1 cone=1`), while the falls are the four
|
||||
`4f41801[2-5]` cell statics. Slice 3 fixed a real desync for the wrong
|
||||
objects, and its success criterion was therefore never testable.
|
||||
|
||||
### `f3a03efc` — FW4 slice 4, straddler dynamics emit pre-clear, once
|
||||
**REVERT.** Three independent problems:
|
||||
1. **Wrong objects** — same misidentification as slice 3.
|
||||
2. **Invented rule.** Retail has no "exit-plane straddler" concept. An
|
||||
object's parts are in every shadow cell its sphere overlaps
|
||||
(`AddPartToShadowCells` @0x0051d126); membership, not a plane test,
|
||||
decides where it draws.
|
||||
3. **Contradicts the decomp.** The commit's core invariant is "every owner
|
||||
emits exactly once". Retail deliberately bumps `m_nFrameStamp` at
|
||||
`0x005a4886` — *after* the pre-clear drain — precisely so that a part
|
||||
drawn during `LScape::draw` **draws again** in the interior pass. The
|
||||
`ExceptWith(_preClearParticleOwnerScratch)` subtractions at
|
||||
`RetailPViewRenderer.cs:2184` and `:2322` implement the opposite of
|
||||
retail's rule.
|
||||
|
||||
### `0d6cd5c0` — FW4 slice 5, straddling cell statics' particles emit pre-clear
|
||||
**REVERT, but keep the insight.** The retail citation is the right one —
|
||||
`DrawBlock` pc:430056-430064 / the shadow-cell draw *is* how an object
|
||||
overlapping outdoor landcells reaches the landscape scope. The implementation
|
||||
is not that rule: it is `SphereStraddlesExitPlane` over the walk's **flood**
|
||||
cells (`RetailPViewRenderer.cs:604-626`), gated with
|
||||
`if ((cellId & 0xFFFF) < 0x100 || _lookInCellIds.Contains(cellId)) continue;`
|
||||
— it **explicitly skips look-in cells**. On the failing frames
|
||||
`0xF4180106` is a *look-in* cell (`fw4-slice6-gate.log:12786` shows it in
|
||||
`walkCells` while the root flood at `:10352` does not contain it), so this
|
||||
slice does not touch the falls at all. Its own probe evidence shows the falls
|
||||
arriving on the `cell-owners` route, not the `static-owners n=12
|
||||
ids=[4f418007…]` straddler set it created.
|
||||
|
||||
### `bb9212c7` — FW4 slice 6, interior roots draw landscape unclipped
|
||||
**KEEP THE SHAPE, FIX THE CULL, RETRACT THE PREMISE.**
|
||||
- **Right:** collapsing slice 1's per-view terrain fan to **one** terrain
|
||||
turn is a genuine correction — `LScape::draw` runs its block walk once, and
|
||||
the active views feed only `draw_check_blocks`. Removing the hardware clip
|
||||
and scissor is also retail-correct ("retail never clips ordinary meshes per
|
||||
view").
|
||||
- **Wrong premise:** "at the #456 cathedral seam those authored portal polys
|
||||
are thin BANDS far narrower than the real opening" is **refuted by the
|
||||
DAT** (§3.2): `0xF4180104`'s exits are full ±12.00 m faces and 7-vertex
|
||||
arches; `0xF4180106`'s exit is a 4-vertex face. The narrow bands are a
|
||||
property of our clip-slice construction, not of the authored geometry —
|
||||
which means the *slice builder* is the thing to look at, and slice 6
|
||||
papered over that by removing the consumer instead.
|
||||
- **Regression risk:** it replaced "clipped too tight" with "**not culled at
|
||||
all**". Retail restricts the landscape to `in_view` landcells; the walk
|
||||
already computes exactly that set (`RetailFrameWalk.DrawLandscape` calls
|
||||
`landscape.CheckBlocks(ctx.CyPlane, activeViews)` at
|
||||
`RetailFrameWalk.cs:152` and emits per-cell turns), and the leaf renderer
|
||||
**ignores it**. Two consequences: terrain colour is painted over the whole
|
||||
frustum before the clear (so any hole in the interior repaint shows world
|
||||
where retail would show only the aperture), and
|
||||
`TerrainModernRenderer.VisibleCellIds` — which feeds next frame's
|
||||
`ShouldDrawParticles` cell-in-view gate — is widened to the whole frustum.
|
||||
Both are live candidates for the owner's new report #3.
|
||||
|
||||
---
|
||||
|
||||
## 6. The "stalks" — a separate, already-solved defect
|
||||
|
||||
The lake-edge marker "stalks" are not part of the falls story and do not
|
||||
need a new mechanism. `fce250ec` ("port retail marker-class degrade
|
||||
admission") implements the decomp rule — `get_degrade` @0x0051E4B0 selects a
|
||||
per-part LOD off the CYpt viewer distance, and both `DrawBuilding`
|
||||
@0x0059f2a0 (`gfxobj[deg_level] != 0`, verified above at `0059f2d3`) and
|
||||
`CPhysicsPart::Draw` @0x0050D7A0 skip the draw when the selected id is 0;
|
||||
spawn markers author `{self at MaxDist=0, id 0 to infinity}` and are
|
||||
therefore **never** drawn by retail at any distance.
|
||||
|
||||
`git merge-base --is-ancestor fce250ec bb9212c7` returns **NO**. That commit
|
||||
lives only on the quarantined `claude/cathedral-rendering-debug-47a417`.
|
||||
Nothing on the FW branch replaces it: the FW1 degrade work
|
||||
(`e23a589a`, `c1a029ed`, `b3ac5872`, all ancestors) selects a building's
|
||||
drawing-BSP inside `WalkBuilding` only (`WalkBuildingFactory.cs:87-120`,
|
||||
`WalkBuilding.cs:101-114`); no degrade gate is applied to cell statics or
|
||||
dynamics anywhere in `src/` (`grep -n "Degrade" Walk/*.cs` and
|
||||
`WalkStaticStreamPopulator.cs` — zero hits in the populator).
|
||||
|
||||
The DAT confirms the class is present here: ordinals 1–7 of landblock
|
||||
`0xF418`'s outdoor object list are model `0x0200085A`, the known marker
|
||||
Setup.
|
||||
|
||||
**Caveat, honestly flagged:** the prompt describes the observed stalks as
|
||||
"server-spawned weenies, e.g. entities parented at `0xF418010D`". Weenies
|
||||
carry their own Setup, so whether restoring `fce250ec` covers *these*
|
||||
particular stalks depends on the Setup ACE gives them.
|
||||
`claude-memory/project_pview_visibility_campaign.md` already flags this:
|
||||
"If stalk-like objects still show at a gate, they're server weenies with a
|
||||
different model: identify by guid." **UNVERIFIED** until someone logs the
|
||||
guid → Setup → DIDDegrade chain for one visible stalk.
|
||||
|
||||
---
|
||||
|
||||
## 7. Why the symptom survived all six fixes
|
||||
|
||||
This is a method failure. Five specific causes, each with evidence:
|
||||
|
||||
1. **The object was never identified before the fixes started.** Slice 2
|
||||
called the falls "cell-owners route" emitters, slice 3 named
|
||||
`f4243`/`f4285` (dynamics), slice 4 called them "interior-parented
|
||||
exit-plane straddler DYNAMICS", slice 5 called them "EnvCell STAB-LIST
|
||||
statics". Only slice 5 was close, and even it used the wrong sense of
|
||||
"stab" — in the DAT, `numStabs`/`stab_list` is the **visible-cell** list
|
||||
(`references/ACE/Source/ACE.DatLoader/FileTypes/EnvCell.cs:36-68`;
|
||||
our `WalkCell.StabList` is cell ids, `WalkCellFactory.cs:105`), while the
|
||||
placed objects are `StaticObjects`. The four falls were identifiable from
|
||||
`fw3-falls-probe.log` — which existed **before slice 2** — in one grep.
|
||||
|
||||
2. **The depth buffer was never measured.** Every slice reasoned about depth
|
||||
from code reading. Slice 6's own message asserts "the depth they test
|
||||
against is EMPTY" as a conclusion, not a measurement. The probe apparatus
|
||||
answers *when* (`phase=pre/post`) and *who* (`ids=[…]`) but has no verb
|
||||
for *what depth is at these pixels*. The single question that would
|
||||
discriminate between D1, D3, D4 and "the particle's own depth is wrong"
|
||||
has never been asked of the machine.
|
||||
|
||||
3. **Once `phase=pre` was achieved, the hypothesis space collapsed.** By
|
||||
slice 5 the falls demonstrably drained pre-clear
|
||||
(`fw4-slice6-gate.log:12793-12801`). At that point the ordering axis was
|
||||
closed and every further ordering change was necessarily inert. Slice 6
|
||||
correctly inferred "then the depth must be empty" but then guessed at
|
||||
*why* instead of measuring, and guessed a DAT fact that is false.
|
||||
|
||||
4. **Two retail citations were mis-scoped, and nothing caught it.** Slice 4's
|
||||
"emit exactly once" is the opposite of `m_nFrameStamp += 1` @0x005a4886.
|
||||
Slice 6's "thin bands" is contradicted by four lines of A8CellAudit
|
||||
output from an already-built tool. The campaign's binding rule 2 ("grep
|
||||
named first … pseudocode notes before C#") was followed for the *shape*
|
||||
of the mechanism and skipped for the *specific numbers*.
|
||||
|
||||
5. **Six commits in 105 minutes with no gate between them.** `37febd1f`
|
||||
18:21 → `bb9212c7` 20:06. Each was validated by hermetic tests only
|
||||
(6,762/0), which cannot see a depth-composition defect. Each slice's
|
||||
premise was built on the previous slice's *unvalidated* conclusion, so the
|
||||
errors compounded: slice 4 built on slice 3's misidentification, slice 5
|
||||
built on slice 4's invented rule, slice 6 built on slice 5's non-effect.
|
||||
The campaign's own rule 4 ("the transition checklist is a standing
|
||||
self-run gate … before the owner sees it") was not exercised between
|
||||
slices.
|
||||
|
||||
---
|
||||
|
||||
## 8. Recommended fix plan
|
||||
|
||||
**Ground rule: no code change until Stage 0 produces a number.** The
|
||||
investigation has spent six rounds on plausible mechanisms; the cheapest
|
||||
remaining move is to make the frame observable.
|
||||
|
||||
### Stage 0 — instrument (no behaviour change)
|
||||
|
||||
**P1 — depth checkpoint readback.** Under a new
|
||||
`ACDREAM_PROBE_WALK_DEPTH=1` (documented in `docs/launch-options.md` in the
|
||||
same commit, per the binding rule), capture the depth attachment at three
|
||||
points in an interior-rooted frame: (i) after the terrain turn, (ii) after
|
||||
the last building turn, (iii) immediately before `passes.FlushLandscapeAlpha()`
|
||||
in the `clearInteriorDepth` closure (`RetailPViewRenderer.cs:356`). Emit a
|
||||
downsampled 64×36 depth grid per checkpoint plus a per-pixel min/max.
|
||||
*Falsifiable prediction (D1/D3):* at checkpoint (iii), the screen cells where
|
||||
the falls bleed hold ≈`0.999999` (punched far-Z), while checkpoint (i) holds
|
||||
real terrain depth at the same cells. If (iii) already holds real terrain
|
||||
depth, **D1 and D3 are dead** and the residual is the particles' own depth —
|
||||
which the same dump measures by also logging each drained falls submission's
|
||||
projected NDC z.
|
||||
|
||||
**P2 — punch census.** Per punch fan, log building id, portal index,
|
||||
`activeViewIndex`, `slices.Length`, whether `planes` was empty (the silent
|
||||
fall-open), and the fan's screen-space AABB.
|
||||
*Falsifiable prediction:* on failing frames at least one punch of building
|
||||
`0x01001FB3` (the owner of `0xF4180106`) is emitted with `planes` empty and a
|
||||
screen AABB covering the falls' region.
|
||||
|
||||
**P3 — one grep, zero code:** dump `EnvCell 0xF4180106`'s `StaticObjects`
|
||||
(a `stabs` verb on `A8CellAudit`, ~20 lines) to get the four falls objects'
|
||||
Setup/GfxObj/DIDDegrade ids, and do the same for one visible stalk's weenie
|
||||
Setup. This closes the two UNVERIFIED items in §3.4 and §6 and tells you
|
||||
whether D6 alone fixes the stalks.
|
||||
|
||||
### Stage 1 — decomp-provable repairs (each its own commit, each gated)
|
||||
|
||||
| id | change | retail pin |
|
||||
|---|---|---|
|
||||
| **R1** | `AlphaBarrier` becomes a **full** `FlushLandscapeAlpha()`. Retire `FlushLandscapeAlphaFartherThan` from production. | `0x0059f30b` — `FlushAlphaList(0f)`, unconditional, inside the degrade gate |
|
||||
| **R2** | Gate `ClearInteriorDepth` **and** `ExitSeals` on `outsideView.ViewCount > 0`; delete the "unconditional" doc claim at `WalkFrameDriver.cs:695-699`. | `0x005a4852` opens the block; `0x005a48a9` and `0x005a49b7` are both inside it |
|
||||
| **R3** | Punch the **unclipped** authored polygon; delete the `slices[activeViewIndex]` clip. Make an out-of-range `activeViewIndex` throw, not silently fall open. | `0x005a5a7b` draws `esi` (the raw `CPolygon*`); the clip result feeds only `copy_view` |
|
||||
| **R4** | Terrain draws the walk's own `in_view` landcell set (already computed by `WalkLandscape.CheckBlocks`), whole and unclipped. Retire the frustum-only interior path. | `0x00505f80` `draw_check_blocks`, `0x005050a0` `landcell_check` |
|
||||
| **R5** | Drop `SortRetailOrder` once the walk owns submission order end to end; drain in insertion order. | `0x0059d2e0` has no sort; `0x0059c230` stores no depth key |
|
||||
| **R6** | Cherry-pick `fce250ec`'s `RetailDegradePolicy` + `WbMeshAdapter.IsNeverDrawnByDegrade` onto this branch and apply it in the walk's static populator and the dynamic classifier. | `get_degrade` @0x0051E4B0; `gfxobj[deg_level] != 0` at `0x0059f2d3` and `CPhysicsPart::Draw` @0x0050D7A0 |
|
||||
| **R7** | Replace `SphereStraddlesExitPlane` + the "emit exactly once" subtractions with retail's rule: an object submits at **every cell it is a shadow member of**, with a frame-stamp dedupe that is **re-armed after the pre-clear drain**. | `AddPartToShadowCells` @0x0051d126; `m_nFrameStamp += 1` @0x005a4886; `GetDrawnThisFrame`/`SetDrawnThisFrame` @0x0059f388 |
|
||||
|
||||
**Ordering:** R2 and R3 are cheap and decomp-unambiguous — land them first;
|
||||
they may change the symptom on their own and they make P1/P2's readings
|
||||
easier to interpret. R1 next (it is the mechanism D1 names). R4 next (it also
|
||||
closes the D9 widening). R7 replaces slices 4 and 5 wholesale, so revert
|
||||
`f3a03efc` and `0d6cd5c0` as part of it rather than layering. R5 and R6 are
|
||||
independent and can land any time.
|
||||
|
||||
**Gate for each:** the two-checkpoint depth dump from P1 at the cathedral
|
||||
pose, plus the campaign's own transition checklist (portal-in, Holtburg house
|
||||
exit, teleport stopwatch, steady-state fps) — rule 4, which was skipped for
|
||||
all six slices. Do **not** stack two of these before an owner look.
|
||||
|
||||
### What NOT to do
|
||||
|
||||
- Do not add a seventh ordering fix. The ordering axis is closed
|
||||
(`fw4-slice6-gate.log:12793-12801`).
|
||||
- Do not suppress the falls emitters, add a depth bias, force particle depth
|
||||
writes, or scissor the drain. All are symptom-site workarounds and all are
|
||||
already on the render digest's DO-NOT-RETRY list in spirit.
|
||||
- Do not reintroduce per-view terrain clipping. Slice 6 is right that retail
|
||||
culls rather than clips; the fix is to add the cull, not to restore the
|
||||
clip.
|
||||
|
||||
---
|
||||
|
||||
## 9. Open questions
|
||||
|
||||
1. **What depth is actually at the failing pixels at the pre-clear drain?**
|
||||
Unanswered. This is the whole investigation in one question and Stage 0/P1
|
||||
answers it in one run.
|
||||
2. **Setup/GfxObj/DIDDegrade of `0xF4180106`'s four static objects.**
|
||||
UNVERIFIED — needs a `stabs` verb on `A8CellAudit` (no build of production
|
||||
code required).
|
||||
3. **Where does the lake water surface live** — landcell terrain polys, or a
|
||||
separate surface with its own pass? If it is a separate translucent pass,
|
||||
it shares the alpha list with the falls and their relative order is decided
|
||||
by D5 (our sort) rather than by the walk. UNVERIFIED.
|
||||
4. **The `DrawBlock` alpha-valve polarity** (`test ah,0x41` @0x005a19fc).
|
||||
I read the idiom, not the raw bytes. Both readings make the valve inert at
|
||||
default `flush`, so it is not load-bearing here, but the FW pseudocode doc
|
||||
still lists it as owed and it should be closed from the binary.
|
||||
5. **Are the observed stalks the marker class?** Depends on the weenie Setup
|
||||
ACE assigns. If not, `fce250ec` will not cover them and the guid → Setup
|
||||
chain must be logged live.
|
||||
6. **Why are our clip slices "thin bands" when the authored polygons are full
|
||||
faces?** Slice 6 removed the *consumer* of those slices for terrain, but
|
||||
the slices still drive the punch fans and the dynamics apertures. If the
|
||||
slice builder is producing bands from full-face polygons, that is a defect
|
||||
in `ClipFrameAssembler`/`ClipPlaneSet`/`WalkScreenClip` that outlives this
|
||||
bug. Worth one focused offline check against `0xF4180104` poly 36 (a
|
||||
4-vertex ±12.00 m face) — project it and compare against the produced
|
||||
slice.
|
||||
7. **Does the one-frame lag in particle visibility matter here?**
|
||||
`WorldSceneRenderer.cs:441-447` marks visible cells at frame end for the
|
||||
next frame, while retail asks `cell->IsInView()` on the current frame.
|
||||
Combined with slice 6's frustum-wide `VisibleCellIds`, this is the most
|
||||
likely source of the owner's new "particles within the cathedral" report,
|
||||
but I have no capture of that symptom to test against. UNVERIFIED.
|
||||
|
||||
---
|
||||
|
||||
### Appendix — commands used (all read-only)
|
||||
|
||||
```
|
||||
git show --stat 37febd1f c40aecfc 5f7ccdea f3a03efc 0d6cd5c0 bb9212c7
|
||||
git merge-base --is-ancestor fce250ec bb9212c7 # -> NO
|
||||
tools/A8CellAudit/bin/Release/net10.0/A8CellAudit.exe portals F4180104|F4180106|F418010F
|
||||
curl -s "http://127.0.0.1:8081/decompile_function?address=0x0059d2e0"
|
||||
grep -n "FlushAlphaList|AddMeshToAlphaList|DrawPortalPolyInternal|ShouldDrawParticles" \
|
||||
docs/research/named-retail/acclient_2013_pseudo_c.txt
|
||||
grep/awk over %LOCALAPPDATA%\Temp\fw3-falls-probe.log, fw4-slice3-gate.log,
|
||||
fw4-slice5-gate.log, fw4-slice6-gate.log, fw4-emit-probe.log
|
||||
```
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
# Cathedral rendering review — independent troubleshooting prompt
|
||||
|
||||
You are an independent reviewer investigating a persistent rendering defect
|
||||
in **acdream** (a modern C# .NET 10 Asheron's Call client; the code is
|
||||
modern, the behavior must be retail). This is a **report-only**
|
||||
investigation: read anything, run read-only commands (git log/diff/show,
|
||||
grep, dat inspection), but do NOT edit code, do NOT build, do NOT launch
|
||||
clients, and do NOT commit. Your single deliverable is a written report
|
||||
(the path is given at the end).
|
||||
|
||||
## Where the code is
|
||||
|
||||
- Git worktree (the active campaign branch, work here):
|
||||
`C:\Users\erikn\source\repos\acdream\.claude\worktrees\peaceful-blackburn-5333f0`
|
||||
- Branch: `claude/campaign-w-retail-frame-walk`
|
||||
- HEAD at review time: `bb9212c7` (FW4 slice 6)
|
||||
- **Baseline before ANY cathedral-specific fix: `37febd1f`** — the owner
|
||||
first reported this defect on a build of that commit. Six fix commits
|
||||
followed (see below). The owner's verdict: *nothing has fixed anything*.
|
||||
- Main repository (for main-branch history): `C:\Users\erikn\source\repos\acdream`
|
||||
|
||||
## The defect (owner-observed, on a live ACE server at 127.0.0.1:9000)
|
||||
|
||||
At the cathedral terrace, landblock `0xF418`, standing in interior EnvCells
|
||||
`0xF4180104` / `0xF4180106` (owner teleloc: `0xF4180104 [31.9 57.3 169.8]`):
|
||||
|
||||
1. **Waterfall particles** (the blue falls over the lake outside) render on
|
||||
top of terrain, water, and other content that should occlude them.
|
||||
Intermittent — toggles with which side of "the seam" (the terrace's
|
||||
interior/outdoor boundary) the CAMERA is on. Standing outdoors near the
|
||||
falls (`0xF418000B`, `0xF4180015`) they look correct.
|
||||
2. **"Stalks"** (dark monster-marker shapes near the lake-edge trees,
|
||||
server-spawned weenies, e.g. entities parented at `0xF418010D`) leak
|
||||
over trees and scenery. This predates the current campaign.
|
||||
3. NEW (after the sixth fix): the owner reports **particles WITHIN the
|
||||
cathedral are also wrong** (uncharacterized — treat as a regression
|
||||
candidate of the fix stack).
|
||||
4. A previous session's analysis (owner-supplied) adds: the visible
|
||||
cathedral shell is assembled from multiple authored GfxObjs
|
||||
(`0x01001FB2`, `0x01001FB0`, `0x01001FB3`) + EnvCells + a portal graph;
|
||||
cell `0xF4180104` has exterior portals 0/1/2/4 and interior portal 3 →
|
||||
`0xF4180105`; "if an incorrect depth or portal route prevents one
|
||||
wall's colour pass from drawing, the wall appears transparent even
|
||||
though its geometry and opaque texture are present."
|
||||
|
||||
**The oracle fact that anchors everything: the RETAIL client connected to
|
||||
the SAME ACE server renders this scene perfectly.** So a correct
|
||||
client-side mechanism exists; the task is to find what retail actually
|
||||
does for this scene and where our port diverges.
|
||||
|
||||
## What you must do
|
||||
|
||||
1. **Reconstruct retail's actual frame for this scene** from the named
|
||||
decomp: `docs/research/named-retail/acclient_2013_pseudo_c.txt`
|
||||
(18,366 PDB-named functions; grep by `class::method`) and
|
||||
`docs/research/named-retail/acclient.h`. Key functions:
|
||||
`SmartBox::RenderNormalMode` @0x00453aa0, `PView::DrawInside` @0x005a5860,
|
||||
`PView::DrawCells` @0x005a4840 (landscape draw pc:432719, gated depth
|
||||
clear pc:432731-2, exit seals pc:432785-6), `LScape::draw` @0x00506330,
|
||||
`RenderDeviceD3D::DrawBlock` @0x005a17c0 (per-shadow-cell object draw
|
||||
pc:430056-430064), `RenderDeviceD3D::DrawSortCell` @0x0059f140,
|
||||
`RenderDeviceD3D::DrawBuilding` @0x0059f2a0,
|
||||
`CPhysicsObj::ShouldDrawParticles` @0x0050FE60,
|
||||
`D3DPolyRender::FlushAlphaList` @0x0059d2e0, `PView::GetClip` @0x005a4320,
|
||||
`PView::InitCell` @0x005a4b70. A Ghidra MCP HTTP server may be live at
|
||||
`http://127.0.0.1:8081` (`/decompile_function?address=0x...`) serving the
|
||||
same 2013 build with PDB names — use it when grep is too noisy.
|
||||
Answer specifically: for an interior root whose cell has exit portals,
|
||||
(a) what exactly gets view-clipped vs view-culled vs drawn whole;
|
||||
(b) when does each content class (terrain, sky, building shells,
|
||||
EnvCell shells, cell-static objects + their emitters, dynamic objects +
|
||||
their emitters, unattached emitters, water) draw relative to the gated
|
||||
depth clear and the exit seals; (c) how does the ONE alpha list drain
|
||||
(which flush sites, what depth is in place at each); (d) what depth
|
||||
does the frame hold at the aperture and at the vista at final-drain time.
|
||||
|
||||
2. **Read the cathedral's actual geometry from the installed DATs**
|
||||
(`%USERPROFILE%\Documents\Asheron's Call\`, DatCollection is the
|
||||
in-tree reader; `references/ACViewer` is a rendering oracle):
|
||||
landblock `0xF418` — the LandblockInfo object list, the EnvCells
|
||||
`0x0100`-`0x0115`-ish (esp. `0x0104`/`0x0105`/`0x0106`), their portal
|
||||
polygons (which are exits, flag 0x4 → other_cell 0xFFFF), their
|
||||
stab lists (the falls objects: local static ids `0x4F418012-15` were
|
||||
observed as the falls' particle owners; setup/gfx ids of waterfall
|
||||
objects), and where the water surface geometry lives. Establish:
|
||||
are the falls objects EnvCell stabs, landblock objects, or both?
|
||||
What shape/extent are the exit portal polygons vs the real visible
|
||||
opening (the prior campaign claims they are thin "seam bands")?
|
||||
|
||||
3. **Audit our implementation against that reconstruction**, at BOTH
|
||||
`37febd1f` (baseline) and `bb9212c7` (HEAD). The relevant code:
|
||||
`src/AcDream.App/Rendering/Walk/` (RetailFrameWalk, WalkPView,
|
||||
WalkFrameDriver, WalkViews, WalkScreenClip, WalkProductionWorldData),
|
||||
`src/AcDream.App/Rendering/RetailPViewRenderer.cs`,
|
||||
`RetailPViewPassExecutor*.cs`, `ClipFrameAssembler.cs`,
|
||||
`ClipPlaneSet.cs`, `ParticleRenderer.cs`, `RetailAlphaQueue.cs`,
|
||||
`PortalVisibilityBuilder.cs` (the legacy visibility layer FW4 is
|
||||
deleting), `Scene/RenderScenePViewFrameProduct.cs`.
|
||||
|
||||
4. **Judge the six fix commits individually** — each claims a retail
|
||||
citation; decide keep / revert / wrong-mechanism for each:
|
||||
- `37febd1f` FW4 slice 1 — interior outside-view slices from the walk
|
||||
(was: old PortalVisibilityBuilder slices; also fixed punch-fan index
|
||||
aliasing).
|
||||
- `c40aecfc` slice 2 — exit seals stamp the walk's own flood.
|
||||
- `5f7ccdea` slice 3 — outside-stage predicate reads the walk flood.
|
||||
- `f3a03efc` slice 4 — exit-plane straddler DYNAMICS' particles emit
|
||||
pre-clear once (was: deliberately last-pass).
|
||||
- `0d6cd5c0` slice 5 — straddling CELL-STATIC particles emit pre-clear
|
||||
(retail shadow-cell rule claim).
|
||||
- `bb9212c7` slice 6 — interior roots draw terrain+sky UNCLIPPED
|
||||
(view-culled only), one terrain turn (was: per-exit-view
|
||||
scissor+clip slices).
|
||||
Evidence from the live probes is in
|
||||
`%LOCALAPPDATA%\Temp\fw3-*.log` and `%LOCALAPPDATA%\Temp\fw4-*.log`
|
||||
(`[walk-root]`, `[walk-part]`, `[walk-alpha]`, `[walk-dyn]`,
|
||||
`[walk-portal]`, `[walk-emit]` lines; the probe is
|
||||
`ACDREAM_PROBE_WALK_ROOT`, documented in `docs/launch-options.md`).
|
||||
|
||||
5. **Explain why the symptom survived all six fixes**, and produce a
|
||||
concrete, mechanism-first fix plan (what to change, where, what retail
|
||||
evidence pins it, and what a falsifiable test/probe for it looks like
|
||||
BEFORE any code is written).
|
||||
|
||||
## Required background reading (in this order)
|
||||
|
||||
1. `claude-memory/project_pview_visibility_campaign.md` (the #456
|
||||
campaign memory — the proven mechanism model, the do-not-retry list;
|
||||
note: `claude-memory/` is a junction in the MAIN repo root at
|
||||
`C:\Users\erikn\source\repos\acdream\claude-memory\`; the same files
|
||||
are at `C:\Users\erikn\.claude\projects\C--Users-erikn-source-repos-acdream\memory\`)
|
||||
2. `docs/plans/2026-08-30-campaign-fw-frame-walk.md` (the active
|
||||
campaign plan — stages FW0-FW6, the FW1 conformance status, the FW4
|
||||
slice log)
|
||||
3. `claude-memory/project_render_pipeline_digest.md` (render SSOT +
|
||||
DO-NOT-RETRY table)
|
||||
4. `docs/research/2026-08-30-fw-walk-oracle/` (the ten retail cdb traces
|
||||
the walk is conformance-tested against)
|
||||
5. `docs/plans/2026-08-30-pview-visibility-campaign.md` (the abandoned
|
||||
PView campaign — what was tried and refuted)
|
||||
|
||||
## Hard rules
|
||||
|
||||
- The decomp is ground truth; ACE/ACViewer/WorldBuilder are interpretation
|
||||
aids. Where sources disagree, the decomp wins.
|
||||
- Do not propose workarounds (suppression flags, grace periods,
|
||||
symptom-site early-outs). Root mechanisms only.
|
||||
- Respect the do-not-retry lists in the memory docs (e.g., depth stamps as
|
||||
"retail's mechanism" is decomp-refuted; the panels are portal polys, not
|
||||
inert decoration).
|
||||
- Every claim in your report needs a citation: a decomp address, a DAT
|
||||
field, a file:line in our tree, a probe-log line, or a commit SHA.
|
||||
- If you cannot verify a claim, say so explicitly — an honest "unverified"
|
||||
beats a plausible guess. This investigation has already burned six fix
|
||||
rounds on plausible guesses.
|
||||
|
||||
## Deliverable
|
||||
|
||||
Write your full report to: **`docs/research/2026-08-30-cathedral-review-<YOURNAME>.md`**
|
||||
(replace `<YOURNAME>` with your model name, e.g. `fable`, `opus`,
|
||||
`external`). Structure: Executive verdict (≤10 lines) → Retail frame
|
||||
reconstruction → DAT geometry findings → Divergence table (ranked) →
|
||||
Per-commit verdicts → Why the symptom survived → Recommended fix plan →
|
||||
Open questions. Length: as long as it needs to be; completeness beats
|
||||
brevity.
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
# Cathedral rendering — the seven-report synthesis and adjudicated plan
|
||||
|
||||
2026-08-30. Synthesizes: the fable review, the opus review, the external
|
||||
model review (owner-supplied), the code+probe-log audit, the DAT-format
|
||||
report, the F418 DAT-geometry report, and the retail decomp
|
||||
reconstruction (`2026-08-30-cathedral-review-*.md`). Where reports
|
||||
disagreed, the adjudication below names the winner and the evidence.
|
||||
|
||||
## Adjudications (the disagreements, settled)
|
||||
|
||||
1. **Root kind at the terrace.** The fable review said "retail never
|
||||
roots interior at the terrace" (top-level free traces: zero DI); the
|
||||
external review cited `DI f4180104, ov=1, n=1`. BOTH are right about
|
||||
different captures: `posed/terrace-center.log` (the defect pose) shows
|
||||
39× `DI f4180104` + `DC ov=1 n=1`; the top-level free captures rooted
|
||||
outdoor. VERDICT: retail roots interior at our defect pose, with a
|
||||
ONE-CELL flood and healthy exit views. The divergence to chase is the
|
||||
FLOOD SHAPE and the exit-view survival, not the root kind.
|
||||
2. **The falls' identity.** Opus said cell-0x106 stabs; the DAT-geometry
|
||||
report proves the falls emitters are OUTDOOR LANDBLOCK OBJECTS
|
||||
(0xCF418000–13: 7 east jets Setup 0x0200085A, 7 north jets 0x02000859,
|
||||
6 lake mist 0x020003C5), and 0x4F418012–15 are cell 0x106's TORCHES +
|
||||
prop + plant. VERDICT: DAT-geometry wins (byte-exact parse); slice 5
|
||||
moved torches — the in-cathedral particle regression. Confirmed by
|
||||
the external review. Residual: trace the 0xC owners' live route
|
||||
(never directly observed in any probe — instrumentation step 1).
|
||||
3. **"Thin authored seam bands."** REFUTED by the DAT: all exits at
|
||||
0x104/0x106 are full-size (10.72×10.20 m facades, 24×9.2 m arches).
|
||||
Thin/empty exit views (the 66 ov==0 frames) are OUR
|
||||
projection/clip/traversal loss. The REAL seam: 0x104's south exit and
|
||||
0x106's north exit are the SAME coincident rectangle at y=48, EACH
|
||||
authored exit-to-outside — two abutting buildings; retail cannot
|
||||
flood between these cells (they meet only through 0xFFFF portals).
|
||||
4. **"Retail gates clear+seals on ov>0."** Refined by the decomp
|
||||
reconstruction: the WHOLE landscape→drain→stamp→clear→seals block is
|
||||
inside `ov > 0` (pc:432715), and the depth clear is additionally a
|
||||
ONE-FRAME LATCH armed by the previous frame's seals
|
||||
(`portalsDrawnCount`, pc:432725-32). Ours emits clear+seals
|
||||
unconditionally — divergence confirmed, mechanism refined.
|
||||
5. **"Stale color."** Retail full-clears COLOR+depth every frame at
|
||||
BeginScene (Clear(7), pc:68673-86). Whether our ov==0 frames show
|
||||
stale color depends on our world-pass color-load behavior —
|
||||
UNVERIFIED; the instrumentation phase settles it. Either way the
|
||||
ov==0 frames are wrong (retail has ov=1 at those poses).
|
||||
|
||||
## The frame truth (retail, interior root — the decomp reconstruction)
|
||||
|
||||
See `2026-08-30-cathedral-review-decomp.md` for the full pc-cited version.
|
||||
Load-bearing facts:
|
||||
- TWO PViews: the root's (draw_landscape=1) and the look-ins'
|
||||
(draw_landscape=0, ov always 0) — look-in floods are ISOLATED from the
|
||||
root flood and never trigger landscape/clear/seals.
|
||||
- Landscape (sky, terrain in_view-culled + drawn whole, buildings with
|
||||
punches + look-in interiors, outdoor objects INCLUDING the falls
|
||||
emitters' parts) all draws PRE-clear; the full alpha drain at
|
||||
pc:432720 resolves outdoor translucency vs outdoor depth.
|
||||
- m_nFrameStamp re-arms mid-frame (pc:432722): straddling parts draw in
|
||||
BOTH scopes. "Emit once" is anti-retail.
|
||||
- z-only clear (latch-gated) → exit seals: EVERY 0xFFFF portal of EVERY
|
||||
flooded cell, TRUE depth, DEPTHTEST_ALWAYS + write, invisible fan,
|
||||
clipped to the installed view.
|
||||
- Interior shells (whole, once, z-test only) → interior objects/emitters
|
||||
(per-view sphere-cull, whole) → final drain vs interior+seal depth.
|
||||
- Alpha lists: two FIFOs, NO sort at flush; order = the walk +
|
||||
per-cell CYpt insertion sort. Full-flush sites: DrawBuilding (0f!),
|
||||
DrawCells pre-clear, RenderNormalMode final; DrawBlock's per-cell
|
||||
0.75 pressure valve (BN misread resolved — it DOES fire).
|
||||
- ShouldDrawParticles = UPDATE-time NoDraw (distance; cell test VACUOUS
|
||||
for interior emitters — CEnvCell inherits constant PARTIALLY_INSIDE,
|
||||
PDB-verified ICF fold @0x005269f0).
|
||||
|
||||
## Why six fixes failed (unanimous)
|
||||
|
||||
Slices 1–3+6 repaired real seams of the hybrid renderer; slices 4–5
|
||||
acted on a misidentified target (torches ≠ falls) with an invented
|
||||
mechanism (sphere-straddle) — and no round measured depth at the
|
||||
artifact pixels or verified the target's identity against the DAT before
|
||||
editing. The two live defect mechanisms (the ov==0 frames; the missing
|
||||
degrade admission for marker anchors) were never in any slice's scope.
|
||||
|
||||
## Per-commit ledger (final)
|
||||
|
||||
- 37febd1f slice 1 — KEEP (transitional; the walk owns outside views;
|
||||
fixed real punch-fan index aliasing).
|
||||
- c40aecfc slice 2 — KEEP the rule (seal the walk's flood); REWORK: use
|
||||
the walk cell's installed views for the clip, not legacy per-cell
|
||||
slices/no-clip fallback (plan step 4).
|
||||
- 5f7ccdea slice 3 — KEEP mechanically for now; REWORK: VisitedCells is
|
||||
a union of root flood + look-ins — retail stages those differently
|
||||
(plan step 2 splits the sets and re-sources the predicate).
|
||||
- f3a03efc slice 4 — REVERTED (7f500b97).
|
||||
- 0d6cd5c0 slice 5 — REVERTED (7f500b97).
|
||||
- bb9212c7 slice 6 — KEEP the one-turn/no-hard-clip principle; FIX: the
|
||||
interior terrain currently uses the full camera frustum — restore
|
||||
retail's in_view landcell culling under the active exit views
|
||||
(draw_check_blocks @0x00505f80) (plan step 8).
|
||||
|
||||
## The plan (instrument-first; each step gated)
|
||||
|
||||
PHASE I — instrumentation (NO rendering edits):
|
||||
1. **Falls-owner trace**: watch owners 0xCF418000–13 live — route
|
||||
(outdoor-static vs other), walk turn, submission phase, drain point,
|
||||
projected bounds/depth. Never infer identity from aggregate sets
|
||||
again.
|
||||
2. **Depth checkpoints**: read back depth at one known-bad pixel after
|
||||
terrain / around punches / before the pre-clear drain / after clear /
|
||||
after seals / before the final drain. Distinguishes missing terrain
|
||||
depth vs punch overwrite vs clear/seal mis-gating vs particle depth
|
||||
state.
|
||||
3. **Pose conformance**: drive OUR walk at posed/terrace-center's exact
|
||||
camera. Retail truth: DI f4180104, root flood n=1, ov=1, and separate
|
||||
look-ins. Compare our root flood size, exit-view survival, and the
|
||||
VisitedCells composition (root vs look-in members). If we produce
|
||||
ov=0 or a multi-cell ROOT flood there, fix WalkPView
|
||||
projection/side/clip math FIRST — the F67–79 foundry residue is the
|
||||
same family, and the coincident y=48 double-exit plane is the prime
|
||||
suspect surface.
|
||||
|
||||
PHASE II — decomp-pinned repairs (one at a time, each with a falsifiable
|
||||
prediction and a connected check):
|
||||
4. Split the stage sets: InteriorFloodCells (root flood, post-clear) /
|
||||
look-in cells (pre-clear, landscape scope) / VisitedLandscapeCellIds —
|
||||
delete every union-based stage decision (incl. slice 3's predicate
|
||||
source and the cell-owners particle union).
|
||||
5. Gate clear+seals as retail does (the ov>0 block; note the seal-latch
|
||||
nuance for the clear).
|
||||
6. Seals from the walk cell's installed views (drop the legacy clip
|
||||
borrow + unclipped fallback).
|
||||
7. Retail particle staging: emitters draw at their owner cell's actual
|
||||
walk turn; adopt the frame-stamp boundary (mid-frame re-arm) so
|
||||
straddling parts draw in both scopes; delete SphereStraddlesExitPlane.
|
||||
8. Alpha parity: DrawBuilding barrier = FULL flush; the queue preserves
|
||||
insertion order (remove the global distance sort); keep the 0.75
|
||||
per-cell valve semantics.
|
||||
9. Terrain: whole + unclipped (slice 6) but culled by the walk's
|
||||
in_view landcell set (draw_check_blocks port).
|
||||
10. Re-land the runtime-hidden degrade admission for outdoor objects
|
||||
(the stalks = drawn never-drawn marker anchors; fce250ec is NOT an
|
||||
ancestor of this branch). If a stalk survives, log its live
|
||||
GUID→Setup→GfxObj chain.
|
||||
|
||||
PHASE III — FW4 completion: delete the legacy PortalVisibilityBuilder
|
||||
production role, legacy clip frames/viewcone feeds, packed dynamic
|
||||
routes; the walk becomes the sole frame-decision owner; architecture doc
|
||||
updated (it currently overstates single ownership).
|
||||
|
||||
## Open questions carried
|
||||
|
||||
- Depth at the leaking pixel pre-drain (Phase I.2 answers).
|
||||
- The 0xC falls owners' actual live route (Phase I.1 answers).
|
||||
- Why full-size authored portals yield thin/zero views at some poses
|
||||
(Phase I.3 localizes; the fix lands in WalkPView).
|
||||
- Our world-pass color-load behavior (stale color vs black on ov==0).
|
||||
- Seal/punch clip equivalence vs retail setup_view+polyClipFinish.
|
||||
- The ~1.5 MB/frame walk alloc tail (FW6).
|
||||
|
|
@ -1,703 +0,0 @@
|
|||
# FW1 flood decomp-read appendix - raw extraction reports (2026-08-30)
|
||||
|
||||
Second read round: the interior-flood and view-support functions. Same method as the first appendix (BN pseudo-C, Ghidra-arbitrated where flagged).
|
||||
|
||||
## Report 1 - Flood bookkeeping (InitCell / InsCellTodoList / GetVisible / curr_view_push)
|
||||
|
||||
### PView::InitCell @0x005a4b70
|
||||
|
||||
**Summary:** Initializes the cell's TOP portal_view slot (portal_view.data[num_view-1]) for the flood: stamps view_timestamp = master_timestamp, clears cell_view_done, grows the per-portal portal_info array to num_portals, classifies every portal as in-view (inflag) or rejected via a cell-local viewpoint-vs-portal-plane side test, computes max_indist = max SQUARED viewpoint distance to any in-view portal vertex, and marks every rejected portal seen=1 so the flood never traverses it. The entry portal (real index; 0xffff sentinel never matches) is forced inflag=1 + seen=1 instead of side-tested.
|
||||
|
||||
```c
|
||||
int PView::InitCell(CEnvCell* cell, uint16 entry_portal_idx) // Ghidra: returns int; BN said void
|
||||
{
|
||||
slot = cell->portal_view.data[cell->num_view - 1]; // TOP recursion slot
|
||||
if (slot->view_count == 0) return 0; // no views on this cell -> nothing to do
|
||||
|
||||
Render::positionPush(3, &cell->pos); // install CELL frame: viewpoint below is CELL-LOCAL
|
||||
slot->cell_view_done = 0;
|
||||
slot->view_timestamp = PView::master_timestamp;
|
||||
if (slot->portal.sizeOf < cell->num_portals)
|
||||
DArray<portal_info>::grow(&slot->portal, cell->num_portals); // exact-size; NEW entries UNINITIALIZED
|
||||
|
||||
float max_d2 = 0.0f;
|
||||
int any_rejected = /*UNINITIALIZED stack dword — see gotchas*/;
|
||||
|
||||
for (i = 0; i < cell->num_portals; i++) { // CCellPortal stride 0x18
|
||||
CPolygon* poly = cell->portals[i].portal;
|
||||
if (i == entry_portal_idx && slot->portal.data[i].inflag == 0) {
|
||||
// entered-through portal: forced visible + consumed (0xffff seed sentinel never hits this)
|
||||
slot->portal.data[i].inflag = 1;
|
||||
slot->portal.data[i].seen = 1;
|
||||
} else {
|
||||
slot->portal.data[i].seen = 0;
|
||||
// side of cell-local viewpoint vs portal plane (CPolygon.plane @ +0x20)
|
||||
float d = plane.N.x*vp.x + plane.N.y*vp.y + plane.N.z*vp.z + plane.d;
|
||||
int side; // 0=POSITIVE, 1=NEGATIVE
|
||||
if (d > F_EPSILON) side = 0; // F_EPSILON = 0.000199999995f
|
||||
else if (d < -F_EPSILON) side = 1;
|
||||
else { slot->portal.data[i].inflag = 0; any_rejected = 1; goto vertex_scan; } // IN_PLANE: always reject
|
||||
if (side != cell->portals[i].portal_side)
|
||||
slot->portal.data[i].inflag = 1; // viewer on the see-through side
|
||||
else { slot->portal.data[i].inflag = 0; any_rejected = 1; } // viewer on the portal's own side
|
||||
}
|
||||
vertex_scan:
|
||||
if (slot->portal.data[i].inflag == 1 && poly->num_pts > 0) // num_pts = byte @ +0xe
|
||||
for each CVertex* v in poly->vertices[0..num_pts) { // vertices = CVertex** @ +0
|
||||
d2 = (vp.x-v.x)^2 + (vp.y-v.y)^2 + (vp.z-v.z)^2; // SQUARED, cell-local, no sqrt
|
||||
if (max_d2 < d2) max_d2 = d2;
|
||||
}
|
||||
}
|
||||
slot->max_indist = max_d2; // max squared distance to any in-view portal vertex
|
||||
|
||||
if (any_rejected != 0 && slot->view_count > 0)
|
||||
for (v = 0; v < slot->view_count; v++) {
|
||||
Render::set_view(&slot->view, v); // installs global active view; the check below does NOT read it
|
||||
for (j = 0; j < cell->num_portals; j++)
|
||||
if (portal[j].inflag == 0 && portal[j].seen == 0)
|
||||
portal[j].seen = 1; // rejected portals become 'consumed': flood never walks them
|
||||
}
|
||||
|
||||
slot->update_count = slot->view_count;
|
||||
Render::positionPop();
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** BN body @0x005a4b70 is UNUSABLE: it scrambled the x87 side-test control flow AND elided the squared-distance math (showed only the z subtraction). This model is Ghidra-verified (127.0.0.1:8081). Confirmed semantics: side==portal_side rejects, IN_PLANE (|d|<=0.000199999995f) always rejects — matches the FW doc's sidedness table. Real retail quirks: (1) any_rejected (local_4) is NEVER initialized — if no portal is rejected it reads stack garbage; the effect is benign (the fixup inner body no-ops when nothing was rejected; only side effect is redundant set_view churn), so a port should init it to 0 with identical observable behavior. (2) The entry-portal branch is guarded by the STALE inflag (inflag==0) — on a freshly grown portal array inflag is heap garbage (DArray::grow does NOT zero new entries); the caller (AddViewToPortals) presumably establishes it — verify that contract when porting FW3. (3) set_view inside the fixup loop installs each view globally but nothing in the loop consults it, and the LAST view stays installed on exit — both decompilers agree; purpose unclear (possibly vestigial). (4) positionPush(3, cell->pos) means the plane test and max_indist run in CELL-LOCAL coordinates. (5) 0xffff sentinel: ushort zero-extended vs uint loop index — never matches, so the seed cell side-tests every portal. (6) max_indist is a SQUARED distance — todo-list keys fed from it are squared; comparisons stay consistent. Ghidra return type is int (0 = early-out on view_count==0, 1 = did work); BN said void __stdcall.
|
||||
|
||||
### PView::InsCellTodoList @0x005a4f50
|
||||
|
||||
**Summary:** Sorted insert into the flood todo list. Keeps cell_todo_list ordered NON-INCREASING by float distance from index 0: index 0 = FARTHEST, END = NEAREST. The new entry sinks toward index 0 past every entry with dist <= its own and stops under the first STRICTLY greater one. Since the flood pops from the END, pop order is SMALLEST-key-first (nearest-first traversal); ties pop FIFO (older equal entries first). 8-byte {cell,dist} nodes are lazily allocated once and recycled across floods; grow is batched +30 with explicit zero-fill of new slots.
|
||||
|
||||
```c
|
||||
void PView::InsCellTodoList(CEnvCell* cell, float dist)
|
||||
{
|
||||
n = this->cell_todo_num;
|
||||
if (n >= cell_todo_list.sizeOf) {
|
||||
DArray<CellListType*>::grow(&cell_todo_list, n + 0x1e); // +30; grow sets sizeOf to EXACTLY n+30
|
||||
for (k = this->cell_todo_num; k < cell_todo_list.sizeOf; k++)
|
||||
cell_todo_list.data[k] = 0; // zero-fill ALL new slots (lazy-node contract)
|
||||
}
|
||||
if (cell_todo_list.data[n] == 0)
|
||||
cell_todo_list.data[n] = new CellListType{cell=0, dist=0}; // 8 bytes; alloc-once, recycled, never freed here
|
||||
node = cell_todo_list.data[n];
|
||||
node->cell = cell; node->dist = dist;
|
||||
|
||||
pos = n;
|
||||
while (pos > 0) {
|
||||
prev = cell_todo_list.data[pos - 1];
|
||||
if (dist < prev->dist) break; // STRICT less stops the sink [GHIDRA polarity — BN was INVERTED]
|
||||
cell_todo_list.data[pos] = prev; // shift the <=-dist entry toward the END
|
||||
pos--;
|
||||
}
|
||||
cell_todo_list.data[pos] = node;
|
||||
this->cell_todo_num++;
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** LOAD-BEARING BN INVERSION: BN's x87 flag mush (test ah,0x5) read as shift-while-LESS, which would produce an ascending list and a farthest-first pop. Ghidra (authoritative, verified live) shows `if (dist < prev->dist) break` — shift while dist >= prev->dist — descending list, END = nearest, pop-from-END = NEAREST-first flood. This reconciles exactly with DrawCells walking cell_draw_list from the end for far-to-near: cells append to the draw list in pop order (near->far), so end-first walk = far-to-near. Ties: equal-dist predecessors get shifted (>= shifts), so the newcomer lands closer to index 0 and pops AFTER existing equals — FIFO among ties (seed at dist 0 always pops first). No dedup: the same cell can be enqueued multiple times; dedup is the caller's job (ConstructView/AddViewToPortals). Nodes are pointer-recycled and permuted by the shift, never freed; the explicit zero-fill after grow is what makes the data[n]==0 lazy-alloc test sound (DArray::grow itself does NOT zero and sets sizeOf to exactly the requested count — the +30 batching is caller-side).
|
||||
|
||||
### CEnvCell::GetVisible @0x0052dc10
|
||||
|
||||
**Summary:** Static resident-cell registry lookup: walks the bucket chain of CEnvCell::visible_cell_table (intrusive hash keyed by cell id, bucket = (uint64)cell_id % numBuckets) and returns the CEnvCell* whose node key matches, or null if the cell is not currently loaded/visible. Node layout {key @+0, next @+4, CEnvCell* value @+8}.
|
||||
|
||||
```c
|
||||
static CEnvCell* CEnvCell::GetVisible(uint32 cell_id)
|
||||
{
|
||||
for (node = visible_cell_table.m_intrusiveTable.m_buckets[(uint64)cell_id % m_numBuckets];
|
||||
node != 0; node = node->next) // next @ +4
|
||||
if (node->key == cell_id) // key @ +0
|
||||
return node->value; // CEnvCell* @ +8
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** Trivial and unambiguous; both decompilers agree. BN's COMBINE(0, arg1) is just unsigned zero-extension of the 32-bit key for the modulo. The redundant post-match null re-check in both decomps (BN 'if (i != 0)' / Ghidra's second null test) is decompiler noise from the shared return path — one lookup, no side effects.
|
||||
|
||||
### CEnvCell::curr_view_push @0x005a5090
|
||||
|
||||
**Summary:** Pushes one view-recursion level onto the cell: ensures portal_view has a slot at index num_view (exact-size grow to num_view+1, nulling just the ONE new slot), lazily allocates a 0x48-byte portal_view_type on first use (three empty DArrays with blocksize 0x80: portal_info list, view.poly, view.vertex; view_timestamp=0), then unconditionally resets EXACTLY three counters — view_count=0, update_count=0, view_timestamp=0 — and increments num_view. Slots are recycled across frames (never freed here); their DArrays keep capacity.
|
||||
|
||||
```c
|
||||
void CEnvCell::curr_view_push()
|
||||
{
|
||||
if (num_view >= portal_view.sizeOf) {
|
||||
DArray<portal_view_type*>::grow(&portal_view, num_view + 1); // grow sets sizeOf EXACTLY num_view+1
|
||||
portal_view.data[num_view] = 0; // null only the single new slot
|
||||
}
|
||||
if (portal_view.data[num_view] == 0) { // lazy first-use alloc
|
||||
s = operator new(0x48);
|
||||
if (s) {
|
||||
s->portal = {data=0, blocksize=0x80, next_available=0, sizeOf=0}; // DArray<portal_info>
|
||||
s->view.poly = {data=0, blocksize=0x80, next_available=0, sizeOf=0};
|
||||
s->view.vertex = {data=0, blocksize=0x80, next_available=0, sizeOf=0};
|
||||
s->view_timestamp = 0;
|
||||
// NOT initialized: view.vertex_count_total, max_indist, view_count, cell_view_done, update_count
|
||||
}
|
||||
portal_view.data[num_view] = s;
|
||||
}
|
||||
slot = portal_view.data[num_view];
|
||||
slot->view_count = 0;
|
||||
slot->update_count = 0;
|
||||
slot->view_timestamp = 0;
|
||||
num_view += 1;
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** VERIFIES the earlier read: num_view++ with 0x48-byte lazy slot alloc + counter resets — with precision: exactly view_count/update_count/view_timestamp are reset on EVERY push (fresh or recycled); cell_view_done and max_indist are NOT reset here and stay stale (heap garbage on a brand-new slot) until PView::InitCell writes them — InitCell always runs before they are consumed, but a port must preserve that ordering or zero them harmlessly. The single-slot null after grow is sound only because this DArray grow(n) sets sizeOf to EXACTLY n (verified @0x005a45d0: copies old, sizeOf=arg; blocksize unused by grow; grow with arg<=sizeOf delegates to shrink) — no hidden capacity slack, so no garbage slots. portal_view_type layout confirmed in acclient.h: {DArray<portal_info> portal @0; view_type view @0x10 (vertex_count_total, poly@0x14, vertex@0x24); max_indist @0x34; view_count @0x38; cell_view_done @0x3c; view_timestamp @0x40; update_count @0x44}; DArray = {data, blocksize, next_available, sizeOf}.
|
||||
|
||||
**Report notes:** All four bodies cross-checked against live Ghidra MCP (http://127.0.0.1:8081, patchmem.gpr) — mandatory here, because BN got two of them materially wrong: (1) InsCellTodoList's insertion comparison was polarity-INVERTED in BN (would have modeled a farthest-first pop); Ghidra's strict `dist < prev->dist` break gives a descending-from-index-0 list whose END-pop is NEAREST-first, which is what makes the draw list come out near->far and DrawCells' end-first walk far-to-near — consistent with FW doc section 5. (2) InitCell's BN body scrambled the plane-side branches and elided the dx^2+dy^2+dz^2 accumulation entirely (showed a bare z subtraction). Ghidra-confirmed model: side 0/1 vs portal_side rejects on equality, IN_PLANE always rejects (matches the doc's section 7 sidedness table), max_indist = max SQUARED cell-local distance to in-view portal vertices, and rejected portals get seen=1 in a fixup pass whose per-view set_view calls are side-effect-only. Two genuine retail quirks worth register-awareness if ported observably: InitCell's any_rejected flag is an uninitialized stack read (benign in effect), and the entry-portal force-visible branch keys off STALE inflag whose state is a caller contract (read PView::AddViewToPortals before relying on it in FW3). Struct authorities verified in acclient.h: portal_info {seen, inflag}; portal_view_type (0x48 bytes, field offsets in the curr_view_push gotchas); PView {outside_view, draw_landscape, outdoor_portal_list, cell_draw_list, cell_draw_num, cell_todo_list, cell_todo_num, lscape}; CellListType nodes are 8-byte {CEnvCell* cell, float dist}; CPolygon {vertices@0, num_pts byte@0xe, plane@0x20}. Sources: docs/research/named-retail/acclient_2013_pseudo_c.txt lines 311378-311393, 432896-433045, 433183-433243, 433279-433320; DArray grow/shrink @0x005a45d0 region; struct defs in docs/research/named-retail/acclient.h (portal_info @32458, portal_view_type @32346, view_type @32338, PView @45934, CPolygon @31855).
|
||||
|
||||
## Report 2 - Flood propagation (ClipPortals / AddViewToPortals)
|
||||
|
||||
### PView::ClipPortals @0x005a5520
|
||||
|
||||
**Summary:** Per popped flood cell: pass 1 scans the cell's top portal_view flags (seen && inflag!=1) and lazily resolves neighbor pointers via CEnvCell::GetVisible; returns 0 if no portal is live. Pass 2, in the cell's frame, installs each view in [start_view, view_count) and clips every live portal polygon (GetClip, do_clip=1). Survivors are appended to the neighbor's top portal_view (double-clipped via OtherPortalClip when exact_match==0 && other_portal_id>=0), or into this->outside_view for portals leading outdoors. Returns 1 when any portal was live.
|
||||
|
||||
```c
|
||||
int PView::ClipPortals(CEnvCell* cell, uint start_view /*first view index to process; 0 from ConstructView@005a5845, top->update_count from AdjustCellView@005a5796*/)
|
||||
{
|
||||
portal_view_type* top = cell->portal_view.data[cell->num_view - 1]; // cell's current (top) view slot
|
||||
Render::PortalList = top; // GLOBAL set unconditionally, before any early-out
|
||||
int any_live = 0;
|
||||
if ((int)cell->num_portals <= 0) return 0; // signed
|
||||
|
||||
// ---- pass 1: which portals are live; resolve+cache neighbor pointers ----
|
||||
for (int j = 0; j < (int)cell->num_portals; j++) { // CCellPortal stride 0x18
|
||||
portal_info* pi = &top->portal.data[j]; // {seen@+0, inflag@+4}
|
||||
if (pi->seen != 0 && pi->inflag != 1) {
|
||||
CCellPortal* p = &cell->portals[j];
|
||||
if (p->other_cell_ptr == null && p->other_cell_id != 0xFFFFFFFF) {
|
||||
p->other_cell_ptr = CEnvCell::GetVisible(p->other_cell_id); // cached into the portal record
|
||||
if (p->other_cell_ptr == null) continue; // neighbor not visible/loaded => not live
|
||||
}
|
||||
any_live = 1; // live: ptr already set, OR id==0xFFFFFFFF (outdoors), OR GetVisible succeeded
|
||||
}
|
||||
}
|
||||
if (!any_live) return 0;
|
||||
|
||||
// ---- pass 2: clip every live portal against each view in the window ----
|
||||
Render::positionPush(3, &cell->pos); // enter the cell's frame
|
||||
for (uint i = start_view; (int)i < (int)top->view_count; i++) { // signed compare
|
||||
Render::set_view(&top->view, i); // install view i (CPU globals only)
|
||||
for (int j = 0; j < (int)cell->num_portals; j++) {
|
||||
portal_info* pi = &top->portal.data[j];
|
||||
if (pi->seen == 0 || pi->inflag == 1) continue;
|
||||
CCellPortal* p = &cell->portals[j];
|
||||
uint n;
|
||||
PView::GetClip(this, (Sidedness)p->portal_side, p->portal, clip_view /*global out buffer*/, &n, 1 /*do_clip*/);
|
||||
if (n == 0) continue; // portal fully clipped away in this view
|
||||
if (p->other_cell_id == 0xFFFFFFFF) { // ---- portal leads OUTDOORS ----
|
||||
if (this->draw_landscape != 0) {
|
||||
if (cliplandscape != 0)
|
||||
Render::copy_view(&this->outside_view, clip_view, n); // exit view = the clipped portal shape
|
||||
else
|
||||
Render::copy_view(&this->outside_view, null, 0); // null-src marker view (see gotchas)
|
||||
}
|
||||
} else if (p->other_cell_ptr != null) { // ---- portal into a resolved neighbor ----
|
||||
if (p->exact_match == 0 && (int)p->other_portal_id >= 0) { // far poly differs => double clip
|
||||
if (PView::OtherPortalClip(this, p, clip_view, &n) == 0) {
|
||||
Render::set_view(&top->view, i); // restore after far-frame excursion; nothing survived
|
||||
continue;
|
||||
}
|
||||
Render::set_view(&top->view, i); // restore; clip_view/n now the doubly-clipped poly
|
||||
}
|
||||
CEnvCell* nb = p->other_cell_ptr;
|
||||
if (nb->num_view != 0) // neighbor must have a pushed view slot (add_views/stab_list)
|
||||
Render::copy_view(nb->portal_view.data[nb->num_view - 1], clip_view, n); // append to NEIGHBOR top view
|
||||
}
|
||||
// else: id != 0xFFFFFFFF and neighbor unresolved => clipped view silently dropped
|
||||
}
|
||||
}
|
||||
Render::positionPop();
|
||||
return 1; // 1 = some portal was live, even if the view window [start_view, view_count) was empty
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** BN's body is type-mushed garbage here (`esi_2->vtable` stands for BOTH top->portal.data and the view_count loop bound; `m_timeStamp` = top->view @+0x10) — this model is Ghidra-arbitrated (127.0.0.1:8081, matches doc §9 practice). Render::PortalList is set BEFORE the early-outs, so the global still points at this cell's top view even on return 0. The cliplandscape==0 arm calls copy_view(&outside_view, null, 0) — doc §6 documents copy_view(dest,null,4)=full-viewport quad; whether count 0 vs 4 matters must be verified in Render::copy_view's body before porting that arm. Loop/window compares are SIGNED ints. Ghidra names the pop positionPop, BN framePop — same paired call. draw_landscape==0 discards exit-portal views entirely (outside_view never raised).
|
||||
|
||||
### PView::OtherPortalClip @0x005a5400
|
||||
|
||||
**Summary:** The double-clip helper for non-exact_match portals (the task's ~0x005a5495 temp_view + copy_view site). Snapshots the near-clipped polygon into a lazily-initialized function-static temp_view (view 0), enters the FAR cell's frame, installs that snapshot as the active view, and re-clips against the far cell's own matching portal polygon (portals[other_portal_id]) with INVERTED sidedness (pass 1 iff far portal_side==0). Writes the result back through clip_view/count; returns count!=0.
|
||||
|
||||
```c
|
||||
int PView::OtherPortalClip(CCellPortal* p, Vec2Dscreen** clip_view, int* n)
|
||||
{
|
||||
static portal_view_type temp_view; // guard-bit lazy init ($S225): all 3 DArrays {data=0,sizeOf=0,next_available=0,blocksize=0x80}, view_timestamp=0, atexit dtor
|
||||
temp_view.view_count = 0; // reset EVERY call
|
||||
if (Render::copy_view(&temp_view, clip_view, *n) == 0) // snapshot the near-clipped poly as temp view 0
|
||||
return 0; // rejected (<3 survivors per copy_view rules)
|
||||
int opid = p->other_portal_id;
|
||||
CCellPortal* far = &p->other_cell_ptr->portals[opid]; // far cell's OWN portal entry
|
||||
Render::positionPush(3, &p->other_cell_ptr->pos); // enter the FAR cell's frame
|
||||
Render::set_view(&temp_view.view, 0); // install the snapshot as the active view
|
||||
PView::GetClip(this, (Sidedness)(far->portal_side == 0 ? 1 : 0), // INVERTED vs the far cell's declared side
|
||||
far->portal, clip_view, n, 1 /*do_clip*/); // result written back through clip_view/n
|
||||
Render::positionPop();
|
||||
return *n != 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** The sidedness inversion is a literal `== 0` test, NOT a 0<->1 swap: a far portal_side of 2 (IN_PLANE) would pass POSITIVE(0) — in-data values should only be 0/1, but don't port it as XOR. temp_view is one shared static (single-threaded renderer; not reentrant). BN's `int80_t` return / `result` from copy_view is FPU-tracking mush — Ghidra confirms the return is exactly (*n != 0), with an early return 0 when the snapshot copy_view fails. Only called when exact_match==0 && other_portal_id>=0, so far indexing is safe.
|
||||
|
||||
### PView::AddViewToPortals @0x005a52d0
|
||||
|
||||
**Summary:** Runs after ClipPortals(cell,...) returned 1: schedules neighbors that just received views. For each portal with a resolved neighbor, live source flags (seen && inflag!=1), and a nonempty neighbor top view: first touch (update_count==0) => InitCell + InsCellTodoList keyed by the neighbor top view's max_indist (+0x34); duplicate reach with NEW views (update_count!=view_count) => AddToCell, plus FixCellList re-place/re-clip when the neighbor was already marked cell_view_done, then update_count=view_count; no new views => skip entirely. Finally SetOtherSeen marks the neighbor's back-portal when other_portal_id>=0.
|
||||
|
||||
```c
|
||||
void PView::AddViewToPortals(CEnvCell* cell)
|
||||
{
|
||||
for (uint j = 0; j < cell->num_portals; j++) { // unsigned compare; CCellPortal stride 0x18
|
||||
CCellPortal* p = &cell->portals[j];
|
||||
CEnvCell* nb = p->other_cell_ptr; // null for outdoor portals + unresolved neighbors
|
||||
portal_info* pi = &cell->portal_view.data[cell->num_view - 1]->portal.data[j]; // SOURCE cell top-view flags (re-read each iteration)
|
||||
if (nb == null || pi->inflag == 1 || pi->seen == 0 || nb->num_view == 0)
|
||||
continue;
|
||||
portal_view_type* nbtop = nb->portal_view.data[nb->num_view - 1]; // neighbor's top view slot
|
||||
if (nbtop->view_count == 0) continue; // ClipPortals copied nothing in => nothing to schedule
|
||||
|
||||
if (nbtop->update_count == 0) {
|
||||
// ---- first touch this flood: schedule the neighbor ----
|
||||
if (PView::InitCell(this, nb, (uint16)p->other_portal_id) != 0) // ZERO-EXTENDED 16-bit read of the id
|
||||
PView::InsCellTodoList(this, nb, nbtop->max_indist); // todo distance key = neighbor top view's max_indist (float @+0x34)
|
||||
// update_count NOT set here (InitCell's business); fall through to SetOtherSeen
|
||||
} else if (nbtop->update_count != nbtop->view_count) {
|
||||
// ---- duplicate reach (2nd+ portal into nb) WITH new views since last processed ----
|
||||
PView::AddToCell(this, nb, (uint16)p->other_portal_id);
|
||||
if (nbtop->cell_view_done != 0) // nb already popped+processed by ConstructView
|
||||
PView::FixCellList(this, nb, cell); // = AdjustCellPlace(nb, cell) + AdjustCellView(nb);
|
||||
// AdjustCellView@005a5770 re-runs ClipPortals(nb, nbtop->update_count)
|
||||
// then AddViewToPortals(nb) => recursive incremental re-flood
|
||||
nbtop->update_count = nbtop->view_count; // fresh re-read AFTER FixCellList returns (both arms)
|
||||
} else {
|
||||
continue; // update_count == view_count: nothing new; NO SetOtherSeen either (goto loop end)
|
||||
}
|
||||
|
||||
if ((int)p->other_portal_id >= 0) // FULL-WIDTH SIGNED test (sentinel -1 skips)
|
||||
PView::SetOtherSeen(this, cell, j);
|
||||
// SetOtherSeen@005a4e30 (verified): q = &nb->portals[other_portal_id];
|
||||
// if (q->other_cell_ptr == null) q->other_cell_ptr = cell; // backlink fill
|
||||
// bp = &nbtop->portal.data[other_portal_id]; if (bp->inflag != 0) bp->seen = 1; // mark back-portal seen
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** TASK CORRECTION: outside_view is raised in ClipPortals, not here — outdoor portals keep other_cell_ptr null forever (GetVisible is never called for the 0xFFFFFFFF sentinel), so the nb==null gate skips them. The InitCell/AddToCell argument is a zero-extended 16-bit read of other_portal_id (both decompilers agree): id -1 => 0xFFFF, which IS ConstructView's no-through seed sentinel — coherent, but note InitCell/AddToCell DO run for such portals while SetOtherSeen (full signed int test) is skipped. In the InitCell branch, SetOtherSeen fires even when InitCell returned 0. update_count = view_count is a fresh read AFTER the FixCellList recursion returns — a portal cycle that adds views to nb during that recursion gets marked consumed (retail behavior; do not 'fix'). BN's loop-pointer prints (`arg2 = &arg2->m_pNext`, uint16 reads) are byte-offset-accumulator artifacts; Ghidra's `_padding_` expressions are equally mushed — the field decode above is offset-verified against acclient.h (other_cell_ptr +4, other_portal_id +0x10, stride 0x18; portal_view_type max_indist +0x34, view_count +0x38, cell_view_done +0x3C, update_count +0x44).
|
||||
|
||||
**Report notes:** Extraction sources: docs/research/named-retail/acclient_2013_pseudo_c.txt lines 433446-433737 (BN), cross-arbitrated function-by-function against live Ghidra MCP (127.0.0.1:8081) — the BN body of ClipPortals is type-confused throughout and MUST NOT be used directly; Ghidra's decomp was clean and is the authority for all three bodies, with struct offsets independently verified against docs/research/named-retail/acclient.h (portal_view_type /* 3415 */, CCellPortal /* 3406 */, portal_info /* 3407 */, CEnvCell /* 3405 */, PView /* 4912 */ with outside_view at offset 0). Caller contract (completes doc §5): ConstructView@0x005a57b0 pops a todo cell, appends it to cell_draw_list (grow +30), sets top->cell_view_done=1, then ClipPortals(cell, 0) and — only on return 1 — AddViewToPortals(cell). The second ClipPortals call site is AdjustCellView@0x005a5770 (reached via FixCellList@0x005a5250 = AdjustCellPlace + AdjustCellView), which passes start_view = top->update_count, i.e. arg3 is the first-unprocessed-view index and the update_count/view_count pair is a consumed-views watermark: that is the whole duplicate-reach mechanism — a cell reached through a second portal only re-clips the view window [update_count, view_count). clip_view is a global Vec2Dscreen** scratch buffer shared by GetClip; Render::PortalList is a global that GetClip/DrawCells consume. The seen/inflag ORIGIN (who first sets them per view) is not in these bodies — it belongs to InitCell/AddToCell/GetClip extraction; here they are only read (gate: seen!=0 && inflag!=1) and seen is set by SetOtherSeen on the neighbor's back-portal, gated on that back-portal's inflag!=0. Open verify-before-port items: Render::copy_view(dest, null, 0) semantics in the cliplandscape==0 arm (doc §6 only documents count=4), and the far-side sidedness ==0 test if any data ever carries portal_side 2.
|
||||
|
||||
## Report 3 - View-clip support (copy_view / polyClipFinish / xformStart)
|
||||
|
||||
### Render::copy_view @0x0054dfc0
|
||||
|
||||
**Summary:** Appends ONE polygon view slot to a portal_view_type: perspective-divides homogeneous screen points in place, marks survivors by dropping ~1px duplicates and collinear points (three closing wrap checks included), rejects <3 survivors (returns 0, view_count untouched), caps at 31, stores pt list + closing duplicate, computes screen bounds, then per-edge WORLD planes from unprojected rays (live path: ScreenToViewTransform; N=cross(ray[k+1],ray[k]) normalized unless degenerate; d=-dot(N,viewer_world_space.viewpoint)). pts==null builds the full-viewport root quad. Returns 1 on success.
|
||||
|
||||
```c
|
||||
int copy_view(portal_view_type* dest, Vec2Dscreen** pts, uint npts) // Ghidra-verified; returns int (BN showed void)
|
||||
vc = dest->view_count
|
||||
vbase = (vc == 0) ? 0 : dest->view.vertex_count_total // FIRST view resets the vertex pool
|
||||
if (vc >= view.poly.sizeOf) DArray<view_poly>::grow(&view.poly, vc + 0x10)
|
||||
|
||||
if (pts == null) { // ---- full-viewport root quad path
|
||||
n = 4; view.vertex_count_total = vbase + 5
|
||||
if (vbase+5 >= view.vertex.sizeOf) DArray<view_vertex>::grow(&view.vertex, vbase + 0x15)
|
||||
poly[vc] = { vertex_count: 4, vertex_index: vbase }
|
||||
v = &view.vertex.data[vbase] // W/H = render_device->m_viewportWidth/Height (uint->float)
|
||||
v[0].pt=(0,H); v[1].pt=(W,H); v[2].pt=(W,0); v[3].pt=(0,0); v[4].pt=(0,H) // closing dup
|
||||
goto bounds
|
||||
}
|
||||
|
||||
// ---- survivor marking
|
||||
keep[0]=1; n=1; last=0; stl=0 /*second-to-last kept corner*/; second=0 /*idx of 2nd kept (local_220)*/
|
||||
for (i = 0; i < npts; i++) {
|
||||
p = pts[i]
|
||||
if (p->w != 1.0) { p->x /= p->w; p->y /= p->w; p->w = 1.0 } // perspective divide IN PLACE (mutates caller's points)
|
||||
if (i == 0) continue
|
||||
distinct = |pts[i].x - pts[last].x| > 1.0 || |pts[i].y - pts[last].y| > 1.0 // strict >, vs LAST KEPT
|
||||
keep[i] = distinct
|
||||
if (!distinct) continue // ~1px duplicate dropped; last unchanged
|
||||
if (this is only the 2nd kept point) { n++; second = i }
|
||||
else {
|
||||
pp = pts[stl]; prev = pts[last]; cur = pts[i]
|
||||
span = max(|pp.x - cur.x|, |pp.y - cur.y|) // Chebyshev chord length
|
||||
cross = (pp.x - prev.x)*(prev.y - cur.y) - (pp.y - prev.y)*(prev.x - cur.x)
|
||||
if (|cross| >= span) { n++; stl = last } // prev is a genuine corner (~1px deviation test)
|
||||
else { keep[last] = 0; if (second == last) second = i } // prev collinear: un-keep it; n unchanged (prev out, cur in)
|
||||
}
|
||||
last = i
|
||||
}
|
||||
// ---- closing wrap checks against pts[0]
|
||||
first = pts[0]
|
||||
distinct = |first.x - pts[last].x| > 1.0 || |first.y - pts[last].y| > 1.0
|
||||
keep[last] = distinct
|
||||
if (!distinct) { n--; last = stl } // last duplicates first: drop it
|
||||
else {
|
||||
span = max(|pts[stl].x - first.x|, |pts[stl].y - first.y|)
|
||||
cross = (pts[stl].x - pts[last].x)*(pts[last].y - first.y) - (pts[last].x - first.x)*(pts[stl].y - pts[last].y)
|
||||
if (|cross| < span) { keep[last] = 0; n--; last = stl } // last collinear on stl->first
|
||||
}
|
||||
stl = last
|
||||
if (second > 0) { // is point 0 itself collinear between stl and second?
|
||||
span = max(|pts[stl].x - pts[second].x|, |pts[stl].y - pts[second].y|)
|
||||
cross = (first.y - pts[second].y)*(pts[stl].x - first.x) - (first.x - pts[second].x)*(pts[stl].y - first.y)
|
||||
if (|cross| < span) { n--; keep[0] = 0 }
|
||||
}
|
||||
|
||||
if (n < 3) return 0 // REJECT: nothing written, view_count NOT bumped
|
||||
if (n > 0x1f) n = 0x1f // cap 31
|
||||
view.vertex_count_total = vbase + n + 1
|
||||
if (>= view.vertex.sizeOf) DArray<view_vertex>::grow(&view.vertex, vbase + n + 0x11)
|
||||
poly[vc] = { vertex_count: n, vertex_index: vbase }
|
||||
v = &view.vertex.data[vbase]; j = 0
|
||||
for (i = 0; i < npts; i++)
|
||||
if (keep[i]) { v[j].pt.x = fabs(pts[i].x); v[j].pt.y = fabs(pts[i].y); j++ } // REAL fabs (Ghidra-confirmed)
|
||||
v[n].pt = v[0].pt // closing duplicate vertex (its plane slot never written)
|
||||
|
||||
bounds: // over v[0..n-1] only
|
||||
xmin=xmax=v[n-1].pt.x; ymin=ymax=v[n-1].pt.y
|
||||
for (k = n-2; k >= 0; k--) {
|
||||
if (v[k].x < xmin) xmin=v[k].x; else if (v[k].x > xmax) xmax=v[k].x
|
||||
if (v[k].y < ymin) ymin=v[k].y; else if (v[k].y > ymax) ymax=v[k].y
|
||||
}
|
||||
poly[vc].{xmin,xmax,ymin,ymax} = ...
|
||||
|
||||
// ---- per-edge world planes via unprojected rays (stack array ray[0..n], stride 0xc)
|
||||
if (newmethod == 1) // newmethod statically = 1: this is the LIVE path
|
||||
for (k = n-1; k >= 0; k--) PrimD3DRender::ScreenToViewTransform(&ray[k], v[k].pt.x, v[k].pt.y) // world ray dir @0x0059aa40
|
||||
else // DEAD legacy software fork
|
||||
for (k = n-1; k >= 0; k--) {
|
||||
u = v[k].pt.x * Render::xinvscale - Render::tx
|
||||
w = v[k].pt.y * Render::yinvscale - Render::ty
|
||||
ray[k] = Render::Xaxis*u + Render::Yaxis*Render::vdst - Render::Zaxis*w
|
||||
}
|
||||
ray[n] = ray[0] // closing duplicate ray
|
||||
for (k = n-1; k >= 0; k--) {
|
||||
N = cross(ray[k+1], ray[k]) // NOTE ORDER: next x current
|
||||
if (|N.x| >= F_EPSILON || |N.y| >= F_EPSILON || |N.z| >= F_EPSILON) // F_EPSILON = 0.000199999995f
|
||||
N *= 1.0f / sqrt(N.x^2 + N.y^2 + N.z^2) // else left tiny/unnormalized (degenerate edge, no reject)
|
||||
v[k].plane.N = N // view_vertex = {Vec2D pt; Plane plane} stride 0x18
|
||||
v[k].plane.d = -dot(N, Render::viewer_world_space.viewpoint) // edge k = screen verts k -> k+1
|
||||
}
|
||||
dest->view_count += 1
|
||||
return 1
|
||||
```
|
||||
|
||||
**Gotchas:** BN body is heavy x87-flag mush; every branch here is Ghidra-cross-checked (8081/patchmem). (1) MUTATES the caller's points (in-place perspective divide). (2) The vertex copy applies REAL fabs to x,y — harmless post-clip (coords in-viewport up to rounding) but real. (3) Cap-31 quirk: if >31 survive, the copy loop still writes ALL survivors, then the closing dup overwrites index 31 — corrupt polygon; unreachable from the <=32-vert callers but not guarded. (4) Plane N = cross(ray[k+1], ray[k]) — the fw-walk doc §6 summary says cross(ray_i, ray_i+1), i.e. NEGATED; decomp order is next-x-current. (5) The corner-keep boundary is |cross| >= span per Ghidra (consistent at all 3 sites); BN rendered strictness ambiguously. (6) newmethod is statically 1 with no other write found — ScreenToViewTransform is the live unproject; the Xaxis/vdst fork is dead. (7) Grow checks are '>= sizeOf' with +0x10 slack. (8) Returns int 0/1 (BN showed void); reject leaves dest completely untouched.
|
||||
|
||||
### ACRender::polyClipFinish @0x006b6d00
|
||||
|
||||
**Summary:** Sutherland-Hodgman clip of a homogeneous screen polygon (array of Vec2Dscreen pointers) against the INSTALLED view: first the w >= cdstW plane (cdstW = 0.000199999995f), then each edge of Render::portal_vertex[0..portal_npnts-1] whose inmask bit is clear (all 3 retail callers pass inmask=0 — clip every edge). Each pass scans input in REVERSE; pointer lists ping-pong between static tempPtPBuf and pts_out; a final reverse-copy restores the ORIGINAL winding. Any stage dropping below 3 vertices returns without writing *npts_out (callers pre-zero it).
|
||||
|
||||
```c
|
||||
void polyClipFinish(Vec2Dscreen** pts_in, int npts_in, Vec2Dscreen** pts_out, int* npts_out, int inmask)
|
||||
// Installed view: Render::portal_vertex (view_vertex[] {pt, plane}, stride 0x18), Render::portal_npnts.
|
||||
// Statics: tempPtBuf = Vec2Dscreen pool for intersections (stride 0x10), tempPtPBuf @0x009053d0 = pointer list.
|
||||
mask = inmask << (0x1e - portal_npnts) // aligns mask bit `npnts` at bit 30; per-edge <<1 exposes it at sign bit
|
||||
parity = 0 // pass parity; dstsel = { [0]: pts_out, [1]: tempPtPBuf }
|
||||
freept = tempPtBuf
|
||||
cur_pts = pts_in; cur_n = npts_in
|
||||
|
||||
// ---- pass 0: w-clip, only if some w < cdstW (scan last-to-first)
|
||||
if (exists i: pts_in[i]->w < cdstW) { // cdstW = 0.000199999995f (set @0x007247d5)
|
||||
parity = 1; out = base = tempPtPBuf
|
||||
prev = pts_in[0]; sprev = prev->w - cdstW; inprev = (sprev >= 0)
|
||||
for (i = npts_in-1; i >= 0; i--) { // REVERSE traversal; wrap pair (pts[0], pts[n-1]) first
|
||||
cur = pts_in[i]; s = cur->w - cdstW; incur = (s >= 0) // INSIDE = w >= cdstW
|
||||
if (inprev != incur) {
|
||||
t = sprev / (sprev - s) // lands exactly on w = cdstW
|
||||
*freept = prev + (cur - prev)*t // ALL FOUR components x,y,z,w lerped
|
||||
*out++ = freept++
|
||||
}
|
||||
if (incur) *out++ = cur
|
||||
prev = cur; sprev = s; inprev = incur
|
||||
}
|
||||
cur_n = out - tempPtPBuf
|
||||
if (cur_n < 3) return // *npts_out NOT written
|
||||
cur_pts = tempPtPBuf
|
||||
}
|
||||
|
||||
// ---- edge passes, edges iterated LAST-to-FIRST as pairs (a,b):
|
||||
// (v[0], v[npnts-1]), (v[npnts-1], v[npnts-2]), ..., (v[1], v[0])
|
||||
a = portal_vertex[0]
|
||||
for (b = &portal_vertex[npnts-1]; b >= &portal_vertex[0]; a = b, b--) {
|
||||
mask <<= 1
|
||||
if (mask < 0 /*sign bit set*/) continue // inmask bit SET = poly already inside this edge: skip
|
||||
parity ^= 1; out = base = dstsel[parity]
|
||||
ex = b->pt.x - a->pt.x; ey = b->pt.y - a->pt.y // screen-space edge direction
|
||||
side(p) = (p->x - a->pt.x * p->w) * ey - (p->y - a->pt.y * p->w) * ex // homogeneous 2D cross; w>0 preserves sign
|
||||
prev = cur_pts[0]; s0 = side(prev); sprev = s0; inprev = (s0 <= 0) // INSIDE = side <= 0 (Ghidra-verified)
|
||||
for (i = cur_n-1; i >= 0; i--) { // REVERSE traversal again
|
||||
cur = cur_pts[i]
|
||||
s = (i != 0) ? side(cur) : s0 // final pair (prev=cur_pts[1], cur=cur_pts[0]) reuses point 0's side
|
||||
incur = (s <= 0)
|
||||
if (inprev != incur) {
|
||||
t = sprev / (sprev - s)
|
||||
*freept = prev + (cur - prev)*t // 4-component homogeneous lerp
|
||||
*out++ = freept++
|
||||
}
|
||||
if (incur) *out++ = cur
|
||||
prev = cur; sprev = s; inprev = incur
|
||||
}
|
||||
cur_n = out - base
|
||||
if (cur_n < 3) return // *npts_out NOT written
|
||||
cur_pts = base // just-written buffer becomes next pass's input
|
||||
}
|
||||
*npts_out = cur_n
|
||||
if (parity != 0) // final data sits in tempPtPBuf, not pts_out
|
||||
for (p = out-1; p >= base; p--) *pts_out++ = *p // reverse-copy into pts_out
|
||||
// NET ORDERING: each pass reverses order (reverse scan); (#passes + conditional copy) is always even,
|
||||
// so pts_out ends with the ORIGINAL input winding.
|
||||
```
|
||||
|
||||
**Gotchas:** Ghidra-verified against BN's flag mush. (1) The w-plane is w >= cdstW = 0.000199999995f, NOT w >= 0 — BN's 'vs 0f' is fsub-cdstW rendering. (2) Edge INSIDE = side <= 0 (verified sensible: interior of the root full-viewport quad (0,H),(W,H),(W,0),(0,0) yields negative sides); BN's literal rendering inverts this — trust Ghidra. (3) Early <3 returns NEVER write *npts_out — all 3 callers (GetClip @0x005a43b2/0x005a4414, DrawPortalPolyInternal @0x0059bdb0) pre-zero it; a port must preserve or document that contract. (4) inmask bit mapping: iteration k tests original bit npnts-k+1; bit npnts = wrap edge (v[0],v[npnts-1]) pair, bit 1 = (v[1],v[0]); bit 0 (the CY-plane bit of set_view's npnts+1-bit in-mask) is NEVER tested here — but all callers pass inmask=0 anyway, so every edge always clips and the skip path is dead in practice. (5) Zero-pass case (all bits set + no w<cdstW) would set *npts_out=npts_in with pts_out never filled — unreachable with inmask=0. (6) Static tempPtBuf/tempPtPBuf have NO bounds checks; callers assume <=32 verts (GetClip's reversal stack array is exactly 32 pointers). (7) Not reentrant (static buffers).
|
||||
|
||||
### PrimD3DRender::xformStart @0x0059b990
|
||||
|
||||
**Summary:** Transforms one object-space Vector3 into a slot of the 100-entry static Vec2Dscreen ring buffer and returns the slot. toScreen!=0: object->clip via GetObjectMatrix()*WorldToView*ViewToClip, then viewport-scales WITHOUT perspective divide — x=(bw/2)(x_clip+w), y=(bh/2)(w-y_clip) (y flipped, origin top-left), z=raw clip z, w=raw clip w. toScreen==0: object->view; raw view-space x,y stored, z/w from the affine transform (w=1). The divide happens downstream (copy_view divides; polyClipFinish clips pre-divide homogeneously).
|
||||
|
||||
```c
|
||||
Vec2Dscreen* xformStart(Vector3* p_obj, int toScreen)
|
||||
M_ov = D3DXMatrixMultiply(Render::GetObjectMatrix(), render_device->m_GState.WorldToViewMatrix) // object->view (row-vector conv.)
|
||||
M_oc = D3DXMatrixMultiply(M_ov, render_device->m_GState.ViewToClipMatrix) // object->clip
|
||||
M = toScreen ? M_oc : M_ov // BOTH products computed unconditionally, EVERY call
|
||||
v4 = D3DXVec3Transform((p_obj,1), M) // homogeneous (x,y,z,w)
|
||||
slot = &tmpScreenBuffer[pt_to_use] // static ring @0x00871360, stride 0x10
|
||||
pt_to_use += 1; if (pt_to_use == 100) pt_to_use = 0
|
||||
if (toScreen == 0) { slot->x = v4.x; slot->y = v4.y } // raw VIEW-space x,y
|
||||
else {
|
||||
slot->x = v4.x*Render::bw*0.5 + v4.w*Render::bw*0.5 // = (bw/2)*(x_clip + w): viewport-scaled, NO divide
|
||||
slot->y = v4.w*Render::bh*0.5 - v4.y*Render::bh*0.5 // = (bh/2)*(w - y_clip): y-flip, 0 = top
|
||||
}
|
||||
slot->z = v4.z // raw clip z (or view z); never viewport-mapped here
|
||||
slot->w = v4.w // raw clip w (view path: w = 1, affine)
|
||||
return slot
|
||||
```
|
||||
|
||||
**Gotchas:** (1) NO perspective divide — output is homogeneous 'screen * w' coordinates; dividing x,y by w yields pixels in [0,bw]x[0,bh]. copy_view performs the divide (its w != 1.0 check is exactly this contract); polyClipFinish consumes them undivided via the (p.x - a.x*p.w) homogeneous side test and clips w at cdstW. (2) Recomputes BOTH matrix concatenations per vertex, even the unused one — a per-vertex cost quirk, do not 'optimize into' different rounding without noting it. (3) 100-slot static ring: >100 outstanding returned pointers alias (callers hold <=32). (4) Uses Render::bw/bh for scaling while copy_view's root quad uses render_device->m_viewportWidth/Height — kept equal by the device, but two different globals. (5) BN's '0x63 == pre-increment' check is the same as Ghidra's '==100 post-increment' wrap.
|
||||
|
||||
**Report notes:** All three bodies extracted from docs/research/named-retail/acclient_2013_pseudo_c.txt and cross-checked function-by-function against the live Ghidra MCP (http://127.0.0.1:8081, patchmem 2013 build) — the BN text for copy_view and polyClipFinish is severe x87-flag/stack mush and would have yielded at least two inverted branch polarities (the edge inside test and the corner-keep test) plus a wrong w-clip plane (0 instead of cdstW) if read literally. Key contract facts for FW3/FW4: (a) view_vertex = {Vec2D pt; Plane plane} stride 0x18 (acclient.h line 32483), view_poly = {vertex_count, vertex_index, xmin, xmax, ymin, ymax} stride 0x18, portal_view_type per acclient.h line 32346; copy_view appends into poly slot [view_count] and vertex pool [vertex_count_total], resetting the pool when view_count==0. (b) The full chain: xformStart(v,1) produces homogeneous viewport coords (no divide) -> PView::GetClip @0x005a4320 pre-zeroes *npts_out, reverses the pointer order for Sidedness != POSITIVE, sets Render::PolyCurrent=null/PolyCurrentMod=1.0/PolyCurrentPos=1, and calls polyClipFinish(screen, n, out, npts_out, 0) — inmask is ALWAYS 0 from all three call sites, so the edge-skip fast path is dead; do_clip==0 skips clipping entirely (copy/reverse only). (c) polyClipFinish output preserves input winding (reverse-scan passes + the conditional final reverse-copy always cancel). (d) copy_view then divides, dedups/collinear-prunes at ~1px (corner survives iff |2D cross| >= Chebyshev span), rejects <3, caps 31, and builds per-edge world planes N = normalize(cross(ray[k+1], ray[k])), d = -dot(N, Render::viewer_world_space.viewpoint) — note the cross ORDER is next-x-current, the fw-walk doc's §6 one-liner has it reversed (doc fix candidate, affects plane sign conventions for any consumer like viewconeCheck). (e) The live unproject is ScreenToViewTransform @0x0059aa40 (newmethod statically 1, initializer at data 0x0081efe8, no other writes found): ndc_u = ((2*sx/vpW)-1)/ViewToClip[0][0], ndc_v = ((2*sy/vpH)-1)*(-1/ViewToClip[1][1]), ray = u*row0 + v*row1 + row2 of inverse(WorldToView), with out.y taking matrix column 2 and out.z column 1 (D3D y-up -> AC z-up wiring); un-normalized direction. (f) Constants: cdstW = F_EPSILON = 0.000199999995f (0x3951B717); dedup threshold 1.0 px; vertex cap 0x1f; poly grow +0x10; vertex grow +0x10 slack. Ghidra decomp copies saved in scratchpad (copyview_ghidra.c, polyclip_ghidra.c, xformstart_ghidra.c) if the parent wants the raw text.
|
||||
|
||||
## Report 4 - Landscape visibility (get_clip_height / block_check / set_default_view / update_viewpoint)
|
||||
|
||||
### Render::get_pt_limit @0x0054b840
|
||||
|
||||
**Summary:** Defines the ViewIntervalType cell encoding. Classifies the vertical column at (x,y) against ONE clip plane, returning a signed scalar: outside_val (1001f) = column wholly outside; inside_val (0f) = wholly inside; positive h = inside only BELOW height h (plane normal points down, N.z < -eps); negative -h = inside only ABOVE height h (normal points up, N.z > eps). Vertical planes (|N.z| <= eps) collapse to all-in/all-out via Plane::which_side at z=0.
|
||||
|
||||
```c
|
||||
// F_EPSILON = 0.000199999995f; sky_height = 1000f; inside_val = 0f; outside_val = 1001f (static init $E106: 1000+1)
|
||||
float get_pt_limit(float x, float y, Plane* p) {
|
||||
if (p->N.z > F_EPSILON) { // inside half-space is z >= h
|
||||
h = -((x*p->N.x + y*p->N.y + p->d) / p->N.z);
|
||||
if (h >= sky_height) return outside_val; // need z >= 1000: nothing
|
||||
if (h > 0) return -h; // inside above h
|
||||
return inside_val; // h <= 0: whole column inside
|
||||
} else if (p->N.z < -F_EPSILON) { // inside half-space is z <= h
|
||||
h = -((x*p->N.x + y*p->N.y + p->d) / p->N.z);
|
||||
if (h <= 0) return outside_val; // nothing above ground inside
|
||||
if (h >= sky_height) return inside_val; // everything below sky inside
|
||||
return h; // inside below h
|
||||
} else { // vertical plane: no z dependence
|
||||
if (Plane::which_side(p, Vector3(x, y, 0f), F_EPSILON) == NEGATIVE)
|
||||
return outside_val;
|
||||
return inside_val; // POSITIVE or ON_PLANE
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** BN body is pure x87-flag mush (unusable); this is the Ghidra decomp. Note the ROLE of eps: sidedness of N.z picks which half-space is 'inside' — inside is always the POSITIVE side of the plane (dot(N,p)+d >= 0), consistent with copy_view's plane winding and the vertical-plane which_side test. Vertical-plane test point is (x, y, 0) — z hardwired to 0. Boundary: h exactly 0 in the up-normal arm returns inside_val (encoding is continuous at 0).
|
||||
|
||||
### Render::get_clip_height @0x0054cff0
|
||||
|
||||
**Summary:** Fills one ViewIntervalType (struct { float bound[32]; } — 0x80 bytes, acclient.h #4994) for the grid corner at world (x,y): bound[0] = column vs the CY near plane (Render::viewer_world_space.CY, installed by update_viewpoint); bound[1..portal_npnts] = column vs each edge plane of the ACTIVE view (installed by set_view: portal_vertex[i-1].plane). Called per landblock-grid corner by LScape::draw_check_blocks and per cell-grid corner by LScape::landcell_check.
|
||||
|
||||
```c
|
||||
void get_clip_height(float x, float y, ViewIntervalType* out) {
|
||||
out->bound[0] = get_pt_limit(x, y, &Render::viewer_world_space.CY);
|
||||
for (i = 1; i <= Render::portal_npnts; i++) // npnts = active view's edge count (4 for default view)
|
||||
out->bound[i] = get_pt_limit(x, y, &Render::portal_vertex[i-1].plane);
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** BN's plane pointer '(edi_1 + portal_vertex) - 0x10' decodes as view_vertex stride 0x18 (Vec2Dscreen pt @0 + Plane @+8): iteration 1 hits vertex[0].plane. Loop bound is INCLUSIVE (i <= portal_npnts), so npnts+1 floats are written — matches set_view's portal_inmask = (1 << (npnts+1)) - 1 (edges + CY). Coordinates are viewer-block-relative 'world' space (the landscape's viewer-rebased frame), same space the CY/edge planes were built in.
|
||||
|
||||
### Render::corner_plane_check @0x0054b930
|
||||
|
||||
**Summary:** Tests one corner's per-plane interval encoding against the block's z-range [min_z, max_z]. Returns BoundingType: OUTSIDE=0, PARTIALLY_INSIDE=1, ENTIRELY_INSIDE=2. Decodes the get_pt_limit scalar: sentinels first, then the above-h (negative) and below-h (positive) interval arms.
|
||||
|
||||
```c
|
||||
BoundingType corner_plane_check(float bound, float min_z, float max_z) {
|
||||
if (bound == outside_val) return OUTSIDE; // 1001f
|
||||
if (bound != inside_val) { // != 0f
|
||||
if (bound <= 0f) { // inside is z >= h, h = -bound
|
||||
h = -bound;
|
||||
if (h > min_z) { // block bottom below clip height
|
||||
if (max_z <= h) return OUTSIDE; // whole block below h
|
||||
return PARTIALLY_INSIDE;
|
||||
} // h <= min_z: block wholly above h
|
||||
} else if (bound < max_z) { // inside is z <= h, h = bound; block top above h
|
||||
if (bound <= min_z) return OUTSIDE; // whole block above h
|
||||
return PARTIALLY_INSIDE;
|
||||
} // h >= max_z: block wholly below h
|
||||
}
|
||||
return ENTIRELY_INSIDE;
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** BN body is x87-flag mush; polarity decoded from Ghidra's '(a < b) == (a == b)' idiom = a > b. Equality edges: touching the clip height on the OUT side counts OUTSIDE (max_z == h / min_z == h both reject); touching on the IN side counts ENTIRELY_INSIDE. Arg order min_z-then-max_z is established by the double swap through block_check (see its gotcha).
|
||||
|
||||
### Render::block_plane_check @0x0054d060
|
||||
|
||||
**Summary:** Combines the four corner classifications of one plane for a block: OUTSIDE only when ALL four corners are OUTSIDE; ENTIRELY_INSIDE only when ALL four are ENTIRELY_INSIDE; anything mixed is PARTIALLY_INSIDE. The first corner's class picks which unanimity test runs.
|
||||
|
||||
```c
|
||||
BoundingType block_plane_check(float b1, float b2, float b3, float b4, float min_z, float max_z) {
|
||||
c1 = corner_plane_check(b1, min_z, max_z);
|
||||
c2 = corner_plane_check(b2, min_z, max_z);
|
||||
c3 = corner_plane_check(b3, min_z, max_z);
|
||||
c4 = corner_plane_check(b4, min_z, max_z);
|
||||
if (c1 == OUTSIDE) {
|
||||
if (c2 == OUTSIDE && c3 == OUTSIDE && c4 == OUTSIDE) return OUTSIDE;
|
||||
} else if (c1 == ENTIRELY_INSIDE && c2 == ENTIRELY_INSIDE && c3 == ENTIRELY_INSIDE && c4 == ENTIRELY_INSIDE) {
|
||||
return ENTIRELY_INSIDE;
|
||||
}
|
||||
return PARTIALLY_INSIDE;
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** This is conservative per plane: 3-of-4 corners outside one plane still yields PARTIAL (the block may straddle the plane). Cross-plane rejection happens in block_check, which ANDs OUTSIDE across planes — a block fully outside ANY single plane is culled.
|
||||
|
||||
### Render::block_check @0x0054dc50
|
||||
|
||||
**Summary:** The exact block-vs-view interval test. Takes the block's four corner ViewIntervalTypes (west[y], west[y+1], east[y], east[y+1]) plus the block's max_zval/min_zval. Tests plane 0 (CY) first with early-out, then each of the active view's portal_npnts edge planes: any plane reporting all-corners-OUTSIDE culls the block; any PARTIAL demotes the running result; otherwise the plane-0 result (possibly ENTIRELY_INSIDE) survives.
|
||||
|
||||
```c
|
||||
// caller passes (..., blk->max_zval, blk->min_zval) — note arg5=max, arg6=min
|
||||
BoundingType block_check(ViewIntervalType* i00, ViewIntervalType* i01,
|
||||
ViewIntervalType* i10, ViewIntervalType* i11,
|
||||
float max_zval, float min_zval) {
|
||||
result = block_plane_check(i00->bound[0], i01->bound[0], i10->bound[0], i11->bound[0],
|
||||
min_zval, max_zval); // CY plane first (args swapped back to min,max)
|
||||
if (result == OUTSIDE) return OUTSIDE;
|
||||
for (k = 1; k <= Render::portal_npnts; k++) {
|
||||
r = block_plane_check(i00->bound[k], i01->bound[k], i10->bound[k], i11->bound[k],
|
||||
min_zval, max_zval);
|
||||
if (r == OUTSIDE) return OUTSIDE; // fully outside ONE plane => culled
|
||||
if (r == PARTIALLY_INSIDE) result = PARTIALLY_INSIDE; // sticky demotion
|
||||
}
|
||||
return result; // ENTIRELY_INSIDE only if every plane said so
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** Argument-order trap: block_check receives (max_zval, min_zval) but forwards (arg6, arg5) = (min, max) to block_plane_check, which forwards positionally to corner_plane_check — net effect corner_plane_check(bound, min_z, max_z). Ghidra confirms the swap. BN's pointer-difference loop ('eax_5 = arg1 - arg3' etc.) is just base-relative addressing of bound[k] across the four structs — Ghidra's version shown here is the honest shape. Loop is 1..portal_npnts inclusive (bound[0] handled before the loop).
|
||||
|
||||
### Render::update_viewpoint (Position const*) @0x0054cdd0
|
||||
|
||||
**Summary:** Installs the viewer for the frame: copies the pose into Render::viewer_pos, splits the rotation into Xaxis/Yaxis/Zaxis (right/forward/up rows of m_fl2gv), builds viewer_world_space = { viewpoint = eye origin, CY = near plane with N = Yaxis (forward), d = -dot(origin, Yaxis) - znear }, then pushes the D3D world-to-view matrix (view Z = forward, translation = world origin in viewer-local coords), refreshes lights, and recomputes selection_ray when check_selection is armed. This is ALL the state get_clip_height/viewconeCheck read besides set_view's.
|
||||
|
||||
```c
|
||||
void update_viewpoint(Position* pos) {
|
||||
viewer_pos.objcell_id = pos->objcell_id;
|
||||
viewer_pos.frame = pos->frame;
|
||||
Xaxis = m_fl2gv[0..2]; Yaxis = m_fl2gv[3..5]; Zaxis = m_fl2gv[6..8]; // right, FORWARD, up
|
||||
viewer_world_space.CY.d = -(origin.x*m[3] + origin.y*m[4] + origin.z*m[5]) - znear;
|
||||
viewer_world_space.viewpoint = viewer_pos.frame.m_fOrigin;
|
||||
viewer_world_space.CY.N = Yaxis; // near plane: dot(fwd,p) >= dot(fwd,eye)+znear
|
||||
o = Frame::globaltolocal(&viewer_pos.frame, (0,0,0)); // world origin in view space
|
||||
M = 4x4 { rows built from Xaxis / Zaxis / Yaxis, translation o, last row (0,0,0,1) }; // D3D: view Z = forward
|
||||
RenderDeviceD3D::SetWorldToViewMatrix(render_device, &M);
|
||||
m_pRenderer->UpdateLightsInternal(...); // vtbl +0x28
|
||||
if (check_selection) selection_ray = *pick_ray(&o, selection_x, selection_y);
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** The Frame overload @0x0054dc20 wraps into a stack Position{vftable, objcell_id = 0, frame} and tail-calls this — BN's 'var_48 = 0x796910' is Position's vftable pointer, NOT a cell id; objcell_id is 0. The exact element ordering of the D3D Matrix4 is ambiguous in both decompilers (Ghidra shows _21 = Xaxis.z, _31 = Xaxis.y — transposed-looking stack writes); semantics (world-to-view, forward mapped to view Z) are solid but element layout should be re-derived at port time if it becomes load-bearing. CY does NOT depend on the active view — it is rebuilt only here; set_default_view/set_view never touch it.
|
||||
|
||||
### Render::set_default_view @0x0054ef50
|
||||
|
||||
**Summary:** Installs the outdoor full-screen view. One-time-inits the static Render::window (a portal_view_type; portal/poly/vertex DArrays zeroed, blocksize 0x80, atexit dtor), resets view_count and vertex_count_total to 0, appends the full-viewport quad via copy_view(&window, null, 0), sets Render::PortalList = &window, and activates it with set_view(&window.view, 0) — after which portal_npnts = 4, portal_inmask = 0x1F (4 edge planes + CY), portal_vertex points at the quad's verts, and xmin/xmax/ymin/ymax hold the full screen bounds.
|
||||
|
||||
```c
|
||||
void set_default_view() {
|
||||
if (!($S273 & 1)) { // one-time static init
|
||||
$S273 |= 1;
|
||||
window.portal = window.view.poly = window.view.vertex = empty DArray (blocksize 0x80);
|
||||
window.view_timestamp = 0;
|
||||
atexit($E274);
|
||||
}
|
||||
window.view_count = 0;
|
||||
window.view.vertex_count_total = 0;
|
||||
copy_view(&window, /*screen pts*/ null, /*npts*/ 0); // null source => full-viewport quad:
|
||||
// appends poly {vertex_count=4, vertex_index=base}, verts (0,H),(W,H),(W,0),(0,0) + wrap copy of v0
|
||||
// (W/H = render_device->m_viewportWidth/Height), computes xmin/xmax/ymin/ymax,
|
||||
// builds 4 WORLD-space edge planes from the corner rays:
|
||||
// ray = Xaxis*sx + Yaxis*vdst - Zaxis*sy (sx = x*xinvscale - tx, sy = y*yinvscale - ty)
|
||||
// N = normalize(cross(ray_i, ray_i+1)), d = -dot(N, viewer_world_space.viewpoint)
|
||||
// then window.view_count += 1 (=> 1)
|
||||
Render::PortalList = &window;
|
||||
set_view(&window.view, 0); // portal_npnts=4, portal_inmask=(1<<5)-1=0x1F,
|
||||
// portal_vertex=&vertex[poly[0].vertex_index], screen bounds
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** Third copy_view arg here is 0 (source-point count, unused for the null source) — PView::DrawInside's root uses copy_view(top_view, null, 4); the null branch is identical, the count is ignored. copy_view has TWO ray paths gated on the global 'newmethod': ==1 uses PrimD3DRender::ScreenToViewTransform per point; else the inline xinvscale/yinvscale/tx/ty/vdst formula shown. Edge-plane normalization is skipped when every cross component < F_EPSILON (degenerate edge left unnormalized). The window's DArrays are grow-only and reused across frames; only the counters reset. copy_view returns 0 (view rejected, view_count NOT bumped) when <3 distinct screen points survive dedup — impossible for the null-source quad.
|
||||
|
||||
### LScape::draw_check_blocks @0x00505f80
|
||||
|
||||
**Summary:** The per-frame landscape visibility pass (called by LScape::update_viewpoint @0x005062d0/0x0050634b). Clears every block's and land cell's in_view, (re)allocates the rolling corner-interval buffer, then FOR EACH VIEW in Render::PortalList (1 outdoor default view, or N portal exit views): installs the view with set_view, computes ViewIntervalType per landblock-grid corner at block_length (192 m) spacing in viewer-block-relative coords, and runs block_check per block; non-OUTSIDE blocks get in_view set and are refined per-cell by landcell_check @0x005050a0 (same machinery at 24 m).
|
||||
|
||||
```c
|
||||
void LScape::draw_check_blocks() {
|
||||
// 1) clear
|
||||
for (y,x over mid_width x mid_width) if (blk = land_blocks[mid_width*y + x]) {
|
||||
blk->in_view = OUTSIDE; // +0xfc
|
||||
for (i < side_cell_count^2) blk->lcell[i].in_view = 0; // CLandCell stride 0x108, field +0x104
|
||||
}
|
||||
// 2) rolling corner buffer: 2 rows x (mid_width+1) ViewIntervalType (0x80 each)
|
||||
if (block_interval && block_int_size != mid_width) delete[] block_interval, block_interval = null;
|
||||
if (!block_interval) { block_interval = new[( mid_width+1) * 0x100]; block_int_size = mid_width; }
|
||||
// 3) per active view
|
||||
nviews = Render::PortalList ? Render::PortalList->view_count : 0;
|
||||
v = 0; last = false;
|
||||
do {
|
||||
if (nviews == 0) last = true;
|
||||
else { Render::set_view(&Render::PortalList->view, v); v++; if (v == nviews) last = true; }
|
||||
// seed west column (grid x index 0) into row 0
|
||||
for (i = 0; i <= mid_width; i++)
|
||||
get_clip_height((0 - viewer_b_xoff) * block_length, // block_length = 24*8 = 192f
|
||||
(i - viewer_b_yoff) * block_length,
|
||||
&block_interval[i]);
|
||||
for (bx = 0; bx < mid_width; bx++) {
|
||||
// east column (grid x = bx+1) into row (bx-1)&1 == (bx+1)&1
|
||||
for (by = 0; by <= mid_width; by++)
|
||||
get_clip_height((bx + 1 - viewer_b_xoff) * block_length,
|
||||
(by - viewer_b_yoff) * block_length,
|
||||
&block_interval[((bx-1) & 1) * (mid_width+1) + by]);
|
||||
for (by = 0; by < mid_width; by++) {
|
||||
if (!(blk = land_blocks[mid_width*bx + by])) continue;
|
||||
west = &block_interval[(bx & 1) * (mid_width+1) + by]; // x = bx corners
|
||||
east = &block_interval[((bx-1) & 1) * (mid_width+1) + by]; // x = bx+1 corners
|
||||
bt = Render::block_check(west, west+1, east, east+1, // +1 struct = y+1 corner (+0x80)
|
||||
blk->max_zval, blk->min_zval);
|
||||
if (bt != OUTSIDE) { blk->in_view = bt; LScape::landcell_check(this, blk); }
|
||||
}
|
||||
}
|
||||
} while (!last);
|
||||
}
|
||||
```
|
||||
|
||||
**Gotchas:** BN ELIDED the grid-spacing constant to '0f' — the real multiplier is the global ::block_length = 192.0f (static init: 24*8; the 24f seen at 0x00505187/0x00505206 belongs to landcell_check's 24 m cell grid, a different function). in_view is only WRITTEN when the result is non-OUTSIDE, so across the multi-view loop a block visible in ANY exit view stays marked (values can also be overwritten ENTIRELY<->PARTIALLY by later views; landcell_check reruns per view). When PortalList is null the body still runs ONCE against whatever set_view state is stale — unreachable in practice (set_default_view always precedes the outdoor walk). Ghidra renders side_cell_count/lcell as '_padding_' — real names from CLandBlockStruct (acclient.h #3393): side_cell_count @+0x48, lcell @+0x90; the +0x104/0x108 constants mean CLandCell is 0x108 bytes with in_view last.
|
||||
|
||||
**Report notes:** All addresses from docs/research/named-retail/acclient_2013_pseudo_c.txt; every x87-ambiguous body cross-checked against the live Ghidra MCP (patchmem, port 8081) — get_pt_limit, corner_plane_check, block_check, set_default_view, update_viewpoint, draw_check_blocks, and copy_view's null branch were all fetched and agree with BN's control flow where BN was readable.
|
||||
|
||||
THE MODEL (how this completes FW sections 5-6): a ViewIntervalType is NOT a z-interval — it is a per-grid-corner VECTOR of 32 per-plane column classifications (bound[0] = the CY near plane from Render::viewer_world_space, bound[1..portal_npnts] = the active view's edge planes). get_pt_limit encodes each plane's intersection with the vertical column at (x,y) as a signed height: 0f = column wholly inside that plane's positive half-space, 1001f = wholly outside, +h = inside below h, -h = inside above h (sky_height = 1000f caps both). block_check then tests a landblock's z-slab [min_zval, max_zval] against the four corner columns, plane by plane: fully-outside-any-plane culls; per-plane mixing demotes to PARTIALLY_INSIDE; ENTIRELY_INSIDE requires unanimity on every plane. This is the landscape analog of viewconeCheck (which does the same CY+edge-planes test for a sphere) and consumes exactly the state installed by update_viewpoint (viewpoint, X/Y/Z axes, CY) + set_view (npnts, vertex planes, inmask).
|
||||
|
||||
Outdoor default view: set_default_view appends ONE full-viewport 4-vertex quad view to the static Render::window (screen corners (0,H),(W,H),(W,0),(0,0), 4 world-space edge planes built from corner eye rays with d = -dot(N, eye)), points PortalList at it, and activates it — so the outdoor walk always runs draw_check_blocks with exactly one view of 4 edge planes + CY (portal_inmask 0x1F). Landscape-through-portals reuses the identical loop with PortalList = &outside_view and view_count = N clipped exit views (FW section 5's 'ov' arm).
|
||||
|
||||
Constants for the port: F_EPSILON = 0.000199999995f; sky_height = 1000f; inside_val = 0f; outside_val = 1001f; block_length = 192f (24*8); ViewIntervalType = float[32] (0x80 bytes); BoundingType OUTSIDE=0 / PARTIALLY_INSIDE=1 / ENTIRELY_INSIDE=2; view_vertex stride 0x18 (Vec2Dscreen pt + Plane).
|
||||
|
||||
Key traps recorded in per-function gotchas: (1) BN elided block_length to '0f' in draw_check_blocks — 24f in the neighborhood belongs to landcell_check, not this function; porting 24 here would shrink the visible landscape 8x. (2) block_check receives (max_zval, min_zval) but corner_plane_check consumes (min_z, max_z) via a double positional swap. (3) equality edges: touching a clip height on the out side is OUTSIDE, on the in side is ENTIRELY_INSIDE. (4) update_viewpoint(Frame) wraps with objcell_id = 0 (BN's 0x796910 is Position's vftable). (5) the D3D world-to-view Matrix4 element order is decompiler-ambiguous (flagged, not guessed) — semantics are right/up/forward rows with view Z = forward; not load-bearing for the CPU walk.
|
||||
|
||||
Not extracted (out of scope, already covered or adjacent): copy_view's non-null source path and set_view are as FW section 6 describes (verified in passing); LScape::landcell_check @0x005050a0 is the per-cell (24 m) refinement using the same get_clip_height/interval machinery plus get_block_orient — it is the natural next extraction if FW needs cell-level in_view.
|
||||
|
||||
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
# FW0 — retail walk-oracle traces (captured 2026-08-30)
|
||||
|
||||
Live cdb captures of retail's per-frame draw walk (2013 v11.4186 client,
|
||||
PDB-paired, connected to local ACE; owner drove the character). These are
|
||||
the conformance fixtures for Campaign FW slice FW1: `RetailFrameWalk` must
|
||||
reproduce these sequences position-for-position. Capture harness:
|
||||
`tools/walk-oracle/` (`fw0-recon.cdb` dumped the offsets; `fw0-capture.cdb`
|
||||
is the template the per-pose scripts derive from).
|
||||
|
||||
## Trace format
|
||||
|
||||
One line per event, frames delimited by `F <n>`:
|
||||
|
||||
- `F n` — frame start (`SmartBox::RenderNormalMode` @0x00453AA0 entry).
|
||||
- `LS` — `LScape::draw` @0x00506330 (the landscape walk ran).
|
||||
- `BLD <cellid>` — `RenderDeviceD3D::DrawBuilding` @0x0059F2A0; the id is
|
||||
the building's `Position.objcell_id` (CBuildingObj+0x4C).
|
||||
- `DI <cellid>` — `PView::DrawInside` @0x005A5860; the EnvCell being
|
||||
descended into (arg at esp+4; id = CEnvCell+0x28 `m_DID`).
|
||||
- `DC pv=<PView*> ov=<outside_view.view_count> n=<cell_draw_num>: <ids…>`
|
||||
— `PView::DrawCells` @0x005A4840 entry; dumps the PView's whole
|
||||
`cell_draw_list` (PView+0x50 data / +0x60 count / +0x38 ov).
|
||||
|
||||
The last frame of each capture can be truncated (the auto-detach fires at
|
||||
the frame marker, before that frame's draws) — ignore the final frame when
|
||||
replaying.
|
||||
|
||||
## Traces
|
||||
|
||||
| File | Pose | Root shape | Why it matters |
|
||||
|---|---|---|---|
|
||||
| `terrace-center.log` | Sanctuary terrace center, still, 40f | OUTDOOR | Baseline outdoor frame: `LS`, 14 buildings far-to-near **including 0xF518002E every frame**, terrace cells drawn as 1-cell look-in punches at their building's turn. |
|
||||
| `terrace-edge.log` | Waterfall ledge (the #456 pose), still, 40f | OUTDOOR | **The #456 acceptance oracle.** Retail STILL draws 0xF518002E all 39 complete frames — the vista is hidden by depth cover, not by culling. Edge adds a second look-in cell (n=2: f4180106 f418010f). |
|
||||
| `cathedral-arrival.log` | Cathedral portal-in arrival, still, 40f | INTERIOR (DI f4180106) | Interior root: DI → DC(ov=1) → LS drawn THROUGH the exit view — and the building roster is culled to 10; **0xF518002E is absent**. Rooting selects the mechanism. |
|
||||
| `holtburg-doorway-still.log` | Standing in a cottage doorway, still, 40f | INTERIOR (DI a9b4013f) | The flap scene: root is rock-stable all 39 frames at the threshold. Note the same cell (a9b40100) punched by three consecutive look-in views — retail tolerates duplicate punches. |
|
||||
| `holtburg-walkout.log` | Walking inside → out of the cottage, 80f | DI a9b4013f (F1-76) → DI a9b40150 (F77-79) → OUTDOOR (F80) | The handover contract: interior cell-to-cell at F77, a clean ONE-FRAME interior→outdoor flip at F80. No intermediate state. |
|
||||
| `holtburg-street-porchcam.log` | Player in the street facing away from the cottage, still, 40f | INTERIOR (DI a9b40150) | **Proof the frame roots at the CAMERA's cell, not the player's** — the chase camera tucked into the porch keeps the frame interior-rooted while the player stands outdoors. Wide doorway view culls nothing (full 14-building roster). |
|
||||
| `holtburg-street-outdoor.log` | Mid-street, camera in the open, still, 40f | OUTDOOR | Clean outdoor town fixture: LS, 13 buildings, two look-in punches (n=3, n=2), zero DI. |
|
||||
| `holtburg-walkabout.log` | Walking through Holtburg outdoors, 80f | OUTDOOR throughout | Moving outdoor fixture; dense look-in punch activity (~10 DC/frame) as doorways sweep the view. |
|
||||
| `foundry-entry.log` | Walking from the street into the Holtburg foundry and down, 80f | OUTDOOR (F1-56) → DI a9b40178 (F57-78, **LS=false**) → DI a9b40177 (F79) | The outdoor→interior flip in one frame — and the pure-interior shape: the moment the camera faces down the stairs, no exit view survives, so NO landscape and NO buildings draw (ov=0). |
|
||||
| `foundry-deep.log` | Bottom of the foundry, still, 40f | INTERIOR (DI a9b40176) | Deep interior with a surviving chain: ov=1 through the stairwell, so retail draws the landscape AND 12 town buildings from the basement — all buried by depth. Confirms draw-and-let-z-cover is the philosophy everywhere. |
|
||||
|
||||
## Findings that bind the FW1 port
|
||||
|
||||
1. **The frame roots at the camera-eye's cell** (porchcam trace). Player
|
||||
position is irrelevant to rooting.
|
||||
2. **Outdoor root draws every distance-eligible building — including the
|
||||
#456 far building — and hides them by depth**, never by omission
|
||||
(terrace-edge trace). acdream's #456 bleed is therefore a HOLE in our
|
||||
drawn cover at that pose, not extra visibility. The cover mechanism
|
||||
inside `RenderDeviceD3D::DrawBuilding` (pre-punch flush + portal far-Z
|
||||
pass) is the next decomp read.
|
||||
3. **Interior root gates the world through the exit-view chain**: the
|
||||
building roster and landscape are whatever survives the chain's
|
||||
cone/clip — from nothing (foundry entry, ov=0) to the whole town
|
||||
(foundry deep, porchcam).
|
||||
4. **Look-ins draw at the owning building's walk turn**, as 1-3 cell
|
||||
`DrawCells` punches (ov=0) interleaved with the building sequence, and
|
||||
duplicate punches of the same cell are tolerated.
|
||||
5. **Transitions are one-frame flips** with no blended or intermediate
|
||||
state (walkout F80, foundry F57), and the root is perfectly stable
|
||||
when stationary — even standing in a doorway.
|
||||
6. **Buildings draw far-to-near** (`LScape::calc_draw_order`), and the
|
||||
whole-frame sequence repeats bit-for-bit while the camera is still.
|
||||
|
||||
## Pose-stamped round (2026-08-30 evening, `posed/`)
|
||||
|
||||
The second capture round adds a `P` line per frame (raw IEEE-754 dwords
|
||||
from `Render::viewer_pos` @0x0081ef00: camera cell id, world origin xyz,
|
||||
the four Frame quaternion components in storage order) — the exact-replay
|
||||
inputs the first round lacked. Ten fixtures in `posed/`: cathedral-arrival,
|
||||
terrace-center (interior-rooted this round — different camera state than
|
||||
the morning; the pose stamp makes each fixture self-consistent),
|
||||
terrace-edge (outdoor, the #456 pose), holtburg-doorway-still,
|
||||
holtburg-walkout, holtburg-transitions (a full out-and-back crossing with
|
||||
all four root flips — supersedes walkout as the transition contract),
|
||||
holtburg-street-outdoor, holtburg-walkabout (80 moving frames),
|
||||
foundry-entry (outdoor approach; the flip fell outside the window),
|
||||
foundry-deep (the pure-interior shape: DI + DC ov=0, no landscape).
|
||||
Caveat: the pose is dumped at the frame MARKER (RenderNormalMode entry),
|
||||
i.e. before update_viewpoint copies the new camera state — for moving
|
||||
captures the stamped pose belongs to the PREVIOUS frame's draw; still
|
||||
captures are unaffected. FW1 conformance gates on the STILL fixtures
|
||||
first for exactly this reason.
|
||||
|
|
@ -1,665 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw0-cap-cathedral-arrival.log'
|
||||
0:018> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:018> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:018> .reload /f acclient.exe
|
||||
0:018> r $t0 = 0
|
||||
0:018> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:018> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:018> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:018> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:018> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:018> g
|
||||
F 1
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 2
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 3
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 4
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 5
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 6
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 7
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 8
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 9
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 10
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 11
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 12
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 13
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 14
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 15
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 16
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 17
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 18
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 19
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 20
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 21
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 22
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 23
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 24
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 25
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 26
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 27
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 28
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 29
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 30
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 31
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 32
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 33
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 34
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 35
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 36
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 37
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 38
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 39
|
||||
DI f4180106
|
||||
DC pv=009d4a80 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4b08 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 40
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
|
|
@ -1,665 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw0-cap-foundry-deep.log'
|
||||
0:018> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:018> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:018> .reload /f acclient.exe
|
||||
0:018> r $t0 = 0
|
||||
0:018> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:018> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:018> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:018> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:018> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:018> g
|
||||
F 1
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 2
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 3
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 4
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 5
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 6
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 7
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 8
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 9
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 10
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 11
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 12
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 13
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 14
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 15
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 16
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 17
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 18
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 19
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 20
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 21
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 22
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 23
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 24
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 25
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 26
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 27
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 28
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 29
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 30
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 31
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 32
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 33
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 34
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 35
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 36
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 37
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 38
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 39
|
||||
DI a9b40176
|
||||
DC pv=009d4a80 ov=1 n=2: a9b40176 a9b40177
|
||||
LS
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
BLD a9b40032
|
||||
BLD a9b40022
|
||||
BLD a9b4001a
|
||||
BLD a9b40036
|
||||
BLD a9b4002f
|
||||
BLD a9b4000f
|
||||
BLD a9b40026
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40017
|
||||
F 40
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,916 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw0-cap-holtburg-doorway.log'
|
||||
0:018> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:018> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:018> .reload /f acclient.exe
|
||||
0:018> r $t0 = 0
|
||||
0:018> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:018> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:018> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:018> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:018> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:018> g
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 1
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 2
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 3
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 4
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 5
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 6
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 7
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 8
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 9
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 10
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 11
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 12
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 13
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 14
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 15
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 16
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 17
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 18
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 19
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 20
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 21
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 22
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 23
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 24
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 25
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 26
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 27
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 28
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 29
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 30
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 31
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 32
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 33
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 34
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 35
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 36
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 37
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 38
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 39
|
||||
DI a9b4013f
|
||||
DC pv=009d4a80 ov=1 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40103 a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
DC pv=009d4b08 ov=0 n=1: a9b40100
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 40
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
|
|
@ -1,718 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw0-cap-holtburg-street.log'
|
||||
0:018> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:018> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:018> .reload /f acclient.exe
|
||||
0:018> r $t0 = 0
|
||||
0:018> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:018> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:018> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:018> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:018> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:018> g
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 1
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 2
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 3
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 4
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 5
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 6
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 7
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 8
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 9
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 10
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 11
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 12
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 13
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 14
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 15
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 16
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 17
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 18
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 19
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 20
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 21
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 22
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 23
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 24
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 25
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 26
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 27
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 28
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 29
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 30
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 31
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 32
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 33
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 34
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 35
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 36
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 37
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 38
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 39
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4b08 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 40
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
|
|
@ -1,910 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw0-cap-holtburg-street.log'
|
||||
0:018> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:018> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:018> .reload /f acclient.exe
|
||||
0:018> r $t0 = 0
|
||||
0:018> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:018> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:018> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:018> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:018> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:018> g
|
||||
F 1
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 2
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 3
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 4
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 5
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 6
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 7
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 8
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 9
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 10
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 11
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 12
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 13
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 14
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 15
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 16
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 17
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 18
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 19
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 20
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 21
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 22
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 23
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
DC pv=009d4b08 ov=0 n=1: aab50100
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 24
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 25
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 26
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 27
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 28
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 29
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 30
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 31
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 32
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 33
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 34
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 35
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40116 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40118 a9b40117
|
||||
DC pv=009d4b08 ov=0 n=2: a9b4011d a9b4011b
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 36
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 37
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 38
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 39
|
||||
DI a9b40150
|
||||
DC pv=009d4a80 ov=1 n=1: a9b40150
|
||||
LS
|
||||
BLD aab50002
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
DC pv=009d4b08 ov=0 n=2: a9b40164 a9b40162
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 40
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,716 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw1-cap-cathedral-arrival.log'
|
||||
0:021> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:021> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:021> .reload /f acclient.exe
|
||||
0:021> r $t0 = 0
|
||||
0:021> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .printf \"P %08x %08x %08x %08x %08x %08x %08x %08x\\n\", poi(0x81ef00+4), poi(0x81ef00+0x3c), poi(0x81ef00+0x40), poi(0x81ef00+0x44), poi(0x81ef00+8), poi(0x81ef00+0xc), poi(0x81ef00+0x10), poi(0x81ef00+0x14); .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:021> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:021> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:021> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:021> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:021> g
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 1
|
||||
P f4180106 4217ce4b 4238a567 432c2114 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 2
|
||||
P f4180106 4217cef9 4238a3c4 432c2136 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 3
|
||||
P f4180106 4217cef9 4238a3c4 432c2136 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 4
|
||||
P f4180106 4217cef9 4238a3c4 432c2136 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 5
|
||||
P f4180106 4217cef8 4238a3c6 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 6
|
||||
P f4180106 4217cef8 4238a3c6 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 7
|
||||
P f4180106 4217cef8 4238a3c6 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 8
|
||||
P f4180106 4217cef7 4238a3c9 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 9
|
||||
P f4180106 4217cef7 4238a3c9 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 10
|
||||
P f4180106 4217cef7 4238a3c9 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 11
|
||||
P f4180106 4217cef6 4238a3cc 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 12
|
||||
P f4180106 4217cef6 4238a3cc 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 13
|
||||
P f4180106 4217cef6 4238a3cc 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 14
|
||||
P f4180106 4217cef5 4238a3ce 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 15
|
||||
P f4180106 4217cef5 4238a3ce 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 16
|
||||
P f4180106 4217cef5 4238a3ce 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 17
|
||||
P f4180106 4217cef4 4238a3d0 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 18
|
||||
P f4180106 4217cef4 4238a3d0 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 19
|
||||
P f4180106 4217cef4 4238a3d0 432c2135 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 20
|
||||
P f4180106 4217cef3 4238a3d2 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 21
|
||||
P f4180106 4217cef3 4238a3d2 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 22
|
||||
P f4180106 4217cef3 4238a3d2 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 23
|
||||
P f4180106 4217cef2 4238a3d5 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 24
|
||||
P f4180106 4217cef2 4238a3d5 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 25
|
||||
P f4180106 4217cef2 4238a3d5 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 26
|
||||
P f4180106 4217cef1 4238a3d8 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 27
|
||||
P f4180106 4217cef1 4238a3d8 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 28
|
||||
P f4180106 4217cef1 4238a3d8 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 29
|
||||
P f4180106 4217cef0 4238a3da 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 30
|
||||
P f4180106 4217cef0 4238a3da 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 31
|
||||
P f4180106 4217cef0 4238a3da 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 32
|
||||
P f4180106 4217ceef 4238a3dc 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 33
|
||||
P f4180106 4217ceef 4238a3dc 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 34
|
||||
P f4180106 4217ceef 4238a3dc 432c2134 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 35
|
||||
P f4180106 4217ceee 4238a3de 432c2133 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 36
|
||||
P f4180106 4217ceee 4238a3de 432c2133 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 37
|
||||
P f4180106 4217ceee 4238a3de 432c2133 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 38
|
||||
P f4180106 4217ceed 4238a3e1 432c2133 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 39
|
||||
P f4180106 4217ceed 4238a3e1 432c2133 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
DI f4180106
|
||||
DC pv=009d4e38 ov=1 n=1: f4180106
|
||||
LS
|
||||
BLD f3180020
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=2: f4180101 f4180100
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
BLD f418000a
|
||||
F 40
|
||||
P f4180106 4217ceed 4238a3e1 432c2133 bf786b71 3e11d730 3ce81370 be45a7ad
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw1-cap-foundry-deep.log'
|
||||
0:021> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:021> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:021> .reload /f acclient.exe
|
||||
0:021> r $t0 = 0
|
||||
0:021> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .printf \"P %08x %08x %08x %08x %08x %08x %08x %08x\\n\", poi(0x81ef00+4), poi(0x81ef00+0x3c), poi(0x81ef00+0x40), poi(0x81ef00+0x44), poi(0x81ef00+8), poi(0x81ef00+0xc), poi(0x81ef00+0x10), poi(0x81ef00+0x14); .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:021> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:021> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:021> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:021> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:021> g
|
||||
F 1
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 2
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 3
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 4
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 5
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 6
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 7
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 8
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 9
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 10
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 11
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 12
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 13
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 14
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 15
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 16
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 17
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 18
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 19
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 20
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 21
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 22
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 23
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 24
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 25
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 26
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 27
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 28
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 29
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 30
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 31
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 32
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 33
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 34
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 35
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 36
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 37
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 38
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 39
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
DI a9b40176
|
||||
DC pv=009d4e38 ov=0 n=1: a9b40176
|
||||
F 40
|
||||
P a9b40176 4290a349 431a4e89 427c8503 3f617e5d be0461c6 3d8771b3 bee6b58d
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,744 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw1-cap-holtburg-doorway.log'
|
||||
0:020> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:020> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:020> .reload /f acclient.exe
|
||||
0:020> r $t0 = 0
|
||||
0:020> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .printf \"P %08x %08x %08x %08x %08x %08x %08x %08x\\n\", poi(0x81ef00+4), poi(0x81ef00+0x3c), poi(0x81ef00+0x40), poi(0x81ef00+0x44), poi(0x81ef00+8), poi(0x81ef00+0xc), poi(0x81ef00+0x10), poi(0x81ef00+0x14); .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:020> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:020> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:020> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:020> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:020> g
|
||||
F 1
|
||||
P a9b4013f 43054826 41604391 42c0a8ff 3f7ad6aa be1342bb bca4fd4d 3e0c84c0
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 2
|
||||
P a9b4013f 4305484d 41604402 42c0a8f6 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 3
|
||||
P a9b4013f 4305484d 41604406 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 4
|
||||
P a9b4013f 4305484d 41604407 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 5
|
||||
P a9b4013f 4305484d 41604408 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 6
|
||||
P a9b4013f 4305484d 41604409 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 7
|
||||
P a9b4013f 4305484d 4160440a 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 8
|
||||
P a9b4013f 4305484d 4160440b 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 9
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 10
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 11
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 12
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 13
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 14
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 15
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 16
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 17
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 18
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 19
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 20
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 21
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 22
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 23
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 24
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 25
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 26
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 27
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 28
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 29
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 30
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 31
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 32
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 33
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 34
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 35
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 36
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 37
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 38
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 39
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
DI a9b4013f
|
||||
DC pv=009d4e38 ov=2 n=3: a9b4013f a9b40150 a9b4014c
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
BLD a9b40022
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 40
|
||||
P a9b4013f 4305484d 4160440c 42c0a8f7 3f7ad5a6 be134223 bca51f4a 3e0ca1b2
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
|
|
@ -1,744 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw1-cap-street-outdoor.log'
|
||||
0:020> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:020> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:020> .reload /f acclient.exe
|
||||
0:020> r $t0 = 0
|
||||
0:020> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .printf \"P %08x %08x %08x %08x %08x %08x %08x %08x\\n\", poi(0x81ef00+4), poi(0x81ef00+0x3c), poi(0x81ef00+0x40), poi(0x81ef00+0x44), poi(0x81ef00+8), poi(0x81ef00+0xc), poi(0x81ef00+0x10), poi(0x81ef00+0x14); .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:020> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:020> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:020> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:020> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:020> g
|
||||
F 1
|
||||
P a9b40029 4304c599 41bf2387 42c0a910 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 2
|
||||
P a9b40029 4304c57b 41bf238b 42c0a8fe 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 3
|
||||
P a9b40029 4304c57c 41bf238b 42c0a8fe 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 4
|
||||
P a9b40029 4304c57d 41bf238a 42c0a8ff 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 5
|
||||
P a9b40029 4304c57e 41bf238a 42c0a8ff 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 6
|
||||
P a9b40029 4304c57f 41bf238a 42c0a900 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 7
|
||||
P a9b40029 4304c580 41bf238a 42c0a901 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 8
|
||||
P a9b40029 4304c581 41bf238a 42c0a901 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 9
|
||||
P a9b40029 4304c582 41bf238a 42c0a902 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 10
|
||||
P a9b40029 4304c583 41bf238a 42c0a902 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 11
|
||||
P a9b40029 4304c584 41bf2389 42c0a903 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 12
|
||||
P a9b40029 4304c585 41bf2389 42c0a904 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 13
|
||||
P a9b40029 4304c586 41bf2389 42c0a904 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 14
|
||||
P a9b40029 4304c587 41bf2389 42c0a905 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 15
|
||||
P a9b40029 4304c588 41bf2389 42c0a905 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 16
|
||||
P a9b40029 4304c589 41bf2389 42c0a906 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 17
|
||||
P a9b40029 4304c58a 41bf2389 42c0a907 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 18
|
||||
P a9b40029 4304c58b 41bf2388 42c0a907 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 19
|
||||
P a9b40029 4304c58c 41bf2388 42c0a908 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 20
|
||||
P a9b40029 4304c58d 41bf2388 42c0a908 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 21
|
||||
P a9b40029 4304c58e 41bf2388 42c0a909 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 22
|
||||
P a9b40029 4304c58f 41bf2388 42c0a90a 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 23
|
||||
P a9b40029 4304c590 41bf2388 42c0a90a 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 24
|
||||
P a9b40029 4304c591 41bf2388 42c0a90b 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 25
|
||||
P a9b40029 4304c592 41bf2388 42c0a90b 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 26
|
||||
P a9b40029 4304c593 41bf2387 42c0a90c 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 27
|
||||
P a9b40029 4304c594 41bf2387 42c0a90d 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 28
|
||||
P a9b40029 4304c595 41bf2387 42c0a90d 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 29
|
||||
P a9b40029 4304c596 41bf2387 42c0a90e 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 30
|
||||
P a9b40029 4304c597 41bf2387 42c0a90e 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 31
|
||||
P a9b40029 4304c598 41bf2387 42c0a90f 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 32
|
||||
P a9b40029 4304c599 41bf2387 42c0a910 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 33
|
||||
P a9b40029 4304c59a 41bf2386 42c0a910 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 34
|
||||
P a9b40029 4304c59b 41bf2386 42c0a911 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 35
|
||||
P a9b40029 4304c59c 41bf2386 42c0a911 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 36
|
||||
P a9b40029 4304c59d 41bf2386 42c0a912 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 37
|
||||
P a9b40029 4304c59e 41bf2386 42c0a913 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 38
|
||||
P a9b40029 4304c59f 41bf2386 42c0a913 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 39
|
||||
P a9b40029 4304c5a0 41bf2386 42c0a914 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
LS
|
||||
BLD a9b3003c
|
||||
BLD a9b4000f
|
||||
BLD a9b40017
|
||||
BLD a9b4002f
|
||||
BLD a9b40016
|
||||
BLD a9b4001e
|
||||
BLD a9b40026
|
||||
BLD a9b40036
|
||||
BLD a9b4001a
|
||||
DC pv=009d4398 ov=0 n=3: a9b4016e a9b4016a a9b4016c
|
||||
BLD a9b40022
|
||||
DC pv=009d4398 ov=0 n=4: a9b40164 a9b40162 a9b40167 a9b40169
|
||||
BLD a9b40032
|
||||
BLD a9b40031
|
||||
BLD a9b40029
|
||||
F 40
|
||||
P a9b40029 4304c5a1 41bf2385 42c0a914 3f34a2ab bdd417c0 bdd07917 3f318d73
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,30 +0,0 @@
|
|||
pose cell=a9b40029 origin=<132.77141, 23.892355, 96.33006>
|
||||
building a9b4001a: dist=54.7 eff=0.0 eyeLocal=<47.290646, 28.046478, 2.3300629> portals=3
|
||||
level BSP portal refs: 3
|
||||
ref idx=2 other=a9b4016e rawSide=0 exact=True planeD@eye=-43.04 eyeSide=1 clipN=0 gate(side==rawSide)=reject
|
||||
ref idx=1 other=a9b4016d rawSide=0 exact=True planeD@eye=51.54 eyeSide=0 clipN=0 gate(side==rawSide)=PASS
|
||||
ref idx=0 other=a9b4016c rawSide=0 exact=True planeD@eye=33.35 eyeSide=0 clipN=0 gate(side==rawSide)=PASS
|
||||
building a9b4001e: dist=120.2 eff=20.2 eyeLocal=<107.64956, 48.678543, 30.330063> portals=10
|
||||
level BSP portal refs: 10
|
||||
ref idx=6 other=a9b40107 rawSide=0 exact=True planeD@eye=115.15 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
|
||||
ref idx=9 other=a9b4010b rawSide=0 exact=True planeD@eye=59.68 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
|
||||
ref idx=4 other=a9b40102 rawSide=0 exact=True planeD@eye=112.65 eyeSide=0 clipN=6 gate(side==rawSide)=PASS
|
||||
ref idx=3 other=a9b40103 rawSide=0 exact=True planeD@eye=-102.65 eyeSide=1 clipN=6 gate(side==rawSide)=reject
|
||||
ref idx=7 other=a9b40109 rawSide=0 exact=True planeD@eye=59.68 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
|
||||
ref idx=8 other=a9b4010a rawSide=0 exact=True planeD@eye=59.68 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
|
||||
ref idx=5 other=a9b40106 rawSide=0 exact=True planeD@eye=116.65 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
|
||||
ref idx=0 other=a9b40100 rawSide=0 exact=True planeD@eye=-25.33 eyeSide=1 clipN=4 gate(side==rawSide)=reject
|
||||
ref idx=1 other=a9b40100 rawSide=0 exact=True planeD@eye=-25.33 eyeSide=1 clipN=4 gate(side==rawSide)=reject
|
||||
ref idx=2 other=a9b40100 rawSide=0 exact=True planeD@eye=-25.33 eyeSide=1 clipN=4 gate(side==rawSide)=reject
|
||||
building a9b40022: dist=28.3 eff=0.0 eyeLocal=<12.107626, 25.2714, 2.3300629> portals=3
|
||||
level BSP portal refs: 3
|
||||
ref idx=2 other=a9b4015e rawSide=0 exact=True planeD@eye=17.11 eyeSide=0 clipN=0 gate(side==rawSide)=PASS
|
||||
ref idx=0 other=a9b40164 rawSide=0 exact=True planeD@eye=-14.27 eyeSide=1 clipN=0 gate(side==rawSide)=reject
|
||||
ref idx=1 other=a9b40165 rawSide=0 exact=True planeD@eye=35.27 eyeSide=0 clipN=0 gate(side==rawSide)=PASS
|
||||
building a9b40026: dist=117.7 eff=17.7 eyeLocal=<-28.271423, 111.60765, 30.330063> portals=5
|
||||
level BSP portal refs: 5
|
||||
ref idx=1 other=a9b40120 rawSide=1 exact=True planeD@eye=-117.26 eyeSide=1 clipN=4 gate(side==rawSide)=PASS
|
||||
ref idx=2 other=a9b40122 rawSide=0 exact=True planeD@eye=117.26 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
|
||||
ref idx=4 other=a9b40125 rawSide=0 exact=True planeD@eye=117.26 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
|
||||
ref idx=3 other=a9b40124 rawSide=0 exact=True planeD@eye=-22.07 eyeSide=1 clipN=4 gate(side==rawSide)=reject
|
||||
ref idx=0 other=a9b4011f rawSide=0 exact=True planeD@eye=-22.07 eyeSide=1 clipN=4 gate(side==rawSide)=reject
|
||||
|
|
@ -1,789 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw1-cap-terrace-center.log'
|
||||
0:018> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:018> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:018> .reload /f acclient.exe
|
||||
0:018> r $t0 = 0
|
||||
0:018> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .printf \"P %08x %08x %08x %08x %08x %08x %08x %08x\\n\", poi(0x81ef00+4), poi(0x81ef00+0x3c), poi(0x81ef00+0x40), poi(0x81ef00+0x44), poi(0x81ef00+8), poi(0x81ef00+0xc), poi(0x81ef00+0x10), poi(0x81ef00+0x14); .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:018> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:018> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:018> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:018> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:018> g
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 1
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 2
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 3
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 4
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 5
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 6
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 7
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 8
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 9
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 10
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 11
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 12
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 13
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 14
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 15
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 16
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 17
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 18
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 19
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 20
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 21
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 22
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 23
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 24
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 25
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 26
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 27
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 28
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 29
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 30
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 31
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 32
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 33
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 34
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 35
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 36
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 37
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 38
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 39
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
DI f4180104
|
||||
DC pv=009d4e38 ov=1 n=1: f4180104
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
F 40
|
||||
P f4180104 42076245 425ebea2 432c214a bf3c0083 3ddcbdee bdc74a9f 3f29bba9
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
|
|
@ -1,834 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw1-cap-terrace-edge.log'
|
||||
0:018> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:018> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:018> .reload /f acclient.exe
|
||||
0:018> r $t0 = 0
|
||||
0:018> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .printf \"P %08x %08x %08x %08x %08x %08x %08x %08x\\n\", poi(0x81ef00+4), poi(0x81ef00+0x3c), poi(0x81ef00+0x40), poi(0x81ef00+0x44), poi(0x81ef00+8), poi(0x81ef00+0xc), poi(0x81ef00+0x10), poi(0x81ef00+0x14); .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:018> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:018> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:018> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:018> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:018> g
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 1
|
||||
P f418000b 41c6b82f 425abe2d 432c216f bf3bfcd6 3ddcb99d bdc74f67 3f29bfbb
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 2
|
||||
P f418000b 41c6bab4 425abe28 432c2156 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 3
|
||||
P f418000b 41c6bab4 425abe28 432c2156 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 4
|
||||
P f418000b 41c6bab5 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 5
|
||||
P f418000b 41c6bab5 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 6
|
||||
P f418000b 41c6bab3 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 7
|
||||
P f418000b 41c6bab3 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 8
|
||||
P f418000b 41c6bab2 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 9
|
||||
P f418000b 41c6bab2 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 10
|
||||
P f418000b 41c6bab1 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 11
|
||||
P f418000b 41c6bab1 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 12
|
||||
P f418000b 41c6bab0 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 13
|
||||
P f418000b 41c6bab0 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 14
|
||||
P f418000b 41c6baaf 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 15
|
||||
P f418000b 41c6baaf 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 16
|
||||
P f418000b 41c6baae 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 17
|
||||
P f418000b 41c6baae 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 18
|
||||
P f418000b 41c6baad 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 19
|
||||
P f418000b 41c6baad 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 20
|
||||
P f418000b 41c6baac 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 21
|
||||
P f418000b 41c6baac 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 22
|
||||
P f418000b 41c6baab 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 23
|
||||
P f418000b 41c6baab 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 24
|
||||
P f418000b 41c6baaa 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 25
|
||||
P f418000b 41c6baaa 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 26
|
||||
P f418000b 41c6baa9 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 27
|
||||
P f418000b 41c6baa9 425abe27 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 28
|
||||
P f418000b 41c6baa8 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 29
|
||||
P f418000b 41c6baa8 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 30
|
||||
P f418000b 41c6baa7 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 31
|
||||
P f418000b 41c6baa7 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 32
|
||||
P f418000b 41c6baa6 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 33
|
||||
P f418000b 41c6baa6 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 34
|
||||
P f418000b 41c6baa5 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 35
|
||||
P f418000b 41c6baa5 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 36
|
||||
P f418000b 41c6baa5 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 37
|
||||
P f418000b 41c6baa5 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 38
|
||||
P f418000b 41c6baa5 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 39
|
||||
P f418000b 41c6baa5 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4398 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
DC pv=009d4398 ov=0 n=1: f4180101
|
||||
BLD f418000b
|
||||
DC pv=009d4398 ov=0 n=1: f4180104
|
||||
F 40
|
||||
P f418000b 41c6baa5 425abe26 432c2157 bf3bfcd5 3ddcb99c bdc74f68 3f29bfbc
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
|
|
@ -1,743 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw0-cap-terrace-center.log'
|
||||
0:021> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:021> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:021> .reload /f acclient.exe
|
||||
0:021> r $t0 = 0
|
||||
0:021> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:021> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:021> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:021> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:021> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:021> g
|
||||
F 1
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 2
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 3
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 4
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 5
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 6
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 7
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 8
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 9
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 10
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 11
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 12
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 13
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 14
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 15
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 16
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 17
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 18
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 19
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 20
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 21
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 22
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 23
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 24
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 25
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 26
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 27
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 28
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 29
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 30
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 31
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 32
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 33
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 34
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 35
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 36
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 37
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 38
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 39
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=1: f4180106
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 40
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
|
|
@ -1,743 +0,0 @@
|
|||
Opened log file 'C:\Users\erikn\AppData\Local\Temp\claude\C--Users-erikn-source-repos-acdream--claude-worktrees-peaceful-blackburn-5333f0\e3dbd387-d947-4f49-964d-d454fc9c7d83\scratchpad\fw0-cap-terrace-edge.log'
|
||||
0:018> .sympath C:\Users\erikn\source\repos\acdream\refs
|
||||
Symbol search path is: C:\Users\erikn\source\repos\acdream\refs
|
||||
Expanded Symbol search path is: c:\users\erikn\source\repos\acdream\refs
|
||||
|
||||
************* Path validation summary **************
|
||||
Response Time (ms) Location
|
||||
OK C:\Users\erikn\source\repos\acdream\refs
|
||||
0:018> .symopt+ 0x40
|
||||
Symbol options are 0xB0367:
|
||||
0x00000001 - SYMOPT_CASE_INSENSITIVE
|
||||
0x00000002 - SYMOPT_UNDNAME
|
||||
0x00000004 - SYMOPT_DEFERRED_LOADS
|
||||
0x00000020 - SYMOPT_OMAP_FIND_NEAREST
|
||||
0x00000040 - SYMOPT_LOAD_ANYTHING
|
||||
0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
|
||||
0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
|
||||
0x00010000 - SYMOPT_AUTO_PUBLICS
|
||||
0x00020000 - SYMOPT_NO_IMAGE_SEARCH
|
||||
0x00080000 - SYMOPT_NO_PROMPTS
|
||||
0:018> .reload /f acclient.exe
|
||||
0:018> r $t0 = 0
|
||||
0:018> bp 00453aa0 "r $t0 = @$t0 + 1; .printf \"F %d\\n\", @$t0; .if (@$t0 >= 0n40) { qd } .else { gc }"
|
||||
0:018> bp 005a4840 "r $t2 = poi(@ecx+0x60); .printf \"DC pv=%08x ov=%d n=%d:\", @ecx, poi(@ecx+0x38), @$t2; r $t3 = poi(@ecx+0x50); r $t4 = 0; .while (@$t4 < @$t2) { r $t5 = poi(@$t3 + @$t4*4); .printf \" %08x\", poi(@$t5+0x28); r $t4 = @$t4 + 1 }; .printf \"\\n\"; gc"
|
||||
0:018> bp 005a5860 ".printf \"DI %08x\\n\", poi(poi(@esp+4)+0x28); gc"
|
||||
0:018> bp 00506330 ".printf \"LS\\n\"; gc"
|
||||
0:018> bp 0059f2a0 ".printf \"BLD %08x\\n\", poi(poi(@esp+4)+0x4c); gc"
|
||||
0:018> g
|
||||
F 1
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 2
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 3
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 4
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 5
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 6
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 7
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 8
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 9
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 10
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 11
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 12
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 13
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 14
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 15
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 16
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 17
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 18
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 19
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 20
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 21
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 22
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 23
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 24
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 25
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 26
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 27
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 28
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 29
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 30
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 31
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 32
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 33
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 34
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 35
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 36
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 37
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 38
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 39
|
||||
LS
|
||||
BLD f518002e
|
||||
BLD f4170011
|
||||
BLD f4170012
|
||||
BLD f4170002
|
||||
BLD f3180020
|
||||
BLD f4180011
|
||||
BLD f4180001
|
||||
BLD f4180009
|
||||
BLD f4180002
|
||||
BLD f4180004
|
||||
BLD f4180014
|
||||
BLD f418000a
|
||||
DC pv=009d4b08 ov=0 n=2: f4180106 f418010f
|
||||
BLD f418000c
|
||||
BLD f418000b
|
||||
DC pv=009d4b08 ov=0 n=1: f4180104
|
||||
F 40
|
||||
quit:
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\atlmfc.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\concurrency.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\cpp_rest.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\ObjectiveC.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\stl.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Data.Json.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Geolocation.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Devices.Sensors.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\Windows.Media.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\windows.natvis'
|
||||
NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\Visualizers\winrt.natvis'
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,277 +0,0 @@
|
|||
# FW0 — the retail frame walk, port-ready pseudocode model (2026-08-30)
|
||||
|
||||
The distilled model FW1 ports from. Sources: the five decomp reads in
|
||||
[`2026-08-30-fw-walk-pseudocode-appendix.md`](2026-08-30-fw-walk-pseudocode-appendix.md)
|
||||
(BN pseudo-C cross-checked against the Ghidra patchmem decomp wherever
|
||||
x87-flag mush demanded it), reconciled against the live traces in
|
||||
[`2026-08-30-fw-walk-oracle/`](2026-08-30-fw-walk-oracle/README.md).
|
||||
Every function below carries its named symbol + address; the appendix has
|
||||
the full extractions with per-function gotchas.
|
||||
|
||||
## 1. The frame root — `SmartBox::RenderNormalMode` @0x00453aa0
|
||||
|
||||
Per frame (via `SmartBox::Draw` → `DrawNoBlit` @0x00454c20, which first
|
||||
runs `update_viewer` and skips rendering entirely when `viewer_cell` is
|
||||
null):
|
||||
|
||||
```
|
||||
if ((viewer.objcell_id & 0xFFFF) < 0x100) { // CAMERA cell is outdoor
|
||||
LScape::update_viewpoint(lscape, viewer.objcell_id);
|
||||
Render::update_viewpoint(&viewer);
|
||||
Render::set_default_view(); // full-screen view
|
||||
Render::useSunlightSet(1);
|
||||
LScape::draw(lscape); // → §3
|
||||
} else { // camera in an EnvCell
|
||||
if (viewer_cell->seen_outside)
|
||||
LScape::update_viewpoint(lscape, Position::get_outside_cell_id(&viewer));
|
||||
Render::update_viewpoint(&viewer);
|
||||
render_device->DrawInside(viewer_cell); // → PView::DrawInside, §5
|
||||
}
|
||||
D3DPolyRender::FlushAlphaList(0f);
|
||||
```
|
||||
|
||||
`SmartBox::update_viewer` @0x00453ce0 resolves the camera pose and cell:
|
||||
a `CTransition` sphere sweep (the global `viewer_sphere`) from a pivot on
|
||||
the player (CameraManager pivot part + offset) toward the camera's sought
|
||||
position; `t->sphere_path.curr_cell` becomes `viewer_cell`. Fallbacks:
|
||||
`AdjustPosition` on the raw sought position, else camera at the player
|
||||
with `viewer_cell = null` (frame skipped). **The frame roots at the
|
||||
CAMERA's cell** — proven live by the porch-cam trace.
|
||||
|
||||
## 2. The invisible-panel primitive — `D3DPolyRender::DrawPortalPolyInternal` @0x0059bc90
|
||||
|
||||
The single primitive behind both "punch" and "seal": draws one portal
|
||||
polygon as an untextured CULL_NONE triangle fan, **depth-test ALWAYS,
|
||||
z-write ON, color alpha 0** (invisible under SRCALPHA/INVSRCALPHA).
|
||||
Byte-verified in the raw pseudo-C: `SetDepthBufferMode(DEPTHTEST_ALWAYS,
|
||||
(mode>>2)&1)` @0059be02; forced Z `0.999998987f` @0059bf4b.
|
||||
|
||||
- `flag=1` (mode global `maxZ1`=7): every vertex Z forced to
|
||||
**0.999998987 (far plane)** — the **PUNCH**: re-opens the aperture's
|
||||
depth so look-in content can draw into it.
|
||||
- `flag=0` (mode `maxZ2`=6): real projected Z — the **SEAL**: writes the
|
||||
portal surface's own depth at the aperture. Only seals increment
|
||||
`portalsDrawnCount` (the deferred z-clear trigger).
|
||||
- Trivial reject: polygon wholly outside a ±12.0 local-XY box (the
|
||||
24 m cell); the reject-loop polarity is an FP-status idiom (`test
|
||||
ah,0x44`) BN could not lift — **re-verify polarity at FW1 against the
|
||||
binary bytes** before porting.
|
||||
- Debug: raising the mode's alpha bit (via the maxZ1/maxZ2 globals,
|
||||
.data @0x820e18/0x820e14) renders the panels visibly in cycling colors.
|
||||
- Goes through `DrawPrimitiveUP`, gated by the registry bool
|
||||
`s_bAllowDrawPrimitiveUP` (default 1).
|
||||
- Restores NO state — callers rely on backup/restore_curr_state.
|
||||
|
||||
**This resolves the campaign's panel question:** the "seal panels" are
|
||||
not mesh subsets (those authored Translucency-1.0 subsets stay
|
||||
never-drawn); they are the buildings'/cells' PORTAL POLYGONS drawn
|
||||
depth-only by this primitive. AD-117's stamps unknowingly re-invented a
|
||||
real retail mechanism at the wrong site with the wrong geometry.
|
||||
|
||||
## 3. The outdoor walk — `LScape::draw` @0x00506330
|
||||
|
||||
```
|
||||
GameSky::Draw(sky, 0) // dome
|
||||
LScape::draw_check_blocks(this) // per-view visibility union (below)
|
||||
for i = mid_width² - 1 .. 0: // block_draw_list built near-to-far →
|
||||
blk = block_draw_list[i] // backwards walk = FAR-TO-NEAR
|
||||
if (blk && blk->in_view) render_device->DrawBlock(blk)
|
||||
if (weather) GameSky::Draw(sky, 1)
|
||||
```
|
||||
|
||||
- Grid: `mid_width`=11 × 11 blocks (`mid_radius`=5); block side 192 m
|
||||
(FPU-elided in BN — pinned by the explicit 24 m cell pitch).
|
||||
- `get_block_order` @0x00504c50: viewer's block first, then concentric
|
||||
rings (8 quadrant-symmetric slots per step from static coefficient
|
||||
tables); rebuild only when the viewer crosses a LANDBLOCK boundary.
|
||||
- Block frames are **viewer-relative**: origin
|
||||
`((row−viewer_b_xoff)·192, (col−viewer_b_yoff)·192, 0)` — the
|
||||
landscape renders in viewer-block-local space for float precision.
|
||||
- `draw_check_blocks` @0x00505f80: clears every block/cell `in_view`,
|
||||
then FOR EACH VIEW of `Render::PortalList` (outdoor: the default view;
|
||||
interior: the exit views) unions visibility via clip-height interval
|
||||
grids (`get_clip_height` corners → `block_check` vs max/min_zval);
|
||||
`landcell_check` @0x005050a0 refines per-cell (non-8×8 far blocks:
|
||||
all cells PARTIAL; ENTIRELY_INSIDE blocks: all cells 2; else per-cell
|
||||
corner grids at 24 m pitch). A cell marked by an earlier view is never
|
||||
downgraded.
|
||||
- `CLandBlock::calc_sq_draw_order` @0x0052f4a0: per-block cell order,
|
||||
closest cell (picked by the 9-case compass direction switch) written
|
||||
LAST, rings filled backwards → forward iteration = far-to-near.
|
||||
Early-out compares only `closest.x/y`, not `dir` (keep this quirk).
|
||||
|
||||
Per block, `RenderDeviceD3D::DrawBlock` @0x005a17c0: pass 1 updates
|
||||
in-view cells' shadow objects + insertion-sorts shadow parts by depth;
|
||||
pass 2 per cell (far-to-near): terrain (`DrawLandCell` — detail texture
|
||||
only on full 8×8 blocks) then `DrawSortCell` @0x0059f140:
|
||||
|
||||
```
|
||||
if (cell->building) render_device->DrawBuilding(cell->building) // §4
|
||||
render_device->DrawObjCell(cell) // scenery/statics/dynamics via sorted shadow parts → CPhysicsPart::Draw(part, 0)
|
||||
```
|
||||
|
||||
Mid-walk alpha pressure valve: `FlushAlphaList(flush)` with the global
|
||||
`flush`=0.75 (flushes only above ~2250 queued alpha meshes).
|
||||
|
||||
## 4. Buildings — `RenderDeviceD3D::DrawBuilding` @0x0059f2a0
|
||||
|
||||
```
|
||||
outdoor_pview->outdoor_portal_list = b->portals; // ALWAYS
|
||||
CPhysicsPart::UpdateViewerDistance(part0); // degrade level
|
||||
if (part0->gfxobj[deg_level] == 0) return; // degraded out → whole building skipped
|
||||
CBuildingObj::curr_pos = &b->m_position; // frame-restore anchor for the portal pass
|
||||
set building detail-surface state; FlushAlphaList(0f);
|
||||
CPhysicsPart::Draw(part0, 1); // PORTAL pass
|
||||
ObjBuildingOrBuildingPart = 1;
|
||||
CPhysicsPart::Draw(part0, 0); // SHELL pass (normal mesh; per-frame dedupe applies)
|
||||
ObjBuildingOrBuildingPart = 0;
|
||||
```
|
||||
|
||||
The portal pass, through `DrawMesh` @0x005a0860 (per portal view,
|
||||
`building_view`-gated; portal-flagged draws run even when viewconeCheck
|
||||
says OUTSIDE) → `DrawMeshInternal` @0x0059f360, walks the building's
|
||||
drawing-BSP **twice** (`BSPTREE::build_draw_portals_only` — portal nodes
|
||||
visited far-to-near, plane epsilon 0.0002):
|
||||
|
||||
- **Pass 1 — punch.** Each in_portal → `PView::DrawPortal` @0x005a5ab0 →
|
||||
`ConstructView(CBldPortal)` @0x005a59a0: viewer sidedness must match
|
||||
the authored `portal_side` (IN_PLANE within ±0.0002 fails BOTH gates);
|
||||
`GetClip` must leave ≥3 points; the destination EnvCell must be
|
||||
currently Visible (`CEnvCell::GetVisible` — not loaded ⇒ silently no
|
||||
punch, no fallback). On success: `DrawPortalPolyInternal(poly, 1)` —
|
||||
the far-Z punch — and the clipped view is pushed onto the destination
|
||||
cell's top `portal_view` via `Render::copy_view`.
|
||||
- **Pass 2 — look-in.** Same walk/gates; on success recurses
|
||||
`ConstructView(CEnvCell)` (the §5 flood seeded through
|
||||
`other_portal_id`) and calls `PView::DrawCells` — the trace's
|
||||
`DC ov=0` punches: interior cells + objects drawn into the punched
|
||||
aperture (no landscape, no clear, no sealing on this path).
|
||||
- Push/pop asymmetry (MUST preserve): `ConstructView(CBldPortal)` pops
|
||||
the building frame only on SUCCESS (before recursing); on failure the
|
||||
frame stays pushed and `DrawPortal` compensates by re-pushing
|
||||
`CBuildingObj::curr_pos` only on success.
|
||||
- `DrawCells` bumps `m_nFrameStamp`, re-arming the per-frame part dedupe
|
||||
so objects already drawn outdoors can draw again inside a look-in.
|
||||
- DEAD in 2013 (do not port): `DrawBuildingLeaf` + `curr_leaf_cells`
|
||||
(pre-EnvCell interior model); `PView::DrawPortal`'s pass==3
|
||||
seal-on-fail branch (pass only ever ∈ {1,2}).
|
||||
|
||||
## 5. Interior frames — `PView::DrawInside` @0x005a5860
|
||||
|
||||
```
|
||||
CEnvCell::curr_view_push(cell); // num_view++ (0x48-byte lazy slot, counters reset)
|
||||
PView::add_views(this, cell->num_stabs, cell->stab_list); // push a view slot on every visible stab cell
|
||||
Render::positionPush(3, identity Position stamped with cell DID);
|
||||
Render::copy_view(cell->top_view, null, 4); // root view = full viewport quad
|
||||
PView::ConstructView(this, cell, 0xffff); // the flood (below)
|
||||
PView::DrawCells(this, 0); // dead arg
|
||||
Render::positionPop(); remove_views(...); cell->num_view -= 1;
|
||||
```
|
||||
|
||||
`ConstructView(CEnvCell)` @0x005a57b0 — the flood: zero
|
||||
`outside_view.view_count`, bump `master_timestamp`, reset todo/draw
|
||||
lists; seed `InitCell(cell, 0xffff)` + `InsCellTodoList(cell, 0)`;
|
||||
then pop-from-END: append cell to `cell_draw_list` (grow +30), mark top
|
||||
view `cell_view_done`, `ClipPortals` → `AddViewToPortals` (clips each
|
||||
portal against the cell's current view and enqueues neighbors with their
|
||||
clipped views; portals leading outdoors raise `outside_view`).
|
||||
|
||||
`PView::DrawCells` @0x005a4840 — the draw, all passes far-to-near
|
||||
(`cell_draw_list` walked from the end):
|
||||
|
||||
```
|
||||
if (outside_view.view_count > 0) { // 'ov' in the traces (field, not the dead arg)
|
||||
Render::PortalList = &outside_view;
|
||||
LScape::draw(lscape); // landscape THROUGH the exit views (§3)
|
||||
FlushAlphaList(0f); m_nFrameStamp += 1;
|
||||
if (forceClear || portalsDrawnCount != 0) // count reset only on the !forceClear arm
|
||||
Clear(flag 4 /*z-buffer*/, black, z=1.0); // deferred clear triggered by earlier SEALS
|
||||
for cells far-to-near, per view of the cell's top portal_view:
|
||||
for each portal with other_cell_id == 0xFFFFFFFF: // exits to the landscape
|
||||
DrawPortalPolyInternal(portal.poly, 0); // SEAL @ own depth (verified @0059a49b7)
|
||||
}
|
||||
useSunlightSet(0); restore_all_lighting();
|
||||
for cells far-to-near, per view: setup_view; DrawEnvCell(cell); // BSP geometry
|
||||
for cells far-to-near: PortalList = cell top view; DrawObjCellForDummies(cell); // objects
|
||||
```
|
||||
|
||||
Degenerate guard NOT to port: `num_view==0` would loop 65535 views —
|
||||
unreachable (every listed cell was `curr_view_push`ed).
|
||||
|
||||
## 6. View machinery
|
||||
|
||||
- `Render::set_view` @0x0054d0e0 — pure CPU global install: the active
|
||||
view's edge count, in-mask (`npnts+1` bits: edges + the CY plane),
|
||||
vertex/plane base, screen bounds. No GPU state.
|
||||
- `Render::copy_view` @0x0054dfc0 — appends ONE view: perspective-divided
|
||||
screen points (drop near-duplicates/collinear within ~1 px; <3
|
||||
survivors ⇒ reject; cap 31), bounds, and per-edge WORLD-space planes
|
||||
(`N = normalize(cross(ray[k+1], ray[k]))` — NEXT × CURRENT, corrected
|
||||
2026-08-30 by the flood-read appendix against Ghidra; an earlier
|
||||
revision of this line had the operands reversed —
|
||||
`d = −dot(N, viewpoint)`). `copy_view(dest, null, n)` = the
|
||||
full-viewport root quad (count ignored). Full extraction:
|
||||
`2026-08-30-fw-flood-pseudocode-appendix.md`.
|
||||
- `PView::GetClip` @0x005a4320 — projects the portal polygon
|
||||
(`xformStart`), reverses winding for NEGATIVE side, and (do_clip)
|
||||
clips via `ACRender::polyClipFinish` against the installed view. ≤32
|
||||
verts assumed. The only CPU polygon clip in the frame.
|
||||
- `Render::viewconeCheck` @0x0054c250 — sphere vs the CY plane + the
|
||||
active view's edge planes, in viewer-block space. Cull strict
|
||||
(`d < −r`); partial inclusive (`d ≤ r`); publishes
|
||||
`local_object_center/radius` ALWAYS (even when OUTSIDE). BN's version
|
||||
of this body is unusable (inverted flags) — the model here is
|
||||
Ghidra-verified.
|
||||
|
||||
## 7. Constants and anchors
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Outdoor cell test | `(objcell_id & 0xFFFF) < 0x100` |
|
||||
| Plane/side epsilon `F_EPSILON` | 0.000199999995f (0x3951B717) |
|
||||
| Punch far-Z | 0.999998987f |
|
||||
| Panel depth mode | test ALWAYS, write ON (mode bit2), alpha 0 |
|
||||
| Panel local-XY reject box | ±12.0 |
|
||||
| Landscape grid | mid_width 11, mid_radius 5, block 192 m, cell 24 m |
|
||||
| Alpha pressure valve | global `flush` = 0.75 (~2250 meshes) |
|
||||
| copy_view vertex cap | 31 (0x1F) |
|
||||
| Flood list growth | +30 (0x1E) |
|
||||
| BoundingType | 0 OUTSIDE, 1 PARTIALLY_INSIDE, 2 ENTIRELY_INSIDE |
|
||||
| Sidedness | 0 POSITIVE, 1 NEGATIVE, 2 IN_PLANE (always rejects) |
|
||||
| Exit-portal sentinel | `other_cell_id == 0xFFFFFFFF` (cell), `0xFFFF` (no-through seed) |
|
||||
|
||||
Struct anchors (acclient.h): `PView` {outside_view@0 (view_count@+0x38),
|
||||
draw_landscape, outdoor_portal_list, cell_draw_list@+0x50,
|
||||
cell_draw_num@+0x60, cell_todo_list, cell_todo_num, lscape@+0x78};
|
||||
`CEnvCell.m_DID`@+0x28, `num_view`@+0x134, `portal_view`@+0x138;
|
||||
`CCellPortal` stride 0x18 {other_cell_id, other_cell_ptr, portal@+8,
|
||||
portal_side, other_portal_id, exact_match}; `CBldPortal` {portal_side,
|
||||
other_cell_id, other_portal_id, exact_match, num_stabs, stab_list};
|
||||
`CLandBlock.in_view`@+0xFC, `CLandCell` stride 0x108 (in_view@+0x104).
|
||||
|
||||
## 8. What the traces + code jointly prove about #456
|
||||
|
||||
Outdoor frames draw EVERYTHING distance-eligible — the far building
|
||||
first (far-to-near) — and correctness is pure depth: nearer terrain,
|
||||
shells, punches, and look-in cell geometry bury the vista. Interior
|
||||
frames never roster the far building (exit-view cone culls it) and SEAL
|
||||
their exit apertures at own depth after drawing the landscape through
|
||||
them, so nothing drawn later can leak into the aperture. There is no
|
||||
hiding mechanism to invent; FW3/FW4 reproducing this walk resolves #456
|
||||
wholesale.
|
||||
|
||||
## 9. Verification status
|
||||
|
||||
Byte-verified against the raw pseudo-C by hand: the seal loop
|
||||
(`other_cell_id==0xffffffff → DrawPortalPolyInternal(poly,0)` @0059a49af),
|
||||
the deferred z-clear (@005a48a9), `DEPTHTEST_ALWAYS` + z-write-from-mode
|
||||
(@0059be02), punch Z 0.999998987f (@0059bf4b), the fan submit (@0059bf84).
|
||||
Ghidra-arbitrated where BN mush was flagged: the rooting condition, the
|
||||
`viewconeCheck` comparisons, the ConstructView sidedness decode, the
|
||||
DrawCells dead argument. **Re-verify at FW1 before relying on:** the ±12
|
||||
reject polarity in `DrawPortalPolyInternal` (FP-status idiom), the
|
||||
`DrawBlock` alpha-flush comparison polarity, and `InsCellTodoList`'s
|
||||
insert position (LIFO pop order depends on it — port it from its body
|
||||
@0x005a4f50, not from assumption). Resolved non-issue: the trace's `ov`
|
||||
is the FIELD `outside_view.view_count` (probe read PView+0x38), not
|
||||
`DrawCells`' dead argument — reports 2 and 5 agree once combined.
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
# Campaign FW — the retail frame walk (rendering redesign design spec)
|
||||
|
||||
**Status:** APPROVED by owner 2026-08-30. Implementation plan:
|
||||
[`docs/plans/2026-08-30-campaign-fw-frame-walk.md`](../../plans/2026-08-30-campaign-fw-frame-walk.md).
|
||||
**Naming note:** approved as "Campaign W"; renamed to **FW** because the
|
||||
roadmap already owns "Phase W" (the historical Unified Cell Graph program).
|
||||
Stage ids are FW0–FW6.
|
||||
**Owner directive:** "I want this as solid as retail, no weird seams between
|
||||
walls or floors, it should work as retail. As stable as retail." Scope
|
||||
confirmed 2026-08-30: retail owns every frame decision, modern code owns
|
||||
only GPU mechanics, and the camera behavior is ported too.
|
||||
**Predecessor:** Campaign PV (halted 2026-08-30 by owner verdict —
|
||||
`docs/plans/2026-08-30-pview-visibility-campaign.md`). PV tried to import
|
||||
retail's visibility decisions piecemeal into our gather/regroup renderer
|
||||
and failed. This campaign replaces the renderer's decision layer outright.
|
||||
|
||||
---
|
||||
|
||||
## 1. Why (the disease, named once)
|
||||
|
||||
Retail has no separate "visibility system." The world draws itself by
|
||||
walking itself: the frame starts at the viewer's cell, recurses through
|
||||
portals (or walks landcells outward when outdoors), and each cell draws its
|
||||
own polygons at its turn in the walk. Every polygon is drawn exactly once,
|
||||
by exactly one owner, in an order that resolves every depth tie, from
|
||||
geometry the DATs author to be watertight per cell. Correctness is a
|
||||
property of the structure, not of any check.
|
||||
|
||||
acdream inverted that: gather everything visible into lists, regroup by
|
||||
material for GPU efficiency, draw in batch order. Regrouping discards
|
||||
retail's ordering and ownership guarantees, and we have spent months buying
|
||||
them back one patch at a time. The complete patch inventory — the doorway
|
||||
flap, the seam strips, the +0.02 z-fight lift, the exit-portal stencils,
|
||||
the look-in seed heuristics, the seal-panel depth stamps, the #456
|
||||
cathedral bleed, and the 2026-08-29/30 transition breakage — is one disease
|
||||
with one cure: make the walk own the frame.
|
||||
|
||||
The precedent is physics. "No patching collision" led to a faithful retail
|
||||
port, and physics has been stable since. The renderer is the last subsystem
|
||||
violating the project thesis (modern code, retail behavior).
|
||||
|
||||
## 2. Goal and non-goals
|
||||
|
||||
**Goal.** Port retail's frame composition verbatim so that every defect
|
||||
class above becomes structurally impossible: no seams between walls,
|
||||
floors, or terrain; no visibility flap; no vista bleed at any position
|
||||
retail can reach; no stale partial-world frames; retail camera behavior.
|
||||
|
||||
**Non-goals.**
|
||||
- NOT porting retail's per-polygon D3D submission. Our pre-built meshes
|
||||
contain the same triangles (conformance-tested); porting 2002 D3D buys
|
||||
zero correctness.
|
||||
- NOT touching DAT decoding, mesh extraction, terrain math, texture
|
||||
decode, or GPU residency (the WB-derived asset layer, ~19K lines,
|
||||
healthy).
|
||||
- NOT redesigning streaming. The only streaming change is one new
|
||||
invariant (§4, atomic cell presence).
|
||||
- NOT changing lighting/shading models already user-gated (VisualMaster).
|
||||
|
||||
## 3. The ownership line
|
||||
|
||||
**Retail owns every decision** — what draws, from where, in what order,
|
||||
clipped by what. Ported verbatim from `docs/research/named-retail/` with
|
||||
named-symbol citations in code comments:
|
||||
|
||||
| Decision | Retail mechanism (port source) |
|
||||
|---|---|
|
||||
| Frame rooting | `SmartBox::RenderNormalMode` @0x00453aa0: viewer in an EnvCell → `CEnvCell` DrawInside recursion; truly outdoors → `set_default_view` + `LScape` draw |
|
||||
| Per-portal views | `ConstructView` / `set_view` / `PView::GetClip`; the portal CHAIN's polygon is the only CPU-clipped geometry (`ACRender::polyClipFinish`); one view per chain, never merged (the PV1 finding stands) |
|
||||
| Object gating | `Render::viewconeCheck` — coarse sphere-vs-cone; PARTIALLY_INSIDE draws the whole object; pixel exactness comes from recursion + z-buffer |
|
||||
| Landscape walk | `LScape`/`CLandBlock`/`CLandCell` draw order and radius rules |
|
||||
| Buildings | `CBuildingObj` draw (pre-punch flush @0x0059F2A0) + `BuildInfo.Portals` look-in views (`ConstructView(CBldPortal)`) |
|
||||
| Stage interleave | The full retail frame sequence (landscape early/late, alpha farther/near, exit mask, shells, dynamics, particles) — owned BY the walk, not approximated from outside as `RetailPViewPassExecutor` does today |
|
||||
| Depth semantics | Retail's depth-test function and write configuration verbatim, so submission-order tie resolution matches retail exactly (this is what retires the +0.02 lift) |
|
||||
| LOD/degrade | The marker-class degrade admission (already ported at `RetailDegradePolicy`; relocates to the walk's object-gating step) |
|
||||
| Particles/dynamics | Emitters draw at their owner cell's walk turn, unclipped (`ShouldDrawParticles` @0x0050FE60) |
|
||||
| Camera | Retail camera behavior in full: chase, 0.3 m collision probe, slope alignment (already default-on), PLUS the retail zoom envelope. The 40 m development zoom is retired and its register row (AD-116) deleted. If a development free-camera is still wanted later it is a separate explicit dev tool, not a modification of the retail camera; out of scope here. |
|
||||
|
||||
**Modern code owns only mechanics**, and each substitution carries a proof
|
||||
of pixel-neutrality:
|
||||
|
||||
| Modern mechanic | Neutrality proof |
|
||||
|---|---|
|
||||
| Pre-built vertex/index buffers (WB-derived) | Same triangles — existing conformance suites |
|
||||
| Bindless textures, Vulkan RHI | Resource binding strategy; changes no order, no coverage |
|
||||
| Merging **adjacent** same-state draws into one MDI call | Order-preserving by construction; a merge across a state or stage boundary is forbidden |
|
||||
| Async landblock streaming | The atomic-presence invariant (§4) makes async loading indistinguishable from retail's synchronous loading, minus the hitch |
|
||||
|
||||
**The rule:** if a modern component ever makes a decision retail did not
|
||||
make, that is a campaign bug regardless of how the pixels look.
|
||||
|
||||
## 4. Architecture
|
||||
|
||||
### Components (each one purpose, testable alone)
|
||||
|
||||
- **`RetailFrameWalk`** (new, App/Rendering) — the ported recursion +
|
||||
landscape walk. Pure CPU: input = world snapshot + camera pose; output =
|
||||
the ordered draw stream and the frame's visible-cell set. No GPU types.
|
||||
Unit-testable headless against synthetic worlds and against captured
|
||||
retail traces (§5).
|
||||
- **`PViewSet`** (new) — retail `PView` construction, chain-polygon
|
||||
clipping, cone tests. Owned by the walk; no consumer sees clip planes
|
||||
except through it.
|
||||
- **`OrderedDrawStream`** (new) — the append-only frame command list:
|
||||
(mesh ref, transform, material state, stage) tuples in walk order.
|
||||
- **`OrderPreservingSubmitter`** (new, replaces the regrouping half of
|
||||
`WbDrawDispatcher`) — walks the stream, merges adjacent same-state
|
||||
commands into MDI calls, submits through the existing RHI. May never
|
||||
reorder. The bindless/SSBO machinery beneath is reused as-is.
|
||||
- **`WalkWorldSnapshot`** (new seam over existing streaming) — the atomic
|
||||
view: a cell/landblock is either present-and-complete or absent. Leans
|
||||
on the existing typed completion admission (Modern Runtime Slice E); the
|
||||
walk consumes only committed-complete cells. **Invariant: no frame ever
|
||||
walks a partially published cell.** This is the one place modern differs
|
||||
from retail by design (async vs synchronous load) and it gets a
|
||||
divergence-register row.
|
||||
- **`RetailCameraController`** (extend existing camera) — re-land the
|
||||
retail zoom envelope on top of the already-default-on chase/collide/
|
||||
slope-align behaviors; delete the dev-zoom deviation.
|
||||
|
||||
### What gets deleted (the patch apparatus)
|
||||
|
||||
`PortalVisibilityBuilder` as production visibility, the look-in seed
|
||||
heuristics, exit-portal stencils, `ClipPlaneSet`'s gating role beyond chain
|
||||
polygons, the +0.02 lift, the seal-panel depth stamps (quarantined,
|
||||
never merged), the `InViewCells` side-channel (consumers — radar, lights,
|
||||
particle culling — read the walk's visible-cell output instead: one gate,
|
||||
computed once, enforced once). `RetailPViewRenderer`/`RetailPViewPassExecutor`
|
||||
are absorbed: their already-correct stage knowledge moves into the walk;
|
||||
the shells are retired at cutover.
|
||||
|
||||
### Data flow (one frame)
|
||||
|
||||
1. Streaming publishes committed-complete cells → `WalkWorldSnapshot`.
|
||||
2. `RetailCameraController` produces the eye pose (retail rules).
|
||||
3. `RetailFrameWalk` roots at the eye's cell (or outdoors), recurses/walks,
|
||||
consults `PViewSet` per portal chain, gates objects per view, and
|
||||
appends draws to `OrderedDrawStream` in retail stage order.
|
||||
4. `OrderPreservingSubmitter` merges adjacent runs and submits via RHI.
|
||||
5. The walk's visible-cell set feeds radar/lights/particles/audio.
|
||||
|
||||
## 5. Conformance and gates
|
||||
|
||||
- **The walk oracle (new tooling, built FIRST).** cdb attaches to the
|
||||
paired 2013 retail client and captures the per-frame cell-draw sequence
|
||||
(cell id order + stage boundaries) at canonical positions: the Sanctuary
|
||||
terrace and its edge, the cathedral portal-in, a Holtburg doorway
|
||||
(flap scene), a Holtburg street + house exit, one deep dungeon corridor.
|
||||
Our `RetailFrameWalk`, fed the same world state and pose, must produce
|
||||
the identical sequence. This gates the port BEFORE any pixel is judged.
|
||||
(Toolchain and safety rules: CLAUDE.md "Retail debugger toolchain" + the
|
||||
PV campaign's cdb lessons — no inline `-c` attach, every `j` branch ends
|
||||
in `gc`.)
|
||||
- **Pixel/behavior gates (owner-judged):** cathedral bleed gone at the
|
||||
edge AND at normal camera (#456 acceptance); doorway flap gone; no
|
||||
wall/floor/terrain seams in dungeons; clean portal-in, house exit, and
|
||||
teleport timing. **The transition checklist (portal-in, house exit,
|
||||
teleport stopwatch) is a standing self-run gate on every candidate build
|
||||
before it reaches the owner** — the binding lesson of 2026-08-29/30.
|
||||
- **Perf checkpoint EARLY (stage W3, before full commitment):** dense
|
||||
Arwic uncapped with walk-order submission, measured in Release. Target:
|
||||
within 20% of the current production profile. A larger regression stops
|
||||
the campaign for an explicit owner decision — we measure, not hope.
|
||||
- **Suites:** walk unit tests on synthetic worlds; captured-trace replay
|
||||
tests checked into `docs/research/` fixtures; the standard hermetic
|
||||
suites stay green at every stage; every ported function cites its named
|
||||
retail symbol.
|
||||
|
||||
## 6. Staged cutover (no long-lived dual path)
|
||||
|
||||
Campaign V precedent: port behind the contract, then delete. Each stage
|
||||
lands build+test green; visual stages get the standing transition
|
||||
checklist self-run plus owner gates.
|
||||
|
||||
- **W0** — Oracle tooling: the cdb walk-capture harness + recorded traces
|
||||
at the canonical positions. Deliverable: replayable trace fixtures.
|
||||
- **W1** — `RetailFrameWalk` + `PViewSet` as pure CPU modules, conformant
|
||||
to the traces. No production rendering change yet.
|
||||
- **W2** — `OrderedDrawStream` + `OrderPreservingSubmitter` against the
|
||||
existing RHI; walk drives a diagnostic scene.
|
||||
- **W3** — Static world cutover (terrain, EnvCells, buildings) to the
|
||||
walk; the old gather path for statics is deleted in the same stage.
|
||||
**The perf checkpoint lives here.**
|
||||
- **W4** — Objects, entities, particles, dynamics move into walk turns;
|
||||
delete the look-in machinery, stencils, stamps, lift, and the
|
||||
`InViewCells` side-channel.
|
||||
- **W5** — Camera port completion (retail zoom envelope; AD-116 retired).
|
||||
- **W6** — Closeout: divergence-register reconciliation, docs, the full
|
||||
gate matrix, roadmap/milestones update.
|
||||
|
||||
## 7. Risks
|
||||
|
||||
- **Perf** (walk-order submission loses cross-cell batching). Mitigation:
|
||||
adjacency merging + bindless; the W3 checkpoint decides with numbers.
|
||||
- **Streaming scope creep.** Guard: only the atomic-presence contract may
|
||||
touch streaming; anything more is out of scope.
|
||||
- **Oracle fidelity.** Retail traces need live retail sessions (owner
|
||||
assists per the established cdb workflow); traces are captured once and
|
||||
checked in as fixtures.
|
||||
- **The quarantined PV branch.** Nothing merges from it. Salvageable
|
||||
pieces (the degrade port, PV1's merged-hull deletion) are RE-LANDED
|
||||
fresh on this campaign's stages if and when their stage needs them,
|
||||
each with the transition checklist. The unidentified back-half culprit
|
||||
stays quarantined until its machinery is deleted wholesale at W4.
|
||||
- **Camera/dev workflow.** Retiring the 40 m zoom changes the owner's dev
|
||||
ergonomics; the optional separate flycam tool is deliberately deferred
|
||||
to keep this campaign's scope honest.
|
||||
|
||||
## 8. Register impact
|
||||
|
||||
Added: the streaming atomic-presence adaptation (W3); order-preserving
|
||||
MDI merging as intentional architecture. Retired at their stages: the
|
||||
+0.02 lift row, AD-116 (camera), AD-117 (stamps — dies with the
|
||||
quarantine), and every look-in/stencil-era row the deletions obsolete.
|
||||
Rule 1 of the register applies to every stage commit.
|
||||
|
|
@ -512,13 +512,7 @@ internal sealed class FrameRootCompositionPhase
|
|||
new WorldScenePViewRenderer(
|
||||
new RetailPViewRenderer(
|
||||
currentRenderSceneOracle,
|
||||
renderFrameProduct,
|
||||
// Campaign FW3.2b-2: the walk's production world
|
||||
// data, published/retired with each landblock by the
|
||||
// render publisher (FW3.1).
|
||||
live.LandblockPipeline.RenderPublisher?.WalkBuildings,
|
||||
live.LandblockPipeline.RenderPublisher?.WalkLandscape,
|
||||
d.CellVisibility),
|
||||
renderFrameProduct),
|
||||
retailPViewPassExecutor,
|
||||
retailPViewPassExecutor),
|
||||
retailPViewCells,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
|
|
@ -115,21 +114,6 @@ public sealed class LoadedCell
|
|||
/// test fixtures use low ids for interior cells, so keying on the id would misfire.
|
||||
/// </summary>
|
||||
public bool IsOutdoorNode;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.1: the retail frame walk's model of this cell (portal
|
||||
/// side/exact-match decode, portal polygons + planes, stab list,
|
||||
/// UNLIFTED transform), built atomically alongside this
|
||||
/// <see cref="LoadedCell"/> by
|
||||
/// <c>EnvCellLandblockBuildBuilder.BuildVisibilityCell</c> — see
|
||||
/// <c>WalkCellFactory.FromParsed</c>. Null only for hand-built test
|
||||
/// fixtures that construct a <see cref="LoadedCell"/> directly instead of
|
||||
/// through the streaming build. <c>WalkProductionFrameContext.GetVisible</c>
|
||||
/// is the walk's sole read of this field — the committed registry
|
||||
/// (<see cref="CellVisibility.TryGetCell"/>) is the walk's only cell
|
||||
/// source; its dead BFS role is unrelated.
|
||||
/// </summary>
|
||||
public WalkCell? Walk { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -144,18 +144,6 @@ public sealed class ClipFrameAssembly
|
|||
|
||||
internal void SetOutsideViewSlices(ClipViewSlice[] slices) => OutsideViewSlices = slices;
|
||||
|
||||
/// <summary>FW4 slice 1: returns the outside-view slice array to the
|
||||
/// pool ahead of a same-frame reassembly from the walk's own views
|
||||
/// (<see cref="ClipFrameAssembler.ReassembleOutsideViewFromWalk"/>) —
|
||||
/// without this the replaced array would leak from the slice pool for
|
||||
/// the frame.</summary>
|
||||
internal void ReturnOutsideViewSlicesForReassembly()
|
||||
{
|
||||
if (OutsideViewSlices.Length != 0)
|
||||
ReturnSlices(OutsideViewSlices);
|
||||
OutsideViewSlices = System.Array.Empty<ClipViewSlice>();
|
||||
}
|
||||
|
||||
private ClipViewSlice[] RentSlices(int length)
|
||||
{
|
||||
if (_sliceArraysByLength.TryGetValue(length, out Stack<ClipViewSlice[]>? pool)
|
||||
|
|
@ -392,143 +380,6 @@ public static class ClipFrameAssembler
|
|||
return assembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW4 slice 1 — the interior root's outside-view cutover.
|
||||
/// Replaces the assembly's outside-view block (slices, terrain mode,
|
||||
/// scissor/NDC bounds, plane count) with slices derived from THE WALK'S
|
||||
/// OWN <c>outside_view</c> (<see cref="Walk.RetailFrameWalk.InteriorOutsideView"/>),
|
||||
/// filled by the walk's <c>ConstructView</c> during Collect. Retail has
|
||||
/// exactly ONE visibility structure per frame: <c>LScape::draw</c>'s
|
||||
/// terrain clip, the punch fans' <c>building_view</c> planes, and the
|
||||
/// landscape turn's view count all read the views the walk itself
|
||||
/// installed. Feeding these from the old <c>PortalVisibilityBuilder</c>
|
||||
/// assembly let the two systems desynchronize on camera-transition
|
||||
/// boundary frames — terrain splashed through stale/fat old-apparatus
|
||||
/// exit views over interior pixels the walk's flood never repainted
|
||||
/// (the FW3 visual-gate stairwell/grass flash, probe-pinned
|
||||
/// 2026-08-30), and punch fans indexed the old slice array with walk
|
||||
/// view indices.
|
||||
///
|
||||
/// The walk stores view vertices as PIXEL screen points
|
||||
/// (<c>copy_view</c>'s post-divide viewport coordinates, origin
|
||||
/// top-left, +Y down — <see cref="Walk.WalkScreenClip.TransformToScreen"/>:
|
||||
/// x=(W/2)(x_c+w), y=(H/2)(w−y_c)); inverting that mapping yields the
|
||||
/// standard NDC this assembler's <see cref="ViewPolygon"/>s already use
|
||||
/// (ndcX = 2x/W − 1, ndcY = 1 − 2y/H). Winding is normalized inside
|
||||
/// <see cref="ClipPlaneSet.From(in ViewPolygon)"/>, and the closing
|
||||
/// duplicate vertex <c>copy_view</c> stores is merged there too.
|
||||
///
|
||||
/// Must run AFTER the walk's Collect and BEFORE
|
||||
/// <c>PrepareClipFrame</c> publishes the clip regions — appended slots
|
||||
/// join the same single publication.
|
||||
/// </summary>
|
||||
public static void ReassembleOutsideViewFromWalk(
|
||||
ClipFrameAssembly assembly,
|
||||
Walk.WalkPortalView outsideView,
|
||||
float viewportWidth,
|
||||
float viewportHeight)
|
||||
{
|
||||
System.ArgumentNullException.ThrowIfNull(assembly);
|
||||
System.ArgumentNullException.ThrowIfNull(outsideView);
|
||||
if (viewportWidth <= 0f || viewportHeight <= 0f)
|
||||
{
|
||||
throw new System.ArgumentOutOfRangeException(
|
||||
nameof(viewportWidth),
|
||||
$"viewport {viewportWidth}x{viewportHeight} — the walk projected its "
|
||||
+ "views through a real viewport; a non-positive extent here means the "
|
||||
+ "caller handed a different frame's context (fail-loud rule).");
|
||||
}
|
||||
|
||||
ClipFrame frame = assembly.Frame;
|
||||
int viewCount = outsideView.ViewCount;
|
||||
var polys = outsideView.View.Polys;
|
||||
var pool = outsideView.View.Vertices;
|
||||
if (polys.Count < viewCount)
|
||||
{
|
||||
throw new System.InvalidOperationException(
|
||||
$"walk outside_view holds {polys.Count} polys for ViewCount={viewCount} — "
|
||||
+ "the view set's append bookkeeping desynchronized (fail-loud rule).");
|
||||
}
|
||||
|
||||
assembly.ReturnOutsideViewSlicesForReassembly();
|
||||
|
||||
List<ClipViewSlice> outsideSlicesList = assembly.SliceScratch;
|
||||
outsideSlicesList.Clear();
|
||||
int outsideMaxPlaneCount = 0;
|
||||
bool outsideHasScissorFallback = false;
|
||||
int scissorFallbacks = assembly.ScissorFallbacks;
|
||||
float unionMinX = float.MaxValue, unionMinY = float.MaxValue;
|
||||
float unionMaxX = float.MinValue, unionMaxY = float.MinValue;
|
||||
|
||||
for (int v = 0; v < viewCount; v++)
|
||||
{
|
||||
Walk.WalkViewPoly walkPoly = polys[v];
|
||||
var vertices = new Vector2[walkPoly.VertexCount];
|
||||
for (int k = 0; k < walkPoly.VertexCount; k++)
|
||||
{
|
||||
Vector2 px = pool[walkPoly.VertexIndex + k].Point;
|
||||
vertices[k] = new Vector2(
|
||||
px.X / viewportWidth * 2f - 1f,
|
||||
1f - px.Y / viewportHeight * 2f);
|
||||
}
|
||||
var poly = new ViewPolygon(vertices);
|
||||
if (!poly.IsEmpty)
|
||||
{
|
||||
if (poly.MinX < unionMinX) unionMinX = poly.MinX;
|
||||
if (poly.MinY < unionMinY) unionMinY = poly.MinY;
|
||||
if (poly.MaxX > unionMaxX) unionMaxX = poly.MaxX;
|
||||
if (poly.MaxY > unionMaxY) unionMaxY = poly.MaxY;
|
||||
}
|
||||
|
||||
var cps = ClipPlaneSet.From(poly);
|
||||
if (cps.IsNothingVisible)
|
||||
continue;
|
||||
|
||||
int slot;
|
||||
Vector4[] planes;
|
||||
if (cps.Count > 0)
|
||||
{
|
||||
planes = cps.PlaneArray;
|
||||
slot = frame.AppendSlot(planes);
|
||||
if (cps.Count > outsideMaxPlaneCount)
|
||||
outsideMaxPlaneCount = cps.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
planes = System.Array.Empty<Vector4>();
|
||||
slot = 0;
|
||||
outsideHasScissorFallback = true;
|
||||
scissorFallbacks++;
|
||||
}
|
||||
|
||||
outsideSlicesList.Add(new ClipViewSlice(slot, AabbOf(poly), planes));
|
||||
}
|
||||
|
||||
ClipViewSlice[] outsideViewSlices = assembly.CopySlices(outsideSlicesList);
|
||||
bool outdoorVisible = outsideViewSlices.Length > 0;
|
||||
int outdoorSlot = outdoorVisible ? outsideViewSlices[0].Slot : 0;
|
||||
TerrainClipMode terrainMode = !outdoorVisible
|
||||
? TerrainClipMode.Skip
|
||||
: (outsideHasScissorFallback ? TerrainClipMode.Scissor : TerrainClipMode.Planes);
|
||||
|
||||
Vector4 outsideViewNdcAabb = outdoorVisible
|
||||
? new Vector4(unionMinX, unionMinY, unionMaxX, unionMaxY)
|
||||
: Vector4.Zero;
|
||||
Vector4 terrainScissor = terrainMode == TerrainClipMode.Scissor
|
||||
? outsideViewNdcAabb
|
||||
: Vector4.Zero;
|
||||
|
||||
assembly.SetOutsideViewSlices(outsideViewSlices);
|
||||
assembly.OutdoorSlot = outdoorSlot;
|
||||
assembly.OutdoorVisible = outdoorVisible;
|
||||
assembly.TerrainMode = terrainMode;
|
||||
assembly.TerrainScissorNdcAabb = terrainScissor;
|
||||
assembly.HasOutsideView = outdoorVisible;
|
||||
assembly.OutsideViewNdcAabb = outsideViewNdcAabb;
|
||||
assembly.OutsidePlaneCount = terrainMode == TerrainClipMode.Planes ? outsideMaxPlaneCount : 0;
|
||||
assembly.ScissorFallbacks = scissorFallbacks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the cell views used by nested <c>DrawBuilding -> DrawPortal</c>
|
||||
/// PViews to the already assembled frame. Retail installs each nested
|
||||
|
|
|
|||
|
|
@ -1073,12 +1073,6 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The caster's own depth-only pipeline. Depth compare is
|
||||
/// <see cref="WorldDepthContract.WorldCompare"/> — see that type for the
|
||||
/// world-space <c>GL_LESS</c> citation this shares with every other world
|
||||
/// pipeline.
|
||||
/// </summary>
|
||||
private static IGpuPipeline CreatePipeline(
|
||||
IGpuDevice device,
|
||||
string name,
|
||||
|
|
@ -1093,7 +1087,7 @@ internal sealed class DirectionalSunShadowRenderer : IDirectionalShadowReceiverS
|
|||
VertexLayout = layout,
|
||||
Topology = GpuPrimitiveTopology.TriangleList,
|
||||
Blend = GpuBlendMode.None,
|
||||
Depth = new GpuDepthState(true, true, WorldDepthContract.WorldCompare),
|
||||
Depth = new GpuDepthState(true, true, GpuCompareOp.Less),
|
||||
Cull = GpuCullMode.Back,
|
||||
FrontFace = frontFace,
|
||||
AlphaToCoverage = false,
|
||||
|
|
|
|||
|
|
@ -232,14 +232,12 @@ public sealed unsafe partial class ParticleRenderer
|
|||
/// which is the GL arm's bracket verbatim
|
||||
/// (<c>Enable(DepthTest)</c>/<c>DepthMask(false)</c>/<c>Disable(CullFace)</c>).
|
||||
///
|
||||
/// <para>Depth compare is <see cref="AcDream.App.Rendering.WorldDepthContract.WorldCompare"/>
|
||||
/// (<c>Less</c>), not the contract's <c>LessOrEqual</c> default — see that
|
||||
/// type for the full citation. The world frame runs under <c>GL_LESS</c>
|
||||
/// and this renderer never called <c>glDepthFunc</c>, so it inherited it.
|
||||
/// Alpha-to-coverage is off for the same kind of reason and the opposite
|
||||
/// way round — the frame-global state controller disables it and only
|
||||
/// <c>WbDrawDispatcher</c>'s opaque bracket turns it on, so particles have
|
||||
/// never drawn with it.</para>
|
||||
/// <para>Depth compare is <c>Less</c>, not the contract's <c>LessOrEqual</c>
|
||||
/// default: the world frame runs under <c>GL_LESS</c> and this renderer never
|
||||
/// called <c>glDepthFunc</c>, so it inherited it. Alpha-to-coverage is off
|
||||
/// for the same kind of reason and the opposite way round — the frame-global
|
||||
/// state controller disables it and only <c>WbDrawDispatcher</c>'s opaque
|
||||
/// bracket turns it on, so particles have never drawn with it.</para>
|
||||
/// </summary>
|
||||
private static IGpuPipeline CreateBillboardPipeline(
|
||||
IGpuDevice device,
|
||||
|
|
@ -253,7 +251,7 @@ public sealed unsafe partial class ParticleRenderer
|
|||
VertexLayout = BillboardVertexLayout,
|
||||
Topology = GpuPrimitiveTopology.TriangleList,
|
||||
Blend = blend,
|
||||
Depth = new GpuDepthState(Test: true, Write: false, WorldDepthContract.WorldCompare),
|
||||
Depth = new GpuDepthState(Test: true, Write: false, GpuCompareOp.Less),
|
||||
Cull = GpuCullMode.None,
|
||||
FrontFace = GpuFrontFace.CounterClockwise,
|
||||
AlphaToCoverage = false,
|
||||
|
|
@ -262,12 +260,10 @@ public sealed unsafe partial class ParticleRenderer
|
|||
});
|
||||
|
||||
/// <summary>
|
||||
/// One mesh-particle pipeline. Same depth bracket as the billboards (see
|
||||
/// <see cref="AcDream.App.Rendering.WorldDepthContract"/> for the world
|
||||
/// <c>GL_LESS</c> citation); the winding is CW because
|
||||
/// <c>PrepareMeshPipeline</c> sets <c>glFrontFace(GL_CW)</c>, and the cull
|
||||
/// mode stays DYNAMIC because it is resolved per sub-batch from the DAT's
|
||||
/// own <c>CullMode</c>.
|
||||
/// One mesh-particle pipeline. Same depth bracket as the billboards; the
|
||||
/// winding is CW because <c>PrepareMeshPipeline</c> sets
|
||||
/// <c>glFrontFace(GL_CW)</c>, and the cull mode stays DYNAMIC because it is
|
||||
/// resolved per sub-batch from the DAT's own <c>CullMode</c>.
|
||||
/// </summary>
|
||||
private static IGpuPipeline CreateMeshParticlePipeline(
|
||||
IGpuDevice device,
|
||||
|
|
@ -281,7 +277,7 @@ public sealed unsafe partial class ParticleRenderer
|
|||
VertexLayout = MeshVertexLayout,
|
||||
Topology = GpuPrimitiveTopology.TriangleList,
|
||||
Blend = blend,
|
||||
Depth = new GpuDepthState(Test: true, Write: false, WorldDepthContract.WorldCompare),
|
||||
Depth = new GpuDepthState(Test: true, Write: false, GpuCompareOp.Less),
|
||||
Cull = GpuCullMode.None,
|
||||
FrontFace = GpuFrontFace.Clockwise,
|
||||
AlphaToCoverage = false,
|
||||
|
|
|
|||
|
|
@ -266,15 +266,7 @@ public sealed unsafe partial class ParticleRenderer : IDisposable
|
|||
if (_submissionScratch.Count == 0)
|
||||
return;
|
||||
|
||||
bool defers = renderPass == ParticleRenderPass.Scene && _alphaQueue?.IsCollecting == true;
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[walk-part] finish pass={renderPass} n={_submissionScratch.Count} "
|
||||
+ $"branch={(defers ? "defer" : "immediate")} "
|
||||
+ $"phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}");
|
||||
}
|
||||
if (defers)
|
||||
if (renderPass == ParticleRenderPass.Scene && _alphaQueue?.IsCollecting == true)
|
||||
DeferToRetailAlphaQueue(camera);
|
||||
else
|
||||
DrawOrdered(camera);
|
||||
|
|
|
|||
|
|
@ -342,26 +342,31 @@ public static class PortalVisibilityBuilder
|
|||
Console.WriteLine($"[pv-ERROR] chain tail(24):{tail}");
|
||||
}
|
||||
|
||||
// Campaign FW3.3 (2026-08-30): ShellDrawLiftZ (the 0.02 m drawn-shell
|
||||
// lift, register row AP-32) is RETIRED. Retail draws cell geometry at
|
||||
// the dat EnvCell origin verbatim; the coplanar tie-breaks the lift
|
||||
// stood in for are now owned by the walk's retail draw ORDER under
|
||||
// WorldDepthContract's Less (first-drawn-wins — RenderDeviceD3D::
|
||||
// DrawBlock terrain-then-objects per cell, PView::DrawCells shells-then-
|
||||
// contents). With the lift gone, the visibility gate and the drawn
|
||||
// geometry share ONE space, so the #130 strip mechanism (gate space ≠
|
||||
// drawn space) no longer exists.
|
||||
/// <summary>The +Z world lift applied to DRAWN cell shells (z-fighting vs
|
||||
/// terrain; applied in GameWindow's cell registration). The visibility
|
||||
/// graph stays in PHYSICS (unlifted) space — feeding the lift into portal
|
||||
/// planes broke horizontal-portal side tests (#119-residual, f35cb8b).
|
||||
/// Draw-space consumers of portal polygons (the OutsideView color gate
|
||||
/// here, the seal/punch depth fans in GameWindow) must apply this lift so
|
||||
/// they meet the drawn shell's aperture edge — the unlifted gate left a
|
||||
/// 2 cm background strip under the drawn lintel (#130).</summary>
|
||||
public const float ShellDrawLiftZ = 0.02f;
|
||||
|
||||
/// <param name="lookup">Resolve a full cell id to its LoadedCell, or null if not loaded.</param>
|
||||
/// <param name="buildingMembership">Optional: true if a cell id is in the camera building's cell
|
||||
/// set. When provided, a neighbour OUTSIDE the set routes to CrossBuildingViews instead of
|
||||
/// continuing the in-building BFS. Pass null to treat all reachable cells as in-building.</param>
|
||||
/// <param name="drawLiftZ">World +Z applied ONLY to the exit-portal projection feeding
|
||||
/// <see cref="PortalVisibilityFrame.OutsideView"/> (a draw-space region; see
|
||||
/// <see cref="ShellDrawLiftZ"/>). Flood admission, side tests, and CellViews are unaffected.
|
||||
/// Production passes <see cref="ShellDrawLiftZ"/>; tests replaying visibility semantics pass 0.</param>
|
||||
public static PortalVisibilityFrame Build(
|
||||
LoadedCell cameraCell,
|
||||
Vector3 cameraPos,
|
||||
Func<uint, LoadedCell?> lookup,
|
||||
Matrix4x4 viewProj,
|
||||
Func<uint, bool>? buildingMembership = null,
|
||||
float drawLiftZ = 0f,
|
||||
PortalVisibilityFrame? reuseFrame = null)
|
||||
{
|
||||
var frame = reuseFrame ?? new PortalVisibilityFrame();
|
||||
|
|
@ -539,13 +544,38 @@ public static class PortalVisibilityBuilder
|
|||
|
||||
if (portal.OtherCellId == 0xFFFF)
|
||||
{
|
||||
// Exit portal -> outdoors visible through this (clipped)
|
||||
// opening. FW3.3: shells draw at the dat origin (the
|
||||
// ShellDrawLiftZ retirement), so the gate and the drawn
|
||||
// aperture share one space — the unlifted clippedRegion
|
||||
// IS the draw-space region.
|
||||
AddRegion(frame.OutsideView, clippedRegion);
|
||||
trace?.Add($"portal cell=0x{cell.CellId:X8} p{i}->EXIT addOutside={clippedRegion.Count} clipVerts={clipVerts}");
|
||||
// Exit portal -> outdoors visible through this (clipped) opening.
|
||||
// OutsideView gates DRAWN color (terrain/sky/scissor), and the
|
||||
// shell that rasterizes this aperture draws +drawLiftZ above
|
||||
// the physics transform — project the region in the SAME
|
||||
// lifted space or terrain stops a lift-height short of the
|
||||
// drawn lintel (#130 strip). Flood semantics keep the
|
||||
// unlifted clippedRegion path above.
|
||||
int outsideCount;
|
||||
if (drawLiftZ == 0f)
|
||||
{
|
||||
AddRegion(frame.OutsideView, clippedRegion);
|
||||
outsideCount = clippedRegion.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
using PortalVisibilityFrame.ClipRegionScratchLease outsideRegionLease =
|
||||
frame.RentClipRegionScratch();
|
||||
List<ViewPolygon> outsideRegion = outsideRegionLease.Region;
|
||||
ClipPortalAgainstView(
|
||||
frame,
|
||||
poly,
|
||||
cell.WorldTransform * Matrix4x4.CreateTranslation(0f, 0f, drawLiftZ),
|
||||
viewProj,
|
||||
currentView.Polygons,
|
||||
processedCount,
|
||||
endCount - processedCount,
|
||||
outsideRegion,
|
||||
out _);
|
||||
AddRegion(frame.OutsideView, outsideRegion);
|
||||
outsideCount = outsideRegion.Count;
|
||||
}
|
||||
trace?.Add($"portal cell=0x{cell.CellId:X8} p{i}->EXIT addOutside={outsideCount} clipVerts={clipVerts}");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,12 +146,6 @@ internal sealed class RetailAlphaQueue : IWorldSceneAlphaFrame
|
|||
{
|
||||
if (!IsCollecting)
|
||||
throw new InvalidOperationException("Retail alpha flush requires an active frame.");
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled && _submissions.Count > 0)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[walk-alpha] flush n={_submissions.Count} "
|
||||
+ $"phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}");
|
||||
}
|
||||
|
||||
Exception? drawFailure = null;
|
||||
List<Exception>? resetFailures = null;
|
||||
|
|
|
|||
|
|
@ -252,23 +252,12 @@ public sealed class RetailChaseCamera : ICamera
|
|||
var swept = CollisionProbe.SweepEye(pivotWorld, _soughtEye, cellId, selfEntityId, playerPosition);
|
||||
publishedEye = swept.Eye;
|
||||
ViewerCellId = swept.ViewerCellId;
|
||||
// Total-failure fallback = retail set_viewer(&player->m_position,
|
||||
// reset_sought=1) (update_viewer @0x00453fad and the cell==0 bail
|
||||
// @0x00453d1d — both surface here as ViewerCellId == 0): the
|
||||
// sought resets to the returned position and re-extends, AND the
|
||||
// viewer INHERITS THE PLAYER'S POSITION CELL — set_viewer copies
|
||||
// the whole Position, objcell_id included, so retail ROOTS ON THE
|
||||
// PLAYER'S (interior) CELL for these frames. Leaving 0 here made
|
||||
// the walk read the frame as OUTDOOR-rooted while the eye was
|
||||
// inside a doorway graze: full-screen terrain with the look-in
|
||||
// gates correctly refusing from the interior side — the FW3
|
||||
// visual gate's "grass briefly covers the stairwell at the
|
||||
// crossing" report (the historic #119 family's transition arm).
|
||||
// Total-failure fallback = retail set_viewer(player_pos, reset_sought=1)
|
||||
// (update_viewer :92886 and the cell==0 bail :92775 — both surface here as
|
||||
// ViewerCellId == 0): the sought resets to the returned position and
|
||||
// re-extends from there.
|
||||
if (swept.ViewerCellId == 0)
|
||||
{
|
||||
_soughtEye = swept.Eye;
|
||||
ViewerCellId = cellId;
|
||||
}
|
||||
}
|
||||
// Retail viewer — the base of next frame's interpolation (step 5).
|
||||
_publishedEye = publishedEye;
|
||||
|
|
|
|||
|
|
@ -1,252 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
using AcDream.Core.Vfx;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>Campaign FW3.2b-2 (step 1): the walk driver's leaf draws over
|
||||
/// this executor's renderers. Same leaf mechanics as the packed slice
|
||||
/// methods — the cutover changes ORDER only.</summary>
|
||||
internal sealed partial class RetailPViewPassExecutor
|
||||
{
|
||||
/// <summary>The walk's single sky turn: retail draws GameSky once
|
||||
/// inside <c>LScape::draw</c>, clipped by the active views; looping
|
||||
/// today's per-slice scissor + terrain-clip block reproduces today's
|
||||
/// pixels while the driver sees ONE turn.</summary>
|
||||
internal void DrawWalkSky(
|
||||
RetailPViewFrameInput frame, ClipFrameAssembly clipAssembly)
|
||||
{
|
||||
if (!frame.RenderSky)
|
||||
return;
|
||||
|
||||
// FW4 slice 6: an INTERIOR root draws the sky ONCE, unclipped —
|
||||
// the same LScape::draw rule as the terrain turn below (view-culled
|
||||
// landscape content is never view-clipped; the clear + seals +
|
||||
// interior repaint own aperture exactness). The former per-slice
|
||||
// scissor left stale sky color wherever the authored exit-portal
|
||||
// bands were narrower than the real opening.
|
||||
if (!frame.RootCell.IsOutdoorNode)
|
||||
{
|
||||
_sky?.RenderSky(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
frame.DayFraction,
|
||||
frame.ActiveDayGroup,
|
||||
frame.SkyKeyframe,
|
||||
frame.EnvironOverrideActive);
|
||||
if (_particles is not null && _particleRenderer is not null)
|
||||
{
|
||||
_particleRenderer.Draw(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.SkyPreScene);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ReadOnlySpan<ClipViewSlice> slices = clipAssembly.OutsideViewSlices;
|
||||
for (int sliceIndex = 0; sliceIndex < slices.Length; sliceIndex++)
|
||||
{
|
||||
ClipViewSlice slice = slices[sliceIndex];
|
||||
bool scissor = BeginDoorwayScissor(slice.NdcAabb);
|
||||
_surface.BindTerrainClip();
|
||||
EnableClipDistances();
|
||||
_sky?.RenderSky(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
frame.DayFraction,
|
||||
frame.ActiveDayGroup,
|
||||
frame.SkyKeyframe,
|
||||
frame.EnvironOverrideActive);
|
||||
DisableClipDistances();
|
||||
if (_particles is not null && _particleRenderer is not null)
|
||||
{
|
||||
_particleRenderer.Draw(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.SkyPreScene);
|
||||
}
|
||||
if (scissor)
|
||||
_surface.EndScissor();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The walk's per-slice terrain turn — the terrain block of
|
||||
/// <c>DrawLandscapeSlice</c> (scissor + terrain clip + slice planes +
|
||||
/// NDC AABB), without the sky/entity halves the walk owns separately.</summary>
|
||||
internal void DrawWalkTerrainSlice(
|
||||
RetailPViewFrameInput frame, ClipFrameAssembly clipAssembly, int sliceIndex)
|
||||
{
|
||||
ReadOnlySpan<ClipViewSlice> slices = clipAssembly.OutsideViewSlices;
|
||||
if ((uint)sliceIndex >= (uint)slices.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(sliceIndex), sliceIndex,
|
||||
$"walk terrain turn: slice {sliceIndex} of {slices.Length}");
|
||||
}
|
||||
|
||||
// FW4 slice 6: an INTERIOR root draws the terrain UNCLIPPED —
|
||||
// retail's LScape::draw draws whole blocks (view-culled, never
|
||||
// view-clipped; "retail never clips ordinary meshes per view"),
|
||||
// and aperture exactness comes from the depth clear + exit seals +
|
||||
// interior repaint that follow in the walk's own order. The former
|
||||
// per-slice scissor+clip under-painted terrain COLOR AND DEPTH
|
||||
// wherever the authored exit-portal polygons are narrower than the
|
||||
// real opening (the #456 cathedral seam bands) — leaving stale
|
||||
// color and empty depth that unclipped alpha (the falls) then
|
||||
// painted straight across. The outdoor root keeps its single
|
||||
// full-screen slice (equivalent to unclipped by construction).
|
||||
if (!frame.RootCell.IsOutdoorNode)
|
||||
{
|
||||
_terrainDiagnostics.Begin();
|
||||
_terrain?.Draw(
|
||||
frame.Camera,
|
||||
frame.Frustum,
|
||||
neverCullLandblockId: frame.PlayerLandblockId,
|
||||
clipPlanes: default,
|
||||
ndcClipAabb: new Vector4(-1f, -1f, 1f, 1f));
|
||||
_terrainDiagnostics.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
ClipViewSlice slice = slices[sliceIndex];
|
||||
bool scissor = BeginDoorwayScissor(slice.NdcAabb);
|
||||
_surface.BindTerrainClip();
|
||||
EnableClipDistances();
|
||||
_terrainDiagnostics.Begin();
|
||||
_terrain?.Draw(
|
||||
frame.Camera,
|
||||
frame.Frustum,
|
||||
neverCullLandblockId: frame.PlayerLandblockId,
|
||||
clipPlanes: slice.Planes,
|
||||
ndcClipAabb: slice.NdcAabb);
|
||||
_terrainDiagnostics.Complete();
|
||||
DisableClipDistances();
|
||||
if (scissor)
|
||||
_surface.EndScissor();
|
||||
}
|
||||
|
||||
/// <summary>The walk's punch-fan turn — <c>DrawPortalPolyInternal</c>
|
||||
/// @0x0059bc90's far-Z punch through <c>PortalDepthMaskRenderer</c>,
|
||||
/// clipped by the pinned view's slice planes (retail
|
||||
/// <c>building_view</c> @0x0059f3bf). FW3.3: fans draw at the dat
|
||||
/// aperture verbatim (the ShellDrawLiftZ retirement).</summary>
|
||||
internal void DrawWalkPunchFan(
|
||||
RetailPViewFrameInput frame,
|
||||
ClipFrameAssembly clipAssembly,
|
||||
WalkPolygon worldPolygon,
|
||||
int activeViewIndex)
|
||||
{
|
||||
if (_portalDepthMask is null)
|
||||
return;
|
||||
Vector3[] vertices = worldPolygon.Vertices;
|
||||
if (vertices.Length < 3)
|
||||
return;
|
||||
ReadOnlySpan<ClipViewSlice> slices = clipAssembly.OutsideViewSlices;
|
||||
ReadOnlySpan<Vector4> planes = (uint)activeViewIndex < (uint)slices.Length
|
||||
? slices[activeViewIndex].Planes
|
||||
: default;
|
||||
|
||||
Span<Vector3> world = stackalloc Vector3[32];
|
||||
int count = Math.Min(vertices.Length, world.Length);
|
||||
for (int vertex = 0; vertex < count; vertex++)
|
||||
world[vertex] = vertices[vertex];
|
||||
_portalDepthMask.DrawDepthFan(
|
||||
world[..count],
|
||||
frame.ViewProjection,
|
||||
planes,
|
||||
forceFarZ: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2b-2 (step 1, additive): the production
|
||||
/// <see cref="IWalkFrameLeafRenderer"/> over this executor's leaf renderers.
|
||||
/// Constructed per frame by the FW3.2b-2 rooting (plan §FW3 "FW3.2b-2 —
|
||||
/// the production rooting"); nothing invokes it until the static cutover
|
||||
/// flips. Each member maps a <see cref="WalkFrameDriver"/> turn onto the
|
||||
/// SAME leaf calls the packed path uses today, so the cutover changes
|
||||
/// ORDER, never the leaf mechanics:
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item><see cref="DrawSky"/> → the sky block of <c>DrawLandscapeSlice</c>,
|
||||
/// looped over the active slices internally (retail draws GameSky once
|
||||
/// inside <c>LScape::draw</c> clipped by the active views; the per-slice
|
||||
/// scissor+clip here reproduces today's pixels while the driver still sees
|
||||
/// ONE sky turn).</item>
|
||||
/// <item><see cref="DrawTerrainSlice"/> → the terrain block of
|
||||
/// <c>DrawLandscapeSlice</c> (scissor + terrain clip + slice planes).</item>
|
||||
/// <item><see cref="DrawCellShell"/> → <c>EnvCellRenderer</c> opaque +
|
||||
/// transparent-ordered for ONE cell (retail <c>DrawEnvCell</c>
|
||||
/// @0x0059f170 draws per cell at its flood turn).</item>
|
||||
/// <item><see cref="DrawPunchFan"/> → <c>PortalDepthMaskRenderer.DrawDepthFan</c>
|
||||
/// with <c>forceFarZ</c>, clipped by the pinned view's slice planes
|
||||
/// (retail <c>building_view</c> @0x0059f3bf); FW3.3 draws fans at the dat
|
||||
/// aperture verbatim (the ShellDrawLiftZ retirement).</item>
|
||||
/// <item><see cref="ClearInteriorDepth"/>/<see cref="DrawExitSeals"/> →
|
||||
/// caller-supplied actions (the renderer owns the pass scope and the
|
||||
/// root-flood seal iteration; the adapter only provides the turns).</item>
|
||||
/// <item><see cref="AlphaBarrier"/> → <c>FlushLandscapeAlphaFartherThan</c>
|
||||
/// (the <c>DrawBuilding</c> barrier; retail's flush-all
|
||||
/// <c>FlushAlphaList(0f)</c> @0x0059f30b is the FW4 adjudication
|
||||
/// candidate recorded on the driver's interface).</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
internal sealed class WalkProductionLeafRenderer : IWalkFrameLeafRenderer
|
||||
{
|
||||
private readonly RetailPViewPassExecutor _passes;
|
||||
private readonly RetailPViewFrameInput _frame;
|
||||
private readonly ClipFrameAssembly _clipAssembly;
|
||||
private readonly Action _clearInteriorDepth;
|
||||
private readonly Action _drawExitSeals;
|
||||
private readonly HashSet<uint> _singleCellScratch = new();
|
||||
private readonly List<uint> _singleCellListScratch = new();
|
||||
|
||||
internal WalkProductionLeafRenderer(
|
||||
RetailPViewPassExecutor passes,
|
||||
RetailPViewFrameInput frame,
|
||||
ClipFrameAssembly clipAssembly,
|
||||
Action clearInteriorDepth,
|
||||
Action drawExitSeals)
|
||||
{
|
||||
_passes = passes ?? throw new ArgumentNullException(nameof(passes));
|
||||
_frame = frame ?? throw new ArgumentNullException(nameof(frame));
|
||||
_clipAssembly = clipAssembly;
|
||||
_clearInteriorDepth = clearInteriorDepth
|
||||
?? throw new ArgumentNullException(nameof(clearInteriorDepth));
|
||||
_drawExitSeals = drawExitSeals
|
||||
?? throw new ArgumentNullException(nameof(drawExitSeals));
|
||||
}
|
||||
|
||||
public void DrawSky() => _passes.DrawWalkSky(_frame, _clipAssembly);
|
||||
|
||||
public void DrawTerrainSlice(int sliceIndex) =>
|
||||
_passes.DrawWalkTerrainSlice(_frame, _clipAssembly, sliceIndex);
|
||||
|
||||
public void DrawCellShell(uint cellId)
|
||||
{
|
||||
_singleCellScratch.Clear();
|
||||
_singleCellScratch.Add(cellId);
|
||||
_passes.DrawOpaqueCellShells(_singleCellScratch);
|
||||
if (_passes.CellHasTransparentShell(cellId))
|
||||
{
|
||||
_singleCellListScratch.Clear();
|
||||
_singleCellListScratch.Add(cellId);
|
||||
_passes.DrawTransparentCellShellsOrdered(_singleCellListScratch);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearInteriorDepth() => _clearInteriorDepth();
|
||||
|
||||
public void DrawStaticParticles(IReadOnlySet<uint> ownerIds) =>
|
||||
_passes.DrawLandscapeStaticParticles(
|
||||
_frame, new RetailPViewLandscapeStaticParticleContext(ownerIds));
|
||||
|
||||
public void DrawExitSeals() => _drawExitSeals();
|
||||
|
||||
public void DrawPunchFan(WalkPolygon worldPolygon, int activeViewIndex) =>
|
||||
_passes.DrawWalkPunchFan(_frame, _clipAssembly, worldPolygon, activeViewIndex);
|
||||
|
||||
public void AlphaBarrier(float viewerDistance) =>
|
||||
_passes.FlushLandscapeAlphaFartherThan(viewerDistance);
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Sky;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
|
@ -115,7 +114,7 @@ internal interface IRenderFrameEntityPassExecutor
|
|||
/// is written once; the four places it touches graphics state directly are owned
|
||||
/// by <see cref="IWorldPassSurface"/>.
|
||||
/// </summary>
|
||||
internal sealed partial class RetailPViewPassExecutor :
|
||||
internal sealed class RetailPViewPassExecutor :
|
||||
IRetailPViewPassExecutor,
|
||||
IRenderFrameEntityPassExecutor,
|
||||
IOutdoorSceneParticleOwnerSource
|
||||
|
|
@ -180,29 +179,8 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
public void BeginFrame()
|
||||
{
|
||||
_particleClassifications.BeginFrame();
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||
AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase = "pre";
|
||||
}
|
||||
|
||||
/// <summary>Campaign FW3.2b-2: the shared dispatcher, for
|
||||
/// <see cref="RetailPViewRenderer"/>'s <c>WalkFrameDriver</c>
|
||||
/// construction — the driver's ctor takes a <see cref="WbDrawDispatcher"/>
|
||||
/// directly (it calls <c>SubmitOrderedStream</c> itself; see that
|
||||
/// class's own doc comment).</summary>
|
||||
internal WbDrawDispatcher Dispatcher => _entities;
|
||||
|
||||
/// <summary>Campaign FW3.2b-2: forwards to
|
||||
/// <see cref="WbDrawDispatcher.RequireWalkSubmission"/> — the frame/
|
||||
/// encoder pair the walk driver submits its stream flushes into.</summary>
|
||||
internal (IGpuFrame Frame, IGpuPassEncoder Encoder) RequireWalkSubmission() =>
|
||||
_entities.RequireWalkSubmission();
|
||||
|
||||
/// <summary>Campaign FW3.2b-2: forwards to
|
||||
/// <see cref="WbDrawDispatcher.WalkAttachmentExtent"/> — the real
|
||||
/// viewport size for <c>WalkProductionFrameContext</c>.</summary>
|
||||
internal (int Width, int Height)? WalkAttachmentExtent =>
|
||||
_entities.WalkAttachmentExtent;
|
||||
|
||||
public void BeginEntityFrame(in RenderFrameView view) =>
|
||||
_entities.BeginPackedProductionFrame(in view);
|
||||
|
||||
|
|
@ -470,16 +448,7 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
// DrawLandscapeStaticParticles after the slice loop (retail: one
|
||||
// unclipped alpha-list insertion per emitter), not per slice here.
|
||||
EnableClipDistances();
|
||||
// Retail GameSky::Draw @0x00506ff0 gates the WEATHER pass (arg2==1)
|
||||
// on SmartBox::is_player_outside @0x00451e80 — Ghidra-arbitrated:
|
||||
// (player objcell_id & 0xFFFF) < 0x100. The sky pass always draws;
|
||||
// the rain draws ONLY while the PLAYER stands in an outdoor cell.
|
||||
// That one boolean is retail's entire rain confinement (the
|
||||
// owner-reported indoor/seam rain at the cathedral: rain on the
|
||||
// ledges, none the instant the player crosses into an interior
|
||||
// cell) — no depth or view-clip mechanism is involved.
|
||||
bool playerOutside = (frame.PlayerCellId & 0xFFFFu) < 0x100u;
|
||||
if (frame.RenderSky && frame.RenderWeather && playerOutside)
|
||||
if (frame.RenderSky && frame.RenderWeather)
|
||||
{
|
||||
_sky?.RenderWeather(
|
||||
frame.Camera,
|
||||
|
|
@ -521,7 +490,6 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
DisableClipDistances();
|
||||
|
||||
_particleClassifications.ReplaceOutdoor(context.ParticleOwnerIds);
|
||||
ProbeWalkParticleRoute("static-owners", _particleClassifications.Outdoor);
|
||||
if (_particleClassifications.Outdoor.Count > 0
|
||||
&& _particles is not null
|
||||
&& _particleRenderer is not null)
|
||||
|
|
@ -581,12 +549,7 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
DisableClipDistances();
|
||||
}
|
||||
|
||||
public void ClearInteriorDepth()
|
||||
{
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||
AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase = "post";
|
||||
_surface.ClearInteriorDepth();
|
||||
}
|
||||
public void ClearInteriorDepth() => _surface.ClearInteriorDepth();
|
||||
|
||||
public void DrawExitPortalMask(
|
||||
RetailPViewFrameInput frame,
|
||||
|
|
@ -615,8 +578,6 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
// hardware clip slot made effects vanish by view direction (zero
|
||||
// outside slices in view = zero submissions) — invented behavior.
|
||||
DisableClipDistances();
|
||||
ProbeWalkParticleRoute(
|
||||
outdoorCells ? "unattached-outdoor" : "unattached-interior", null);
|
||||
_particleRenderer.DrawForOwners(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
|
|
@ -629,53 +590,6 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
: UnattachedEmitterCellScope.InteriorCells);
|
||||
}
|
||||
|
||||
/// <summary>ACDREAM_PROBE_WALK_ROOT companion (throwaway, dies with the
|
||||
/// cathedral falls investigation): logs each particle submission route
|
||||
/// with its owner count + the pre/post-depth-clear phase tag.</summary>
|
||||
private static void ProbeWalkParticleRoute(string route, IReadOnlySet<uint>? owners)
|
||||
{
|
||||
if (!AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||
return;
|
||||
string ids = "";
|
||||
if (owners is not null)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
int shown = 0;
|
||||
foreach (uint id in owners)
|
||||
{
|
||||
if (shown++ == 6) { sb.Append(",…"); break; }
|
||||
if (sb.Length > 0) sb.Append(',');
|
||||
sb.Append(id.ToString("x"));
|
||||
}
|
||||
ids = $" n={owners.Count} ids=[{sb}]";
|
||||
|
||||
// Phase I cathedral instrumentation: the REAL falls owners
|
||||
// (outdoor landblock statics 0xCF418000-13 per the DAT-geometry
|
||||
// report) — print EVERY submission that carries one, with the
|
||||
// exact watched ids, unconditionally (never rate-limited; these
|
||||
// are the money lines the six fix rounds never had).
|
||||
System.Text.StringBuilder? falls = null;
|
||||
foreach (uint id in owners)
|
||||
{
|
||||
if (id >= 0xCF418000u && id <= 0xCF418014u)
|
||||
{
|
||||
falls ??= new System.Text.StringBuilder();
|
||||
if (falls.Length > 0) falls.Append(',');
|
||||
falls.Append(id.ToString("x"));
|
||||
}
|
||||
}
|
||||
if (falls is not null)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[falls] route={route} phase="
|
||||
+ $"{AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase} "
|
||||
+ $"ids=[{falls}]");
|
||||
}
|
||||
}
|
||||
Console.WriteLine(
|
||||
$"[walk-part] {route}{ids} phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}");
|
||||
}
|
||||
|
||||
public void FlushLandscapeAlpha() => _alpha.Flush();
|
||||
|
||||
public void FlushLandscapeAlphaFartherThan(float minViewerDistance) =>
|
||||
|
|
@ -699,7 +613,6 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
return;
|
||||
|
||||
DisableClipDistances();
|
||||
ProbeWalkParticleRoute("cell-owners", visible);
|
||||
// Retail never clips cell particles to a portal view: the owner
|
||||
// cell's walls own occlusion via the depth test at the alpha flush.
|
||||
_particleRenderer.DrawForOwners(
|
||||
|
|
@ -725,7 +638,6 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
return;
|
||||
|
||||
DisableClipDistances();
|
||||
ProbeWalkParticleRoute("dyn-owners", dynamics);
|
||||
_particleRenderer.DrawForOwners(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
|
|
@ -779,11 +691,10 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
int count = Math.Min(localVertices.Length, world.Length);
|
||||
for (int vertex = 0; vertex < count; vertex++)
|
||||
{
|
||||
// FW3.3: fans draw at the dat aperture verbatim (the
|
||||
// ShellDrawLiftZ retirement — shells draw unlifted too).
|
||||
world[vertex] = Vector3.Transform(
|
||||
localVertices[vertex],
|
||||
cell.WorldTransform);
|
||||
world[vertex].Z += PortalVisibilityBuilder.ShellDrawLiftZ;
|
||||
}
|
||||
|
||||
_diagnostics.EmitSeamMask(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -70,47 +70,6 @@ internal readonly record struct RenderInstanceCandidate(
|
|||
TupleLandblockId: tupleLandblockId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2a: builds a candidate straight from a
|
||||
/// <see cref="RenderProjectionRecord"/>, without the packed route's
|
||||
/// <see cref="RenderFrameEntityCandidate"/>/frame-arena mesh-part
|
||||
/// flattening. The walk populator reads <see cref="RenderSceneQuery"/>
|
||||
/// records directly (<c>CopyCellStaticsTo</c> / <c>CopyIndexTo</c>), so it
|
||||
/// has no frame arena to look the source candidate up in — every field
|
||||
/// this needs already lives on the record's own <see cref="RenderSourceMetadata"/>
|
||||
/// and <see cref="RenderEntityPayload"/>.
|
||||
///
|
||||
/// <para><paramref name="animated"/> defaults to false: the walk's static
|
||||
/// routes (<c>RenderProjectionClass.OutdoorStatic</c> /
|
||||
/// <c>IndoorCellStatic</c>) never carry retail's per-part
|
||||
/// <c>TransparentPartHook</c> animation — that mechanic keys off a live
|
||||
/// entity's ServerGuid, not a world static's. A future dynamic walk route
|
||||
/// passes true explicitly.</para>
|
||||
/// </summary>
|
||||
internal static RenderInstanceCandidate FromProjection(
|
||||
in RenderProjectionRecord projection,
|
||||
uint tupleLandblockId,
|
||||
bool animated = false)
|
||||
{
|
||||
RenderEntityPayload payload = projection.EntityPayload;
|
||||
return new RenderInstanceCandidate(
|
||||
ProjectionId: projection.Id,
|
||||
LocalEntityId: projection.Source.LocalEntityId,
|
||||
ServerGuid: projection.Source.ServerGuid,
|
||||
SourceId: projection.Source.SourceId,
|
||||
ParentCellId: projection.Source.ParentCellId,
|
||||
RootWorld: projection.Transform.LocalToWorld,
|
||||
Position: projection.Transform.Position,
|
||||
Rotation: projection.Transform.Rotation,
|
||||
Scale: projection.Transform.UniformScale,
|
||||
Bounds: projection.Bounds,
|
||||
PaletteOverride: payload.PaletteOverride,
|
||||
IsBuildingShell: payload.IsBuildingShell,
|
||||
Animated: animated,
|
||||
MeshPartCount: payload.MeshRefs?.Count ?? 0,
|
||||
TupleLandblockId: tupleLandblockId);
|
||||
}
|
||||
|
||||
internal static RenderInstanceCandidate FromFrame(
|
||||
in RenderFrameEntityCandidate source,
|
||||
uint tupleLandblockId)
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ internal readonly record struct RenderScenePViewBuildInput(
|
|||
HashSet<uint> DrawableCells,
|
||||
IRetailPViewCellSource Cells,
|
||||
HashSet<uint>? AnimatedEntityIds,
|
||||
bool RootIsOutdoor,
|
||||
IReadOnlySet<uint> RootFloodCells);
|
||||
bool RootIsOutdoor);
|
||||
|
||||
internal readonly record struct RenderFrameProductComparisonSnapshot(
|
||||
bool Enabled,
|
||||
|
|
@ -123,11 +122,6 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
{
|
||||
private readonly RenderSceneShadowRuntime _shadow;
|
||||
private readonly CurrentRenderSceneOracle? _current;
|
||||
|
||||
/// <summary>Campaign FW3.2b-2: the frame's retained-scene read view for
|
||||
/// the walk's world-data provider (the same query
|
||||
/// <see cref="BuildAndBorrow"/> reads its routes from).</summary>
|
||||
internal RenderSceneQuery SceneQuery => _shadow.Query;
|
||||
private readonly WbDrawDispatcher? _dispatcher;
|
||||
private readonly RenderScenePViewFrameBuilder _builder = new();
|
||||
private readonly RenderFrameExchange _exchange = new();
|
||||
|
|
@ -189,8 +183,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
HashSet<uint> drawableCells,
|
||||
IRetailPViewCellSource cells,
|
||||
HashSet<uint>? animatedEntityIds,
|
||||
bool rootIsOutdoor,
|
||||
IReadOnlySet<uint> rootFloodCells)
|
||||
bool rootIsOutdoor)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(portalFrame);
|
||||
ArgumentNullException.ThrowIfNull(clipAssembly);
|
||||
|
|
@ -217,8 +210,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
drawableCells,
|
||||
cells,
|
||||
animatedEntityIds,
|
||||
rootIsOutdoor,
|
||||
rootFloodCells);
|
||||
rootIsOutdoor);
|
||||
_builder.Build(_exchange, frameSequence, in input);
|
||||
return _exchange.BorrowLatest(
|
||||
scene.Generation,
|
||||
|
|
@ -238,12 +230,8 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
HashSet<uint>? animatedEntityIds,
|
||||
uint tupleLandblockId,
|
||||
bool rootIsOutdoor,
|
||||
Vector3 cameraWorldPosition = default,
|
||||
IReadOnlySet<uint>? rootFloodCells = null)
|
||||
Vector3 cameraWorldPosition = default)
|
||||
{
|
||||
// Comparison/diagnostic path: absent an explicit walk root flood,
|
||||
// drawableCells keeps the pre-stage-split admission (drawableCells
|
||||
// WAS the flood in the legacy meaning these fixtures pin).
|
||||
RenderFrameView view = BuildAndBorrow(
|
||||
portalFrame,
|
||||
clipAssembly,
|
||||
|
|
@ -252,8 +240,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
drawableCells,
|
||||
cells,
|
||||
animatedEntityIds,
|
||||
rootIsOutdoor,
|
||||
rootFloodCells ?? drawableCells);
|
||||
rootIsOutdoor);
|
||||
try
|
||||
{
|
||||
Compare(
|
||||
|
|
@ -1136,6 +1123,7 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
private const byte EnvCellProjectionDomain = 2;
|
||||
|
||||
private readonly HashSet<RenderProjectionId> _projectionIds = [];
|
||||
private RenderProjectionRecord[] _outdoor = [];
|
||||
private RenderProjectionRecord[] _dynamics = [];
|
||||
private RenderProjectionRecord[] _cell = [];
|
||||
private RenderProjectionRecord[] _dirty = [];
|
||||
|
|
@ -1146,15 +1134,14 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
// marks every non-player part): an object whose cell drew with a look-in
|
||||
// must not enter the final dynamics route again.
|
||||
private readonly HashSet<uint> _lookInCellScratch = new();
|
||||
private RenderProjectionRecord[] _cellRoute = [];
|
||||
private readonly Dictionary<RenderProjectionId, int>
|
||||
_outdoorPositions = [];
|
||||
private readonly Dictionary<RenderProjectionId, int>
|
||||
_dynamicPositions = [];
|
||||
private int _outdoorCount;
|
||||
private int _dynamicCount;
|
||||
|
||||
// ACDREAM_PROBE_WALK_ROOT companion (throwaway): rate limiter for the
|
||||
// [walk-dyn] outside-stage classification dump, plus the [dyn-route]
|
||||
// per-entity on-change admission cache.
|
||||
private uint _probeDynFrameCounter;
|
||||
private readonly Dictionary<uint, string> _probeDynRouteStates = new();
|
||||
private int _cellRouteCount;
|
||||
private int _dirtyCount;
|
||||
private RenderSceneGeneration _indexGeneration;
|
||||
private ulong _indexRevision;
|
||||
|
|
@ -1177,13 +1164,7 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
_projectionIds.Clear();
|
||||
LoadSceneIndices(input.Scene);
|
||||
|
||||
// Campaign FW3.2b-2: LandscapeOutdoorStatic, LandscapeBuildingShell,
|
||||
// and CellStatic no longer emit here — WalkFrameDriver draws every
|
||||
// outdoor static, building shell, and cell static (including
|
||||
// look-in cell statics) directly through OrderedDrawStream (plan
|
||||
// §FW3 "FW3.2b-2 — the production rooting", item 3). LookInObject
|
||||
// keeps emitting, but BuildLookInRoutes below is now filtered to
|
||||
// DYNAMIC candidates only — the walk owns that route's statics.
|
||||
BuildOutdoorRoutes(writer, in input);
|
||||
int lookInRouteIndex = 0;
|
||||
for (int frameIndex = 0;
|
||||
frameIndex < input.LookInFrames.Count;
|
||||
|
|
@ -1194,8 +1175,13 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
in input,
|
||||
frameIndex,
|
||||
ref lookInRouteIndex);
|
||||
BuildLookInBuildingShellRoutes(
|
||||
writer,
|
||||
in input,
|
||||
frameIndex);
|
||||
}
|
||||
BuildOutsideDynamicRoutes(writer, in input);
|
||||
BuildCellStaticRoute(writer, in input);
|
||||
BuildDynamicLastRoute(writer, in input);
|
||||
writer.Publish();
|
||||
AcknowledgeCachedDirtyRecords();
|
||||
|
|
@ -1214,6 +1200,18 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
if (scene.Generation != _indexGeneration
|
||||
|| revision != _indexRevision)
|
||||
{
|
||||
EnsureCapacity(ref _outdoor, counts.OutdoorStatic);
|
||||
_outdoorCount = scene.CopyIndexTo(
|
||||
RenderSceneIndex.OutdoorStatic,
|
||||
_outdoor);
|
||||
_outdoorCount = CompactAndSort(
|
||||
_outdoor,
|
||||
_outdoorCount);
|
||||
BuildPositionIndex(
|
||||
_outdoor,
|
||||
_outdoorCount,
|
||||
_outdoorPositions);
|
||||
|
||||
EnsureCapacity(ref _dynamics, counts.Dynamic);
|
||||
_dynamicCount = scene.CopyIndexTo(
|
||||
RenderSceneIndex.Dynamic,
|
||||
|
|
@ -1237,6 +1235,12 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
for (int index = 0; index < _dirtyCount; index++)
|
||||
{
|
||||
RenderProjectionRecord record = _dirty[index];
|
||||
if (_outdoorPositions.TryGetValue(
|
||||
record.Id,
|
||||
out int outdoorPosition))
|
||||
{
|
||||
_outdoor[outdoorPosition] = record;
|
||||
}
|
||||
if (_dynamicPositions.TryGetValue(
|
||||
record.Id,
|
||||
out int dynamicPosition))
|
||||
|
|
@ -1251,6 +1255,16 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
for (int index = 0; index < _dirtyCount; index++)
|
||||
{
|
||||
RenderProjectionId id = _dirty[index].Id;
|
||||
if (_outdoorPositions.TryGetValue(
|
||||
id,
|
||||
out int outdoorPosition))
|
||||
{
|
||||
_outdoor[outdoorPosition] =
|
||||
_outdoor[outdoorPosition] with
|
||||
{
|
||||
DirtyMask = RenderDirtyMask.None,
|
||||
};
|
||||
}
|
||||
if (_dynamicPositions.TryGetValue(
|
||||
id,
|
||||
out int dynamicPosition))
|
||||
|
|
@ -1275,13 +1289,103 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
positions.Add(records[index].Id, index);
|
||||
}
|
||||
|
||||
/// <summary>Campaign FW3.2b-2: <c>LookInObject</c> now carries DYNAMIC
|
||||
/// candidates only — the walk draws every look-in cell's STATIC content
|
||||
/// directly (<c>WalkFrameDriver</c>'s <c>LookInStatic</c> turn, via
|
||||
/// <c>WalkProductionWorldData.GetCellStatics</c>), so loading this
|
||||
/// route's cell contents with <c>includeStatics: false</c> is what keeps
|
||||
/// the two draws from doubling a look-in room's furniture (plan §FW3
|
||||
/// item 3).</summary>
|
||||
private void BuildOutdoorRoutes(
|
||||
RenderFrameWriter writer,
|
||||
in RenderScenePViewBuildInput input)
|
||||
{
|
||||
int sliceCount = input.ClipAssembly.OutsideViewSlices.Length;
|
||||
for (int sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
|
||||
{
|
||||
int count = 0;
|
||||
EnsureCapacity(ref _survivors, _outdoorCount);
|
||||
for (int i = 0; i < _outdoorCount; i++)
|
||||
{
|
||||
RenderProjectionRecord record = _outdoor[i];
|
||||
if (record.EntityPayload.IsBuildingShell
|
||||
&& RetailPViewRenderer.FindLookInFrameIndex(
|
||||
record.Source.BuildingShellAnchorCellId,
|
||||
input.LookInFrames,
|
||||
input.Cells) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Sphere(in record, out Vector3 center, out float radius);
|
||||
if (!input.Viewcone.SphereVisibleInOutsideSlice(
|
||||
sliceIndex,
|
||||
in center,
|
||||
radius))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_survivors[count++] = record;
|
||||
writer.AddOutdoor(in record);
|
||||
AddProjection(
|
||||
writer,
|
||||
in record,
|
||||
input.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.LandscapeOutdoorStatic,
|
||||
sliceIndex,
|
||||
0,
|
||||
_survivors.AsSpan(0, count));
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildLookInBuildingShellRoutes(
|
||||
RenderFrameWriter writer,
|
||||
in RenderScenePViewBuildInput input,
|
||||
int frameIndex)
|
||||
{
|
||||
int sliceCount = input.ClipAssembly.OutsideViewSlices.Length;
|
||||
for (int sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
|
||||
{
|
||||
int count = 0;
|
||||
EnsureCapacity(ref _survivors, _outdoorCount);
|
||||
for (int i = 0; i < _outdoorCount; i++)
|
||||
{
|
||||
RenderProjectionRecord record = _outdoor[i];
|
||||
if (!record.EntityPayload.IsBuildingShell
|
||||
|| RetailPViewRenderer.FindLookInFrameIndex(
|
||||
record.Source.BuildingShellAnchorCellId,
|
||||
input.LookInFrames,
|
||||
input.Cells) != frameIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Sphere(in record, out Vector3 center, out float radius);
|
||||
if (!input.Viewcone.SphereVisibleInOutsideSlice(
|
||||
sliceIndex,
|
||||
in center,
|
||||
radius))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_survivors[count++] = record;
|
||||
writer.AddOutdoor(in record);
|
||||
AddProjection(
|
||||
writer,
|
||||
in record,
|
||||
input.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
int routeIndex =
|
||||
RetailPViewRenderer.LookInBuildingShellRouteIndex(
|
||||
frameIndex,
|
||||
sliceCount,
|
||||
sliceIndex);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.LandscapeBuildingShell,
|
||||
routeIndex,
|
||||
0,
|
||||
_survivors.AsSpan(0, count));
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildLookInRoutes(
|
||||
RenderFrameWriter writer,
|
||||
in RenderScenePViewBuildInput input,
|
||||
|
|
@ -1304,7 +1408,6 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
int count = LoadCell(
|
||||
input.Scene,
|
||||
cellId,
|
||||
includeStatics: false,
|
||||
includeDynamics: true);
|
||||
for (int sliceIndex = 0; sliceIndex < slices.Length; sliceIndex++)
|
||||
{
|
||||
|
|
@ -1334,9 +1437,6 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
return;
|
||||
|
||||
int sliceCount = input.ClipAssembly.OutsideViewSlices.Length;
|
||||
bool probeDynamics =
|
||||
AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled
|
||||
&& ++_probeDynFrameCounter % 120 == 0;
|
||||
for (int sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
|
||||
{
|
||||
int count = 0;
|
||||
|
|
@ -1345,23 +1445,19 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
{
|
||||
RenderProjectionRecord record = _dynamics[i];
|
||||
Sphere(in record, out Vector3 center, out float radius);
|
||||
bool outside = RetailPViewRenderer.DynamicDrawsInOutsideStage(
|
||||
ParentCell(in record),
|
||||
center,
|
||||
radius,
|
||||
input.DrawableCells,
|
||||
input.Cells);
|
||||
bool cone = input.Viewcone.SphereVisibleInOutsideSlice(
|
||||
sliceIndex, in center, radius);
|
||||
if (probeDynamics)
|
||||
if (!RetailPViewRenderer.DynamicDrawsInOutsideStage(
|
||||
ParentCell(in record),
|
||||
center,
|
||||
radius,
|
||||
input.DrawableCells,
|
||||
input.Cells)
|
||||
|| !input.Viewcone.SphereVisibleInOutsideSlice(
|
||||
sliceIndex,
|
||||
in center,
|
||||
radius))
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[walk-dyn] slice={sliceIndex} id={record.Source.LocalEntityId:x} "
|
||||
+ $"parent={ParentCell(in record)?.ToString("x8") ?? "null"} "
|
||||
+ $"r={radius:F1} outside={(outside ? 1 : 0)} cone={(cone ? 1 : 0)}");
|
||||
}
|
||||
if (!outside || !cone)
|
||||
continue;
|
||||
}
|
||||
|
||||
_survivors[count++] = record;
|
||||
writer.AddDynamic(in record);
|
||||
|
|
@ -1379,6 +1475,61 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
}
|
||||
}
|
||||
|
||||
private void BuildCellStaticRoute(
|
||||
RenderFrameWriter writer,
|
||||
in RenderScenePViewBuildInput input)
|
||||
{
|
||||
_cellRouteCount = 0;
|
||||
IReadOnlyList<uint> ordered = input.PortalFrame.OrderedVisibleCells;
|
||||
for (int i = ordered.Count - 1; i >= 0; i--)
|
||||
{
|
||||
uint cellId = ordered[i];
|
||||
if (!input.DrawableCells.Contains(cellId))
|
||||
continue;
|
||||
|
||||
int loaded = LoadCell(
|
||||
input.Scene,
|
||||
cellId,
|
||||
includeDynamics: false);
|
||||
int count = 0;
|
||||
for (int index = 0; index < loaded; index++)
|
||||
{
|
||||
RenderProjectionRecord record = _cell[index];
|
||||
Sphere(in record, out Vector3 center, out float radius);
|
||||
if (!input.Viewcone.SphereVisibleInCell(
|
||||
cellId,
|
||||
in center,
|
||||
radius))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_cell[count++] = record;
|
||||
AddProjection(
|
||||
writer,
|
||||
in record,
|
||||
input.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
writer.AddCellRange(
|
||||
cellId,
|
||||
i,
|
||||
_cell.AsSpan(0, count));
|
||||
EnsureCapacity(
|
||||
ref _cellRoute,
|
||||
checked(_cellRouteCount + count));
|
||||
_cell.AsSpan(0, count).CopyTo(
|
||||
_cellRoute.AsSpan(_cellRouteCount));
|
||||
_cellRouteCount += count;
|
||||
}
|
||||
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.CellStatic,
|
||||
0,
|
||||
0,
|
||||
_cellRoute.AsSpan(0, _cellRouteCount));
|
||||
}
|
||||
|
||||
private void BuildDynamicLastRoute(
|
||||
RenderFrameWriter writer,
|
||||
in RenderScenePViewBuildInput input)
|
||||
|
|
@ -1410,47 +1561,7 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
// would draw it after the boundary alpha drain and overpaint
|
||||
// nearer flames (the Holtburg candle-behind-door class).
|
||||
if (indoor && _lookInCellScratch.Contains(parentCellId!.Value))
|
||||
{
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||
{
|
||||
string state = $"parent={parentCellId.Value:x8} lookin-excluded";
|
||||
if (!_probeDynRouteStates.TryGetValue(record.Source.LocalEntityId, out string? prev)
|
||||
|| prev != state)
|
||||
{
|
||||
_probeDynRouteStates[record.Source.LocalEntityId] = state;
|
||||
Console.WriteLine(
|
||||
$"[dyn-route] id={record.Source.LocalEntityId:x} {state}");
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// The stage-set split (synthesis plan step 4, [dyn-route]-pinned
|
||||
// 2026-08-30): an interior-parented dynamic rides the LAST pass
|
||||
// ONLY when its parent cell is in THE WALK'S OWN ROOT FLOOD.
|
||||
// Retail draws a look-in cell's occupants inside the landscape
|
||||
// stage through the composed portal chain, and an unreachable
|
||||
// cell's occupants not at all — the legacy visibility builder
|
||||
// invents cross-building cell views at the cathedral (real
|
||||
// 3-5-plane cones for 0xF4180112 from roots with no sightline),
|
||||
// which let a remote player draw post-clear/post-world through
|
||||
// opaque walls. The walk's flood is oracle-trace-conformant;
|
||||
// key the route on it.
|
||||
if (indoor && !input.RootFloodCells.Contains(parentCellId!.Value))
|
||||
{
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled)
|
||||
{
|
||||
string state = $"parent={parentCellId.Value:x8} rootflood-excluded";
|
||||
if (!_probeDynRouteStates.TryGetValue(record.Source.LocalEntityId, out string? prev)
|
||||
|| prev != state)
|
||||
{
|
||||
_probeDynRouteStates[record.Source.LocalEntityId] = state;
|
||||
Console.WriteLine(
|
||||
$"[dyn-route] id={record.Source.LocalEntityId:x} {state}");
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Sphere(in record, out Vector3 center, out float radius);
|
||||
bool visible = indoor
|
||||
|
|
@ -1461,25 +1572,6 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
: input.Viewcone.SphereVisibleOutside(
|
||||
in center,
|
||||
radius);
|
||||
// ACDREAM_PROBE_WALK_ROOT companion (throwaway): the DynamicLast
|
||||
// admission trace for the through-wall remote-player report —
|
||||
// per entity, ON CHANGE: parent cell, look-in exclusion, the
|
||||
// viewcone verdict, and the cell's lifted-plane shape ("0" = a
|
||||
// pass-all zero-plane slice — the scissor-fallback trapdoor).
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled
|
||||
&& indoor)
|
||||
{
|
||||
string state =
|
||||
$"parent={parentCellId!.Value:x8} vis={(visible ? 1 : 0)} "
|
||||
+ $"cone={input.Viewcone.ProbeDescribeCell(parentCellId.Value)}";
|
||||
if (!_probeDynRouteStates.TryGetValue(record.Source.LocalEntityId, out string? prev)
|
||||
|| prev != state)
|
||||
{
|
||||
_probeDynRouteStates[record.Source.LocalEntityId] = state;
|
||||
Console.WriteLine(
|
||||
$"[dyn-route] id={record.Source.LocalEntityId:x} {state} route=DynamicLast");
|
||||
}
|
||||
}
|
||||
if (!visible)
|
||||
continue;
|
||||
|
||||
|
|
@ -1501,25 +1593,18 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
private int LoadCell(
|
||||
RenderSceneQuery scene,
|
||||
uint cellId,
|
||||
bool includeDynamics,
|
||||
bool includeStatics = true)
|
||||
bool includeDynamics)
|
||||
{
|
||||
int staticCount = includeStatics
|
||||
? scene.GetCellStaticCount(cellId)
|
||||
: 0;
|
||||
int staticCount = scene.GetCellStaticCount(cellId);
|
||||
int dynamicCount = includeDynamics
|
||||
? scene.GetCellDynamicCount(cellId)
|
||||
: 0;
|
||||
EnsureCapacity(
|
||||
ref _cell,
|
||||
checked(staticCount + dynamicCount));
|
||||
int count = 0;
|
||||
if (staticCount > 0)
|
||||
{
|
||||
count = scene.CopyCellStaticsTo(
|
||||
cellId,
|
||||
_cell.AsSpan(0, staticCount));
|
||||
}
|
||||
int count = scene.CopyCellStaticsTo(
|
||||
cellId,
|
||||
_cell.AsSpan(0, staticCount));
|
||||
if (dynamicCount > 0)
|
||||
{
|
||||
count += scene.CopyCellDynamicsTo(
|
||||
|
|
|
|||
|
|
@ -41,10 +41,7 @@ public sealed partial class TerrainModernRenderer
|
|||
VertexLayout = TerrainVertexLayout,
|
||||
Topology = GpuPrimitiveTopology.TriangleList,
|
||||
Blend = GpuBlendMode.None,
|
||||
// WorldDepthContract.WorldCompare — see that type for the
|
||||
// world-space GL_LESS citation this receiver pipeline shares
|
||||
// with terrain's own base pass.
|
||||
Depth = new GpuDepthState(true, true, WorldDepthContract.WorldCompare),
|
||||
Depth = new GpuDepthState(true, true, GpuCompareOp.Less),
|
||||
Cull = GpuCullMode.Back,
|
||||
FrontFace = GpuFrontFace.CounterClockwise,
|
||||
AlphaToCoverage = false,
|
||||
|
|
|
|||
|
|
@ -85,14 +85,12 @@ public sealed unsafe partial class TerrainModernRenderer
|
|||
VertexLayout = TerrainVertexLayout,
|
||||
Topology = GpuPrimitiveTopology.TriangleList,
|
||||
Blend = GpuBlendMode.None,
|
||||
// WorldDepthContract.WorldCompare (GL_LESS), not the contract's
|
||||
// LessOrEqual default: the world frame runs under GL_LESS and
|
||||
// terrain never called glDepthFunc, so it inherited it. LessOrEqual
|
||||
// would change which of two coplanar retail surfaces wins — visible
|
||||
// exactly where terrain meets roads and building footings, which is
|
||||
// what zFightTerrainAdjust is about. See WorldDepthContract for the
|
||||
// full citation.
|
||||
Depth = new GpuDepthState(Test: true, Write: true, WorldDepthContract.WorldCompare),
|
||||
// GL_LESS, not the contract's LessOrEqual default: the world frame
|
||||
// runs under GL_LESS and terrain never called glDepthFunc, so it
|
||||
// inherited it. LessOrEqual would change which of two coplanar retail
|
||||
// surfaces wins — visible exactly where terrain meets roads and
|
||||
// building footings, which is what zFightTerrainAdjust is about.
|
||||
Depth = new GpuDepthState(Test: true, Write: true, GpuCompareOp.Less),
|
||||
// #108-residual: retail terrain is SINGLE-SIDED. See the GL arm's
|
||||
// Draw for the full reasoning; this bakes the same triple.
|
||||
Cull = GpuCullMode.Back,
|
||||
|
|
|
|||
|
|
@ -166,23 +166,6 @@ public sealed class ViewconeCuller
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>ACDREAM_PROBE_WALK_ROOT companion (throwaway): a cell's
|
||||
/// lifted-plane shape for the [dyn-route] admission trace — "none"
|
||||
/// (culls), or per-slice plane counts ("0" = a pass-all zero-plane
|
||||
/// slice, the scissor-fallback trapdoor).</summary>
|
||||
internal string ProbeDescribeCell(uint cellId)
|
||||
{
|
||||
if (!_cellPlanes.TryGetValue(cellId, out PlaneSet? set))
|
||||
return "none";
|
||||
var sb = new System.Text.StringBuilder();
|
||||
for (int s = 0; s < set.Slices.Count; s++)
|
||||
{
|
||||
if (sb.Length > 0) sb.Append('+');
|
||||
sb.Append(set.Slices[s].Count);
|
||||
}
|
||||
return sb.Length == 0 ? "empty" : sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Sphere-vs-the-cell's-views: visible when any slice passes.
|
||||
/// A cell with no views culls (not in the draw list ⇒ never reached in
|
||||
/// retail). A zero-plane slice is pass-all.</summary>
|
||||
|
|
|
|||
|
|
@ -1,163 +0,0 @@
|
|||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW1 — retail's landscape draw-order machinery, ported from the
|
||||
/// named 2013 decomp (model: docs/research/2026-08-30-fw-walk-pseudocode.md §3).
|
||||
///
|
||||
/// Retail enumerates viewer-centered Chebyshev rings with one shared 8-slot
|
||||
/// coefficient pattern at BOTH grid levels: <c>LScape::get_block_order</c>
|
||||
/// @0x00504c50 builds the landblock draw list NEAR-TO-FAR (viewer block at
|
||||
/// index 0; <c>LScape::draw</c> @0x00506330 then walks it BACKWARDS =
|
||||
/// far-to-near), and <c>CLandBlock::calc_sq_draw_order</c> @0x0052f4a0 fills
|
||||
/// the per-block cell array BACKWARDS from the closest cell (forward walk =
|
||||
/// far-to-near). The coefficient tables were byte-decoded from the
|
||||
/// PDB-paired binary (block tables VA 0x0081cc6c..0x0081cd2b, cell tables
|
||||
/// VA 0x0081df88..0x0081e047 — the two sets are IDENTICAL).
|
||||
/// </summary>
|
||||
public static class LandWalkOrder
|
||||
{
|
||||
// The shared 8-slot ring pattern. For ring r, step s (0..r-1), slot k
|
||||
// emits offset (dx, dy) = (XStep[k]*s + XRing[k]*r + XConst[k],
|
||||
// YStep[k]*s + YRing[k]*r + YConst[k]).
|
||||
// Expanded: (-s,+r) (-r,-s) (+s,-r) (+r,+s) (+s+1,+r) (-r,+s+1)
|
||||
// (-s-1,-r) (+r,-s-1) — tiles each ring's 8r slots exactly once.
|
||||
// Byte-decoded 2026-08-30; do not "simplify" the visit order — it decides
|
||||
// draw order between equidistant blocks/cells and the oracle traces pin it.
|
||||
private static readonly int[] XConst = [0, 0, 0, 0, 1, 0, -1, 0];
|
||||
private static readonly int[] XRing = [0, -1, 0, 1, 0, -1, 0, 1];
|
||||
private static readonly int[] XStep = [-1, 0, 1, 0, 1, 0, -1, 0];
|
||||
private static readonly int[] YConst = [0, 0, 0, 0, 0, 1, 0, -1];
|
||||
private static readonly int[] YStep = [0, -1, 0, 1, 0, 1, 0, -1];
|
||||
private static readonly int[] YRing = [1, 0, -1, 0, 1, 0, -1, 0];
|
||||
|
||||
/// <summary>
|
||||
/// <c>LScape::get_block_order</c> @0x00504c50: fills
|
||||
/// <paramref name="order"/> with grid slots (x * width + y) near-to-far —
|
||||
/// the viewer's slot first, then rings outward, skipping out-of-bounds
|
||||
/// slots. Returns the number of slots written (= width² when the viewer
|
||||
/// is inside the grid). The caller draws by walking the result BACKWARDS.
|
||||
/// </summary>
|
||||
public static int GetBlockOrder(int viewerX, int viewerY, int width, Span<int> order)
|
||||
{
|
||||
int count = 0;
|
||||
order[count++] = viewerX * width + viewerY;
|
||||
int maxRing = MaxRingTo(viewerX, viewerY, width, width);
|
||||
for (int ring = 1; ring <= maxRing; ring++)
|
||||
{
|
||||
for (int step = 0; step < ring; step++)
|
||||
{
|
||||
for (int slot = 0; slot < 8; slot++)
|
||||
{
|
||||
int x = XStep[slot] * step + XRing[slot] * ring + XConst[slot] + viewerX;
|
||||
int y = YStep[slot] * step + YRing[slot] * ring + YConst[slot] + viewerY;
|
||||
if (x >= 0 && x < width && y >= 0 && y < width)
|
||||
order[count++] = x * width + y;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>CLandBlock::calc_sq_draw_order</c> @0x0052f4a0 (ring half): fills
|
||||
/// <paramref name="order"/> with cell slots (x * side + y) so that a
|
||||
/// FORWARD walk visits cells far-to-near — the closest cell is written to
|
||||
/// the LAST index and rings fill backwards from there. The array is
|
||||
/// exactly filled (retail's <c>--k</c> reaches 0).
|
||||
/// </summary>
|
||||
public static void FillCellOrderFarToNear(int closestX, int closestY, int side, Span<int> order)
|
||||
{
|
||||
int k = side * side;
|
||||
order[--k] = closestX * side + closestY;
|
||||
int maxRing = MaxRingTo(closestX, closestY, side, side);
|
||||
for (int ring = 1; ring <= maxRing; ring++)
|
||||
{
|
||||
for (int step = 0; step < ring; step++)
|
||||
{
|
||||
for (int slot = 0; slot < 8; slot++)
|
||||
{
|
||||
int x = XStep[slot] * step + XRing[slot] * ring + XConst[slot] + closestX;
|
||||
int y = YStep[slot] * step + YRing[slot] * ring + YConst[slot] + closestY;
|
||||
if (x >= 0 && x < side && y >= 0 && y < side)
|
||||
order[--k] = x * side + y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>LandDefs::get_dir</c> @0x005a9aa0: compass direction of a block at
|
||||
/// grid offset (dx, dy) from the viewer's block. Lcoord y grows northward.
|
||||
/// </summary>
|
||||
public static LandDirection GetDirection(int dx, int dy)
|
||||
{
|
||||
if (dx < 0)
|
||||
{
|
||||
if (dy < 0) return LandDirection.SouthWest;
|
||||
return dy > 0 ? LandDirection.NorthWest : LandDirection.West;
|
||||
}
|
||||
if (dx == 0)
|
||||
{
|
||||
if (dy < 0) return LandDirection.South;
|
||||
return dy > 0 ? LandDirection.North : LandDirection.InViewerBlock;
|
||||
}
|
||||
if (dy < 0) return LandDirection.SouthEast;
|
||||
return dy > 0 ? LandDirection.NorthEast : LandDirection.East;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>CLandBlock::calc_sq_draw_order</c> @0x0052f4a0 (direction switch,
|
||||
/// jump table @0x0052f8e0): the block's cell nearest the viewer, given the
|
||||
/// block's compass direction from the viewer. For the viewer's own block
|
||||
/// the viewer's cell coordinate (cell & 7 per axis) is scaled by
|
||||
/// 8/side; for other blocks the facing edge/corner is closest.
|
||||
/// </summary>
|
||||
public static (int X, int Y) ClosestCell(
|
||||
LandDirection dir, int viewerSqX, int viewerSqY, int side)
|
||||
{
|
||||
int scale = 8 / side;
|
||||
return dir switch
|
||||
{
|
||||
LandDirection.InViewerBlock => (viewerSqX / scale, viewerSqY / scale),
|
||||
LandDirection.North => (viewerSqX / scale, 0),
|
||||
LandDirection.South => (viewerSqX / scale, side - 1),
|
||||
LandDirection.East => (0, viewerSqY / scale),
|
||||
LandDirection.West => (side - 1, viewerSqY / scale),
|
||||
LandDirection.NorthWest => (side - 1, 0),
|
||||
LandDirection.SouthWest => (side - 1, side - 1),
|
||||
LandDirection.NorthEast => (0, 0),
|
||||
LandDirection.SouthEast => (0, side - 1),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(dir)),
|
||||
};
|
||||
}
|
||||
|
||||
// get_block_order's branchy max computation @0x00504ca0-0x00504cba:
|
||||
// the largest Chebyshev distance from (x, y) to any grid corner —
|
||||
// max(x, y, width-1-x, height-1-y, ...) — which guarantees the ring sweep
|
||||
// reaches every in-bounds slot.
|
||||
private static int MaxRingTo(int x, int y, int width, int height)
|
||||
{
|
||||
int max = x;
|
||||
if (y > max) max = y;
|
||||
if (width - 1 - x > max) max = width - 1 - x;
|
||||
if (height - 1 - y > max) max = height - 1 - y;
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>LandDefs::Direction</c> (retail enum, values used raw by the
|
||||
/// calc_sq_draw_order jump table @0x0052f8e0).
|
||||
/// </summary>
|
||||
public enum LandDirection
|
||||
{
|
||||
InViewerBlock = 0,
|
||||
North = 1,
|
||||
South = 2,
|
||||
East = 3,
|
||||
West = 4,
|
||||
NorthWest = 5,
|
||||
SouthWest = 6,
|
||||
NorthEast = 7,
|
||||
SouthEast = 8,
|
||||
}
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW2 — retail's frame phases, in the order the walk
|
||||
/// visits them. <c>RetailPViewPassExecutor</c>'s packed route contract
|
||||
/// (<c>RenderFrameCandidateRoute</c>: LandscapeOutdoorStatic →
|
||||
/// LandscapeBuildingShell → LookInObject → LandscapeOutsideDynamic →
|
||||
/// CellStatic → DynamicLast — <c>WbDrawDispatcher.PackedOracle.cs:108/171</c>
|
||||
/// enforces in-order consumption today) is the closest existing analogue;
|
||||
/// this enum is the walk submitter's OWN phase vocabulary, and a merge run
|
||||
/// built by <see cref="WbDrawDispatcher.BuildOrderedMergeRuns"/> may never
|
||||
/// span two different stages — the submitter treats a stage boundary exactly
|
||||
/// like a material-state boundary (see
|
||||
/// docs/plans/2026-08-30-campaign-fw-frame-walk.md §FW2).
|
||||
/// </summary>
|
||||
internal enum WalkDrawStage : byte
|
||||
{
|
||||
/// <summary><c>LScape::draw</c> @0x00506330 /
|
||||
/// <c>LScape::grab_visible_cells</c> @0x00504EC0 — outdoor terrain.</summary>
|
||||
Terrain,
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2a: the outdoor static objects (scenery,
|
||||
/// buildings-as-decor, landblock statics —
|
||||
/// <c>RenderProjectionClass.OutdoorStatic</c>) <c>LScape::draw</c> visits
|
||||
/// for each landblock alongside its terrain mesh — retail's landscape
|
||||
/// walk draws a landblock's ground and its static content in the same
|
||||
/// pass, distinct from the ground mesh itself (<see cref="Terrain"/>) and
|
||||
/// from a building's own exterior shell (<see cref="BuildingShell"/>).
|
||||
/// The packed route's analogue is
|
||||
/// <c>RenderFrameCandidateRoute.LandscapeOutdoorStatic</c>
|
||||
/// (<c>WbDrawDispatcher.PackedOracle.cs</c>).
|
||||
/// </summary>
|
||||
OutdoorStatic,
|
||||
|
||||
/// <summary>An indoor <c>PView::DrawCells</c> @0x005A4840 flood's static
|
||||
/// geometry: EnvCell shells plus the static meshes they contain.</summary>
|
||||
CellStatic,
|
||||
|
||||
/// <summary><c>DrawBuilding</c>'s (<c>RenderDeviceD3D::DrawBuilding</c>
|
||||
/// @0x0059f2a0) exterior shell pass.</summary>
|
||||
BuildingShell,
|
||||
|
||||
/// <summary>
|
||||
/// <c>DrawPortalPolyInternal</c>'s depth-only invisible portal-polygon
|
||||
/// panel — punch far-Z / seal own-depth. FW0 confirmed retail actually
|
||||
/// draws these every frame (not an acdream invention); the AD-117 stamps
|
||||
/// re-invented the mechanism at the wrong site.
|
||||
///
|
||||
/// <para><b>No FW2 submission path exists yet.</b>
|
||||
/// <see cref="WbDrawDispatcher.SubmitOrderedStream"/> throws
|
||||
/// <see cref="System.NotSupportedException"/> if a command carries this
|
||||
/// stage — punch geometry emission lands in FW3 with the world wiring.
|
||||
/// The stage exists now purely so stage-separation gates can exercise the
|
||||
/// boundary before the real emission path is built.</para>
|
||||
/// </summary>
|
||||
PortalPunch,
|
||||
|
||||
/// <summary><c>ConstructView(CBldPortal)</c> look-in static geometry —
|
||||
/// what an exterior building's window or doorway reveals of its own
|
||||
/// interior.</summary>
|
||||
LookInStatic,
|
||||
|
||||
/// <summary>Every non-static draw the walk visits last: entities,
|
||||
/// monsters, items, and the meshes particles ride.</summary>
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One walk-ordered draw command. The nine fields after <see cref="Key"/> and
|
||||
/// <see cref="Transform"/> are exactly the per-instance data
|
||||
/// <c>WbDrawDispatcher.PrepareDeferredAlphaDraws</c>'s
|
||||
/// <c>DeferredAlphaInstance</c> carries — that method is the per-instance-first
|
||||
/// SSBO-layout template FW2's submitter follows — plus the walk provenance
|
||||
/// (<see cref="Stage"/>, <see cref="CellId"/>) the submitter needs to know
|
||||
/// where a merge run may and may not cross a boundary.
|
||||
/// </summary>
|
||||
/// <param name="Key">Mesh-subset and material identity: index range, texture
|
||||
/// slot/layer, translucency, foliage flags, cull mode. The same
|
||||
/// <see cref="GroupKey"/> the classic material-bucketed path groups instances
|
||||
/// by — FW2 does not bucket by it, only reads its fields per instance.</param>
|
||||
/// <param name="Transform">World transform. Storage binding 0
|
||||
/// (<c>StorageInstances</c>).</param>
|
||||
/// <param name="Stage">The retail frame phase this command belongs to. A
|
||||
/// merge run may never span two different stages.</param>
|
||||
/// <param name="CellId">Walk-order provenance: which cell's traversal emitted
|
||||
/// this command. Not bound to any GPU storage section today — carried for
|
||||
/// FW3's portal-punch wiring and for diagnostics.</param>
|
||||
/// <param name="ClipSlot">Storage binding 3 (<c>StorageClipSlots</c>).</param>
|
||||
/// <param name="Lights">Storage binding 5 (<c>StorageInstanceLightSets</c>).</param>
|
||||
/// <param name="IndoorFlag">Storage binding 6 (<c>StorageInstanceIndoor</c>).</param>
|
||||
/// <param name="Alpha">Storage binding 7 (<c>StorageInstanceAlpha</c>).</param>
|
||||
/// <param name="SelectionLighting">Storage binding 8
|
||||
/// (<c>StorageInstanceSelectionLighting</c>).</param>
|
||||
/// <param name="DetailCategory">Storage binding 9
|
||||
/// (<c>StorageInstanceDetailCategory</c>). A nonzero value forces this
|
||||
/// command into a solo merge run — mirrors the deferred-alpha detail break in
|
||||
/// <c>WbDrawDispatcher.DrawPreparedAlphaBatchRhi</c>.</param>
|
||||
internal readonly record struct OrderedDrawCommand(
|
||||
GroupKey Key,
|
||||
Matrix4x4 Transform,
|
||||
WalkDrawStage Stage,
|
||||
uint CellId,
|
||||
uint ClipSlot,
|
||||
WbDrawDispatcher.InstanceLightSet Lights,
|
||||
uint IndoorFlag,
|
||||
float Alpha,
|
||||
Vector2 SelectionLighting,
|
||||
uint DetailCategory);
|
||||
|
||||
/// <summary>
|
||||
/// Append-only, walk-ordered draw-command stream. Struct-of-arrays storage —
|
||||
/// one parallel list per <see cref="OrderedDrawCommand"/> field, the same
|
||||
/// shape as <see cref="WbDrawDispatcher.InstanceGroup"/>'s per-instance lists
|
||||
/// — so <see cref="WbDrawDispatcher.SubmitOrderedStream"/> can walk the stream
|
||||
/// by index instead of allocating one boxed command per instance.
|
||||
///
|
||||
/// <para>The stream carries no ordering logic of its own: reading it back is
|
||||
/// exactly the sequence <see cref="Append"/> was called in, unconditionally.
|
||||
/// That is the campaign invariant this type exists to make impossible to
|
||||
/// silently regress — see
|
||||
/// docs/plans/2026-08-30-campaign-fw-frame-walk.md §FW2's "a merge across a
|
||||
/// state or stage boundary is forbidden by construction (assert it)" rule.</para>
|
||||
/// </summary>
|
||||
internal sealed class OrderedDrawStream
|
||||
{
|
||||
public readonly List<GroupKey> Keys = new();
|
||||
public readonly List<Matrix4x4> Transforms = new();
|
||||
public readonly List<WalkDrawStage> Stages = new();
|
||||
public readonly List<uint> CellIds = new();
|
||||
public readonly List<uint> ClipSlots = new();
|
||||
public readonly List<WbDrawDispatcher.InstanceLightSet> Lights = new();
|
||||
public readonly List<uint> IndoorFlags = new();
|
||||
public readonly List<float> Alphas = new();
|
||||
public readonly List<Vector2> SelectionLighting = new();
|
||||
public readonly List<uint> DetailCategories = new();
|
||||
|
||||
/// <summary>Number of commands appended since the last <see cref="Reset"/>.</summary>
|
||||
public int Count => Keys.Count;
|
||||
|
||||
public void Append(in OrderedDrawCommand command)
|
||||
{
|
||||
Keys.Add(command.Key);
|
||||
Transforms.Add(command.Transform);
|
||||
Stages.Add(command.Stage);
|
||||
CellIds.Add(command.CellId);
|
||||
ClipSlots.Add(command.ClipSlot);
|
||||
Lights.Add(command.Lights);
|
||||
IndoorFlags.Add(command.IndoorFlag);
|
||||
Alphas.Add(command.Alpha);
|
||||
SelectionLighting.Add(command.SelectionLighting);
|
||||
DetailCategories.Add(command.DetailCategory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears every parallel list together, in one method. The established
|
||||
/// #193 lesson (<c>WbDrawDispatcher.InstanceGroup.ClearPerInstanceData</c>
|
||||
/// carries the same remark): a stream with N parallel lists that resets
|
||||
/// them independently can leave one list stale relative to the others
|
||||
/// after a future field is added and its clear call forgotten — and a
|
||||
/// stale list that only ever grows leaks unboundedly. Ten lists, one
|
||||
/// reset call, so a new eleventh list has nowhere to hide from it.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
Keys.Clear();
|
||||
Transforms.Clear();
|
||||
Stages.Clear();
|
||||
CellIds.Clear();
|
||||
ClipSlots.Clear();
|
||||
Lights.Clear();
|
||||
IndoorFlags.Clear();
|
||||
Alphas.Clear();
|
||||
SelectionLighting.Clear();
|
||||
DetailCategories.Clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,297 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>The composition context: the cell/building halves plus the
|
||||
/// frame-level state retail keeps in globals (the CY near plane from
|
||||
/// <c>Render::update_viewpoint</c>, and the active-view installation the
|
||||
/// building clip consumes).</summary>
|
||||
public interface IRetailFrameWalkContext : IWalkBuildingFrameContext
|
||||
{
|
||||
WalkPlane CyPlane { get; }
|
||||
|
||||
/// <summary><c>Render::set_view</c> at the frame level: install view
|
||||
/// <paramref name="index"/> of <paramref name="views"/> as the active
|
||||
/// clip context for subsequent building-polygon clips.</summary>
|
||||
void SetActiveView(WalkPortalView views, int index);
|
||||
|
||||
float ViewportWidth { get; }
|
||||
float ViewportHeight { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW1 — the frame walk root, composing the ported machinery into
|
||||
/// retail's frame shapes (model:
|
||||
/// docs/research/2026-08-30-fw-walk-pseudocode.md §1, §3-§5; oracle:
|
||||
/// docs/research/2026-08-30-fw-walk-oracle/README.md):
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item>Rooting (<c>SmartBox::RenderNormalMode</c> @0x00453aa0): the
|
||||
/// CAMERA cell's low word < 0x100 → outdoor root (default full-screen
|
||||
/// view + the landscape walk); otherwise → <c>DrawInside</c>.</item>
|
||||
/// <item>Outdoor (<c>LScape::draw</c> @0x00506330): visibility per view,
|
||||
/// blocks far-to-near, per-block cells far-to-near, buildings at their
|
||||
/// cell's turn with the two-pass portal machinery (punches + look-ins on
|
||||
/// the OUTDOOR pview — the traces' <c>pv=009d4b08 ov=0</c>).</item>
|
||||
/// <item>Interior (<c>PView::DrawInside</c> @0x005a5860 +
|
||||
/// <c>DrawCells</c> @0x005a4840): the flood on the INTERIOR pview
|
||||
/// (<c>pv=009d4a80</c>), then the landscape drawn THROUGH the exit views
|
||||
/// when any survive (<c>ov>0</c>).</item>
|
||||
/// </list>
|
||||
///
|
||||
/// Two PView instances exist exactly as the live traces showed.
|
||||
/// </summary>
|
||||
public sealed class RetailFrameWalk
|
||||
{
|
||||
private readonly WalkPView _interiorPView = new();
|
||||
// The outdoor pview never draws the landscape through its look-in
|
||||
// floods (the landscape is already drawn when buildings punch), so its
|
||||
// draw_landscape is FALSE and look-in DCs always read ov=0 — exactly
|
||||
// the pv=…/ov=0 pattern of every look-in in the oracle traces.
|
||||
private readonly WalkPView _outdoorPView = new() { DrawLandscape = false };
|
||||
private readonly WalkPortalView _defaultView = new();
|
||||
|
||||
public WalkPView InteriorPView => _interiorPView;
|
||||
public WalkPView OutdoorPView => _outdoorPView;
|
||||
|
||||
/// <summary>Retail global <c>alwaysDrawObjects</c> (.data default 1
|
||||
/// @0x00820ed4): cell contents draw for every cell of an in-view block
|
||||
/// regardless of per-cell visibility.</summary>
|
||||
public bool AlwaysDrawObjects = true;
|
||||
|
||||
/// <summary>Retail global <c>Render::deg_mul</c> — DYNAMIC: the
|
||||
/// auto-tuner (<c>auto_update_deg_mul</c>) swings it with frame load
|
||||
/// (positive ⇒ degrade thresholds slide toward each level's max;
|
||||
/// negative ⇒ toward its min). The oracle captures pin ≈+0.99 for every
|
||||
/// fixture except doorway-still, whose capture ran under cdb load with
|
||||
/// the multiplier depressed (the recon session's live dump read −0.99
|
||||
/// under the same conditions).</summary>
|
||||
public float DegradeMultiplier = WalkBuilding.DefaultDegradeMultiplier;
|
||||
|
||||
/// <summary>FW4 slice 1: the interior root's <c>outside_view</c> — the
|
||||
/// exit-view polygons THIS walk's own <c>ConstructView</c> installed
|
||||
/// during the most recent <see cref="DrawInside"/>. The production
|
||||
/// renderer materializes the frame's terrain/sky/punch clip slices from
|
||||
/// these views (retail has exactly one visibility structure; the old
|
||||
/// PortalVisibilityBuilder assembly no longer feeds the walk path's
|
||||
/// outside views). Valid after <see cref="WalkFrame"/> returns for an
|
||||
/// interior root, until the next interior root's view push resets it.</summary>
|
||||
internal WalkPortalView InteriorOutsideView => _interiorPView.OutsideView;
|
||||
|
||||
/// <summary>The per-frame root (<c>SmartBox::RenderNormalMode</c>).
|
||||
/// <paramref name="cameraCell"/> may be null only when the camera is
|
||||
/// outdoors.</summary>
|
||||
public void WalkFrame(
|
||||
uint cameraCellId, WalkCell? cameraCell, WalkLandscape landscape,
|
||||
IRetailFrameWalkContext ctx, IWalkEventSink sink)
|
||||
{
|
||||
if ((cameraCellId & 0xFFFF) < 0x100)
|
||||
{
|
||||
// Render::set_default_view @0x0054ef50: the full-screen quad view.
|
||||
_defaultView.ResetForPush();
|
||||
WalkCopyView.AppendFullViewportQuad(
|
||||
_defaultView, ctx.Rays, ctx.WorldViewpoint,
|
||||
ctx.ViewportWidth, ctx.ViewportHeight);
|
||||
DrawLandscape(landscape, _defaultView, ctx, sink);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawInside(
|
||||
cameraCell ?? throw new ArgumentNullException(nameof(cameraCell)),
|
||||
landscape, ctx, sink);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary><c>PView::DrawInside</c> + the event half of
|
||||
/// <c>DrawCells</c>. The geometry/object passes emit no walk events.
|
||||
/// <c>PView::DrawCells</c> @0x005a4840's actual DRAW order for the root
|
||||
/// flood is NOT the order its events fire in: the
|
||||
/// <see cref="WalkEventKind.DrawCells"/> <see cref="IWalkEventSink.Emit"/>
|
||||
/// call below fires at breakpoint-ENTRY order (matching the FW0 oracle
|
||||
/// traces, whose breakpoint sat at <c>DrawCells</c> entry — before
|
||||
/// retail has drawn anything), but retail itself draws
|
||||
/// <c>LScape::draw</c> FIRST (pc:432719, only when exit views survived),
|
||||
/// then a gated full depth clear (pc:432731-432732), then the exit-
|
||||
/// portal seals (pc:432785-432786), and ONLY THEN the flood's own cells
|
||||
/// far-to-near. <see cref="IWalkEventSink.OnInteriorFloodDrawTurn"/>
|
||||
/// fires at that later point (see its own doc comment).</summary>
|
||||
public void DrawInside(
|
||||
WalkCell cell, WalkLandscape landscape,
|
||||
IRetailFrameWalkContext ctx, IWalkEventSink sink)
|
||||
{
|
||||
sink.Emit(WalkEvent.DrawInside(cell.CellId));
|
||||
cell.PushView();
|
||||
AddViews(cell.StabList, ctx);
|
||||
WalkCopyView.AppendFullViewportQuad(
|
||||
cell.TopView, ctx.Rays, ctx.WorldViewpoint,
|
||||
ctx.ViewportWidth, ctx.ViewportHeight);
|
||||
_interiorPView.ConstructView(cell, 0xFFFF, ctx.CellContext);
|
||||
|
||||
uint[] floodCells = EmitDrawCells(_interiorPView, sink);
|
||||
if (_interiorPView.OutsideView.ViewCount > 0)
|
||||
DrawLandscape(landscape, _interiorPView.OutsideView, ctx, sink);
|
||||
|
||||
// Additive (Campaign FW3.2b-1): see this method's own doc comment —
|
||||
// the flood's actual cell-drawing turn, unconditional of whether a
|
||||
// landscape turn just ran (ov==0 skips straight here from the DC
|
||||
// event above).
|
||||
sink.OnInteriorFloodDrawTurn(floodCells);
|
||||
|
||||
RemoveViews(cell.StabList, ctx);
|
||||
cell.PopView();
|
||||
}
|
||||
|
||||
/// <summary><c>LScape::draw</c>: visibility per active view, then blocks
|
||||
/// far-to-near, cells far-to-near, buildings at their cell's turn.</summary>
|
||||
public void DrawLandscape(
|
||||
WalkLandscape landscape, WalkPortalView activeViews,
|
||||
IRetailFrameWalkContext ctx, IWalkEventSink sink)
|
||||
{
|
||||
sink.Emit(WalkEvent.Landscape(activeViews.ViewCount));
|
||||
landscape.CalcDrawOrder();
|
||||
landscape.CheckBlocks(ctx.CyPlane, activeViews);
|
||||
|
||||
for (int i = landscape.BlockDrawCount - 1; i >= 0; i--)
|
||||
{
|
||||
WalkLandBlock? block = landscape.Blocks[landscape.BlockDrawList[i]];
|
||||
if (block is null || block.InView == WalkBoundingType.Outside)
|
||||
continue;
|
||||
int cellCount = block.SideCellCount * block.SideCellCount;
|
||||
for (int k = 0; k < cellCount; k++)
|
||||
{
|
||||
int cellIndex = block.DrawArray[k];
|
||||
// RenderDeviceD3D::DrawBlock @0x005a19d9: DrawSortCell runs
|
||||
// when alwaysDrawObjects != 0 (retail .data default 1
|
||||
// @0x00820ed4) OR the cell IsInView; terrain (DrawLandCell)
|
||||
// emits no walk event.
|
||||
if (!AlwaysDrawObjects
|
||||
&& block.CellInView[cellIndex] == WalkBoundingType.Outside)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// RenderDeviceD3D::DrawSortCell @0x0059f140 (decomp-
|
||||
// confirmed 2026-08-30): DrawBuilding(building) FIRST, then
|
||||
// DrawObjCell(cell) UNCONDITIONALLY — the building's turn
|
||||
// (shell + portal machinery) precedes this cell's own
|
||||
// outdoor-static turn.
|
||||
if (block.CellBuildings[cellIndex] is WalkBuilding building)
|
||||
DrawBuilding(building, activeViews, ctx, sink);
|
||||
sink.OnLandscapeCellTurn((block.LandblockId & 0xFFFF0000u) | (uint)(cellIndex + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary><c>RenderDeviceD3D::DrawBuilding</c> @0x0059f2a0 at the walk
|
||||
/// level: the BLD event fires at ENTRY (before the degrade check — the
|
||||
/// oracle traces' breakpoint sat there, so this stays unconditional for
|
||||
/// conformance). The ENTIRE rest of the body — the alpha barrier, the
|
||||
/// portal pass, and the shell draw — sits inside retail's
|
||||
/// <c>if (part->gfxobj[part->deg_level] != 0)</c> @0x0059f2d3; a
|
||||
/// degraded-out slot draws NOTHING beyond the BLD event.
|
||||
/// <c>HasGeometry</c> + a non-null <c>SelectDrawingBsp</c> together model
|
||||
/// that one gate. Inside the gate, retail's own order
|
||||
/// (@0x0059f30b–0x0059f345) is <c>D3DPolyRender::FlushAlphaList(0f)</c> →
|
||||
/// <c>CPhysicsPart::Draw(parts, 1)</c> (the PORTAL flavor — the two-pass
|
||||
/// punch/look-in walk below) → <c>CPhysicsPart::Draw(parts, 0)</c> (the
|
||||
/// plain mesh — the building's own SHELL) → flag reset: the alpha
|
||||
/// barrier and the portal pass both precede the shell draw, not follow
|
||||
/// it.</summary>
|
||||
public void DrawBuilding(
|
||||
WalkBuilding building, WalkPortalView activeViews,
|
||||
IRetailFrameWalkContext ctx, IWalkEventSink sink)
|
||||
{
|
||||
sink.Emit(WalkEvent.Building(building.PositionCellId));
|
||||
if (!building.HasGeometry) return;
|
||||
// Retail walks the CURRENT degrade level's drawing BSP
|
||||
// (part->gfxobj[deg_level]); a degraded-out slot skips everything
|
||||
// after publishing the portal list.
|
||||
WalkBspNode? bsp = building.SelectDrawingBsp(
|
||||
ctx.ViewerDistanceTo(building),
|
||||
degradeMultiplier: DegradeMultiplier);
|
||||
if (bsp is null) return;
|
||||
|
||||
// Additive (Campaign FW3.2b-1): the alpha barrier
|
||||
// (D3DPolyRender::FlushAlphaList(0f) @0x0059f30b) — gated by the
|
||||
// SAME part->gfxobj[deg_level]!=0 check as everything below it, so
|
||||
// this fires only now that both HasGeometry and the bsp lookup have
|
||||
// passed.
|
||||
sink.OnBuildingTurn(building);
|
||||
|
||||
int viewCount = Math.Max(activeViews.ViewCount, 0);
|
||||
var passSink = new PortalPassSink(building, sink);
|
||||
Vector3 viewpoint = ctx.ViewpointInBuilding(building);
|
||||
for (int v = 0; v < viewCount; v++)
|
||||
{
|
||||
// Retail pins building_view = the CURRENT view for the whole
|
||||
// two-pass walk (DrawMeshInternal @0x0059f3bf) — the punch fans
|
||||
// it emits clip against THAT view, so the sink carries the index.
|
||||
passSink.ActiveViewIndex = v;
|
||||
ctx.SetActiveView(activeViews, v);
|
||||
WalkBuildingPortals.BuildDrawPortalsOnly(
|
||||
bsp, 1, viewpoint,
|
||||
(portalRef, pass) => WalkBuildingPortals.DrawPortal(
|
||||
_outdoorPView, building, portalRef, pass, ctx, passSink));
|
||||
WalkBuildingPortals.BuildDrawPortalsOnly(
|
||||
bsp, 2, viewpoint,
|
||||
(portalRef, pass) => WalkBuildingPortals.DrawPortal(
|
||||
_outdoorPView, building, portalRef, pass, ctx, passSink));
|
||||
}
|
||||
|
||||
// Additive (Campaign FW3.2b-1): CPhysicsPart::Draw(parts, 0)
|
||||
// @0x0059f331 — the building's own shell mesh — runs AFTER the
|
||||
// portal walk completes (CPhysicsPart::Draw(parts, 1) just above),
|
||||
// not before it.
|
||||
sink.OnBuildingShellTurn(building);
|
||||
}
|
||||
|
||||
/// <summary>Returns the extracted cell-id array so <see cref="DrawInside"/>
|
||||
/// can hand the SAME flood list to <c>OnInteriorFloodDrawTurn</c> later,
|
||||
/// without re-walking <c>pview.CellDrawList</c> a second time.</summary>
|
||||
private uint[] EmitDrawCells(WalkPView pview, IWalkEventSink sink)
|
||||
{
|
||||
uint[] cells = new uint[pview.CellDrawList.Count];
|
||||
for (int i = 0; i < cells.Length; i++)
|
||||
cells[i] = pview.CellDrawList[i].CellId;
|
||||
sink.Emit(WalkEvent.DrawCells(pview.OutsideView.ViewCount, cells));
|
||||
return cells;
|
||||
}
|
||||
|
||||
private void AddViews(uint[] stabList, IRetailFrameWalkContext ctx)
|
||||
{
|
||||
foreach (uint id in stabList)
|
||||
ctx.GetVisible(id)?.PushView();
|
||||
}
|
||||
|
||||
private void RemoveViews(uint[] stabList, IRetailFrameWalkContext ctx)
|
||||
{
|
||||
foreach (uint id in stabList)
|
||||
ctx.GetVisible(id)?.PopView();
|
||||
}
|
||||
|
||||
private sealed class PortalPassSink(WalkBuilding building, IWalkEventSink sink)
|
||||
: WalkBuildingPortals.IWalkPortalPassSink
|
||||
{
|
||||
/// <summary>The view the two-pass walk is currently pinned to
|
||||
/// (retail <c>building_view = Render::portal_view_num</c>
|
||||
/// @0x0059f3bf) — punch fans clip against THIS view's slice.</summary>
|
||||
public int ActiveViewIndex;
|
||||
|
||||
public void OnPunch(WalkPolygon polygon)
|
||||
{
|
||||
// The far-Z punch is a depth-only GPU submission (FW2's ordered
|
||||
// stream); the oracle traces do not log it. Additive (Campaign
|
||||
// FW3.2b-1): forward to the richer sink so a driver can flush +
|
||||
// draw the punch fan — building-local space, world transform is
|
||||
// the driver's job (WalkProductionFrameContext-style lookup).
|
||||
sink.OnPunchGeometry(building, polygon, ActiveViewIndex);
|
||||
}
|
||||
|
||||
public void OnDrawCells(WalkPView pview)
|
||||
{
|
||||
uint[] cells = new uint[pview.CellDrawList.Count];
|
||||
for (int i = 0; i < cells.Length; i++)
|
||||
cells[i] = pview.CellDrawList[i].CellId;
|
||||
sink.Emit(WalkEvent.DrawCells(pview.OutsideView.ViewCount, cells));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,330 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>Retail <c>CBldPortal</c>: one building portal — the authored
|
||||
/// side gate, the destination interior cell, its reciprocal portal index,
|
||||
/// and the stab list of cells whose view stacks are pushed around the
|
||||
/// look-in (<c>PView::add_views</c>/<c>remove_views</c>).</summary>
|
||||
public struct WalkBldPortal
|
||||
{
|
||||
public int PortalSide;
|
||||
public uint OtherCellId;
|
||||
public int OtherPortalId;
|
||||
public bool ExactMatch;
|
||||
public uint[] StabList;
|
||||
}
|
||||
|
||||
/// <summary>One <c>CPortalPoly</c> reference on a PORT BSP node: the index
|
||||
/// into the building's portal list plus the portal polygon (building-local).</summary>
|
||||
public struct WalkPortalRef
|
||||
{
|
||||
public int PortalIndex;
|
||||
public WalkPolygon Polygon;
|
||||
}
|
||||
|
||||
/// <summary>A node of the building's drawing BSP as the portal-only walk
|
||||
/// sees it (retail tags: 'FAIL' leaf = stop, 'PORT' = portal node with
|
||||
/// in_portals, anything else = plain splitting node).</summary>
|
||||
public sealed class WalkBspNode
|
||||
{
|
||||
public WalkPlane SplittingPlane;
|
||||
public WalkBspNode? PosNode;
|
||||
public WalkBspNode? NegNode;
|
||||
public bool IsFail;
|
||||
public WalkPortalRef[]? InPortals; // non-null = PORT node
|
||||
|
||||
public bool IsPortal => InPortals is not null;
|
||||
}
|
||||
|
||||
/// <summary>One degrade-ladder level: the drawing BSP of that level's
|
||||
/// GfxObj (portal-only view) and the level's authored distance bands.</summary>
|
||||
public readonly record struct WalkBuildingDegradeLevel(
|
||||
float MinDist, float IdealDist, float MaxDist, WalkBspNode? DrawingBsp);
|
||||
|
||||
/// <summary>The walk's building model (retail <c>CBuildingObj</c> +
|
||||
/// <c>BuildInfo</c> as the frame walk consumes them).</summary>
|
||||
public sealed class WalkBuilding
|
||||
{
|
||||
/// <summary><c>Position.objcell_id</c> — the id the oracle's BLD events
|
||||
/// carry (CBuildingObj+0x4C).</summary>
|
||||
public uint PositionCellId;
|
||||
|
||||
public WalkBldPortal[] Portals = [];
|
||||
|
||||
/// <summary>The drawing BSP of part 0's BASE GfxObj (portal-only view).
|
||||
/// Used directly when the model has no degrade ladder.</summary>
|
||||
public WalkBspNode? DrawingBsp;
|
||||
|
||||
/// <summary>The degrade ladder (near→far). Retail walks the CURRENT
|
||||
/// level's drawing BSP (<c>part->gfxobj[deg_level]</c>) — and building
|
||||
/// degrade models beyond the first band carry NO portal nodes, which is
|
||||
/// what limits look-in punches to nearby buildings (probed 2026-08-30:
|
||||
/// every Holtburg building has ports at level 0 only).</summary>
|
||||
public WalkBuildingDegradeLevel[] DegradeLevels = [];
|
||||
|
||||
/// <summary><c>part->gfxobj[deg_level] != 0</c> — a degraded-out slot
|
||||
/// skips the whole building AFTER publishing the portal list.</summary>
|
||||
public bool HasGeometry = true;
|
||||
|
||||
/// <summary>The base GfxObj's sort center — retail measures the viewer
|
||||
/// distance to the part's SCALED sort center, not the position origin
|
||||
/// (<c>CPhysicsPart::UpdateViewerDistance</c> @0x0050e030).</summary>
|
||||
public Vector3 SortCenter;
|
||||
|
||||
/// <summary>Retail <c>Render::s_rDegradeDistance</c> — a subtractive
|
||||
/// slack before the ladder applies (live-dumped 100 on the capture
|
||||
/// client; registry-configurable).</summary>
|
||||
public const float DefaultDegradeDistance = 100f;
|
||||
|
||||
/// <summary>The live capture client's <c>Render::deg_mul</c> arm:
|
||||
/// magnitude 0.99 pinned by the walkout-F2 fixture (building a9b4001e,
|
||||
/// effective 30.3, level 0 ideal/max 24/48 — retail floods, so its
|
||||
/// threshold ≈ max ⇒ the POSITIVE arm; the recon note's "−0.99" sign was
|
||||
/// a misread — the negative arm's threshold ≈ min contradicts the
|
||||
/// fixture from both directions). Re-dump at the next retail session.</summary>
|
||||
public const float DefaultDegradeMultiplier = 0.99f;
|
||||
|
||||
/// <summary>
|
||||
/// <c>GfxObjDegradeInfo::get_degrade</c> @0x0051e4b0 (BN flag-mush on
|
||||
/// the FPU compares — cross-checked against the Ghidra decomp, which is
|
||||
/// clean): effective = max(0, |distance| − degradeDistance), then the
|
||||
/// FIRST level whose threshold exceeds effective wins; no match → the
|
||||
/// LAST level. The threshold depends on the multiplier's sign:
|
||||
/// mul ≥ 0 → <c>ideal − (ideal − max)·mul</c> (ideal→max as mul→1);
|
||||
/// mul < 0 → <c>ideal + (ideal − min)·mul</c> (ideal→min as mul→−1).
|
||||
/// mul is <c>Render::deg_mul</c> when <c>auto_update_deg_mul</c> is on,
|
||||
/// else <c>s_rUserSuppliedDegradeBias</c>.
|
||||
/// </summary>
|
||||
public WalkBspNode? SelectDrawingBsp(
|
||||
float viewerDistance,
|
||||
float degradeDistance = DefaultDegradeDistance,
|
||||
float degradeMultiplier = DefaultDegradeMultiplier)
|
||||
{
|
||||
if (DegradeLevels.Length == 0) return DrawingBsp;
|
||||
float effective = MathF.Max(0f, MathF.Abs(viewerDistance) - degradeDistance);
|
||||
foreach (WalkBuildingDegradeLevel level in DegradeLevels)
|
||||
{
|
||||
float threshold = degradeMultiplier >= 0f
|
||||
? level.IdealDist - (level.IdealDist - level.MaxDist) * degradeMultiplier
|
||||
: level.IdealDist + (level.IdealDist - level.MinDist) * degradeMultiplier;
|
||||
if (effective < threshold)
|
||||
return level.DrawingBsp;
|
||||
}
|
||||
return DegradeLevels[^1].DrawingBsp;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW1 — the building look-in machinery, ported from the first
|
||||
/// decomp appendix report 3 (docs/research/2026-08-30-fw-walk-pseudocode-appendix.md):
|
||||
/// <c>BSPTREE/BSPNODE::build_draw_portals_only</c> @0x00539860/@0x0053c100,
|
||||
/// <c>BSPPORTAL::portal_draw_portals_only</c> @0x0053d870,
|
||||
/// <c>PView::DrawPortal</c> @0x005a5ab0, and the CBldPortal
|
||||
/// <c>PView::ConstructView</c> overload @0x005a59a0.
|
||||
///
|
||||
/// The invisible-panel primitive (<c>DrawPortalPolyInternal</c> @0x0059bc90
|
||||
/// — punch far-Z / seal own-depth) is a GPU submission and belongs to FW2's
|
||||
/// ordered stream; here it surfaces as the <see cref="IWalkPortalPassSink"/>
|
||||
/// punch callback so FW1 conformance can observe when retail would draw it.
|
||||
/// </summary>
|
||||
public static class WalkBuildingPortals
|
||||
{
|
||||
/// <summary>What the portal passes report outward: punches (pass 1) and
|
||||
/// look-in cell floods (pass 2, the oracle's <c>DC ov=…</c> events).</summary>
|
||||
public interface IWalkPortalPassSink
|
||||
{
|
||||
/// <summary>Pass 1 drew the portal polygon as a far-Z punch
|
||||
/// (<c>DrawPortalPolyInternal(poly, 1)</c>).</summary>
|
||||
void OnPunch(WalkPolygon polygon);
|
||||
|
||||
/// <summary>Pass 2 completed a look-in flood and retail called
|
||||
/// <c>PView::DrawCells</c> — the DC event with the flood's list.</summary>
|
||||
void OnDrawCells(WalkPView pview);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>BSPTREE::build_draw_portals_only</c> @0x00539860 + the node/portal
|
||||
/// walkers (<c>BSPNODE</c> @0x0053c100, <c>BSPPORTAL</c> @0x0053d870;
|
||||
/// side arms verified against the GHIDRA decomp 2026-08-30 — BN's
|
||||
/// FPU-flag pseudo-C reads the negative/in-plane split ambiguously, and
|
||||
/// a "corrected" swap of these arms broke four fixtures before being
|
||||
/// falsified; Ghidra: <c>d ≤ ε → side=1 unless −ε ≤ d → side=2</c>):
|
||||
/// dispatch the root, then walk plane-side ordered — the child OPPOSITE
|
||||
/// the viewer first, so portals emit far-to-near. PORT nodes emit every
|
||||
/// in_portal on the POSITIVE (d > ε) and NEGATIVE (d < −ε) arms;
|
||||
/// the IN_PLANE arm (|d| ≤ ε) visits the positive child and emits
|
||||
/// NOTHING. Plain nodes group IN_PLANE with NEGATIVE.
|
||||
/// </summary>
|
||||
public static void BuildDrawPortalsOnly(
|
||||
WalkBspNode? root, int pass, Vector3 viewpointInBuilding,
|
||||
Action<WalkPortalRef, int> emitPortal)
|
||||
{
|
||||
if (root is null || root.IsFail) return;
|
||||
Walk(root, pass, viewpointInBuilding, emitPortal);
|
||||
}
|
||||
|
||||
private static void Walk(
|
||||
WalkBspNode node, int pass, Vector3 viewpoint,
|
||||
Action<WalkPortalRef, int> emitPortal)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
float d = Vector3.Dot(node.SplittingPlane.Normal, viewpoint) + node.SplittingPlane.D;
|
||||
int side = d > WalkVisibilityMath.Epsilon ? 0
|
||||
: d < -WalkVisibilityMath.Epsilon ? 1 : 2;
|
||||
|
||||
WalkBspNode? next;
|
||||
if (node.IsPortal)
|
||||
{
|
||||
if (side == 0)
|
||||
{
|
||||
Visit(node.NegNode, pass, viewpoint, emitPortal);
|
||||
foreach (WalkPortalRef portal in node.InPortals!)
|
||||
emitPortal(portal, pass);
|
||||
next = node.PosNode;
|
||||
}
|
||||
else if (side == 1)
|
||||
{
|
||||
Visit(node.PosNode, pass, viewpoint, emitPortal);
|
||||
foreach (WalkPortalRef portal in node.InPortals!)
|
||||
emitPortal(portal, pass);
|
||||
next = node.NegNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
Visit(node.PosNode, pass, viewpoint, emitPortal);
|
||||
next = node.NegNode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (side == 0)
|
||||
{
|
||||
Visit(node.NegNode, pass, viewpoint, emitPortal);
|
||||
next = node.PosNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
Visit(node.PosNode, pass, viewpoint, emitPortal);
|
||||
next = node.NegNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (next is null || next.IsFail) return;
|
||||
node = next; // retail's tail-continue
|
||||
}
|
||||
}
|
||||
|
||||
private static void Visit(
|
||||
WalkBspNode? child, int pass, Vector3 viewpoint,
|
||||
Action<WalkPortalRef, int> emitPortal)
|
||||
{
|
||||
if (child is null || child.IsFail) return;
|
||||
Walk(child, pass, viewpoint, emitPortal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>PView::DrawPortal</c> @0x005a5ab0 for one emitted portal polygon:
|
||||
/// resolve the CBldPortal, push view slots on its stab cells
|
||||
/// (<c>add_views</c>), run the CBldPortal ConstructView, on pass 2
|
||||
/// success run the look-in DrawCells, then pop the stab views. The
|
||||
/// GPU-state backup/restore and the building-frame re-push are
|
||||
/// submission concerns (FW2); the CPU state here is complete.
|
||||
/// </summary>
|
||||
public static bool DrawPortal(
|
||||
WalkPView pview, WalkBuilding building, in WalkPortalRef portalRef,
|
||||
int pass, IWalkBuildingFrameContext ctx, IWalkPortalPassSink sink)
|
||||
{
|
||||
ref readonly WalkBldPortal bldPortal = ref building.Portals[portalRef.PortalIndex];
|
||||
AddViews(bldPortal.StabList, ctx);
|
||||
bool ok = ConstructBuildingView(
|
||||
pview, building, in bldPortal, portalRef.Polygon, pass, ctx, sink);
|
||||
if (ok && pass != 1)
|
||||
sink.OnDrawCells(pview);
|
||||
RemoveViews(bldPortal.StabList, ctx);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CBldPortal <c>PView::ConstructView</c> overload @0x005a59a0:
|
||||
/// the viewer's side of the portal plane must EQUAL the authored
|
||||
/// portal_side (IN_PLANE within ±ε fails both gates); the polygon must
|
||||
/// survive the active-view clip with ≥3 points; the destination cell
|
||||
/// must be Visible; the clipped view is appended to its top slot. Pass 1
|
||||
/// punches the polygon; pass ≠ 1 recurses into the interior flood.
|
||||
/// </summary>
|
||||
public static bool ConstructBuildingView(
|
||||
WalkPView pview, WalkBuilding building, in WalkBldPortal bldPortal,
|
||||
WalkPolygon polygon, int pass, IWalkBuildingFrameContext ctx,
|
||||
IWalkPortalPassSink sink)
|
||||
{
|
||||
Vector3 viewpoint = ctx.ViewpointInBuilding(building);
|
||||
float d = Vector3.Dot(polygon.Plane.Normal, viewpoint) + polygon.Plane.D;
|
||||
int side = d > WalkVisibilityMath.Epsilon ? 0
|
||||
: d < -WalkVisibilityMath.Epsilon ? 1 : 2;
|
||||
if (bldPortal.PortalSide != 0)
|
||||
{
|
||||
if (side != 1) return false;
|
||||
}
|
||||
else if (side != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Span<WalkScreenPoint> clipped = stackalloc WalkScreenPoint[64];
|
||||
int n = ctx.ClipBuildingPolygon(building, polygon, side, clipped);
|
||||
if (n == 0) return false;
|
||||
|
||||
WalkCell? cell = ctx.GetVisible(bldPortal.OtherCellId);
|
||||
if (cell is null) return false;
|
||||
if (!WalkCopyView.Append(
|
||||
cell.TopView, clipped[..n], ctx.Rays, ctx.WorldViewpoint))
|
||||
return false;
|
||||
|
||||
if (pass != 2)
|
||||
sink.OnPunch(polygon); // DrawPortalPolyInternal(poly, pass == 1)
|
||||
if (pass != 1)
|
||||
pview.ConstructView(cell, ToEntryIndex(bldPortal.OtherPortalId), ctx.CellContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int ToEntryIndex(int otherPortalId)
|
||||
=> otherPortalId < 0 ? 0xFFFF : otherPortalId;
|
||||
|
||||
private static void AddViews(uint[] stabList, IWalkBuildingFrameContext ctx)
|
||||
{
|
||||
foreach (uint id in stabList)
|
||||
ctx.GetVisible(id)?.PushView();
|
||||
}
|
||||
|
||||
private static void RemoveViews(uint[] stabList, IWalkBuildingFrameContext ctx)
|
||||
{
|
||||
foreach (uint id in stabList)
|
||||
ctx.GetVisible(id)?.PopView();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The building half of the frame context: building-local
|
||||
/// viewpoint and projection (retail pushes the building's object frame
|
||||
/// before the portal pass), plus the shared cell context.</summary>
|
||||
public interface IWalkBuildingFrameContext
|
||||
{
|
||||
Vector3 ViewpointInBuilding(WalkBuilding building);
|
||||
|
||||
/// <summary>The viewer's distance to the building
|
||||
/// (<c>CPhysicsPart::UpdateViewerDistance</c>'s input) — selects the
|
||||
/// degrade level whose drawing BSP the portal pass walks.</summary>
|
||||
float ViewerDistanceTo(WalkBuilding building);
|
||||
|
||||
/// <summary>Project + clip one building-local portal polygon against
|
||||
/// the ACTIVE view (retail: GetClip with do_clip=1 in the building
|
||||
/// frame). Returns the surviving count.</summary>
|
||||
int ClipBuildingPolygon(
|
||||
WalkBuilding building, WalkPolygon polygon, int side, Span<WalkScreenPoint> output);
|
||||
|
||||
WalkCell? GetVisible(uint cellId);
|
||||
IWalkRayCaster Rays { get; }
|
||||
Vector3 WorldViewpoint { get; }
|
||||
IWalkFrameContext CellContext { get; }
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Content;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.1 — production <see cref="WalkBuilding"/> construction,
|
||||
/// ported from the FW1 conformance harness
|
||||
/// (tests/AcDream.App.Tests/Rendering/Walk/WalkWorldDatAdapter.cs::BuildBuildings
|
||||
/// /ConvertDrawingBsp/BuildGfxPolygon) to the legal
|
||||
/// <see cref="IDatReaderWriter"/> seam. This closes the FW3.1 gap: today's
|
||||
/// <c>Wb.BuildingLoader.AddBuilding</c> (update thread) reads
|
||||
/// <c>BuildingInfo.Portals</c> only for BFS seeding and drops ModelId,
|
||||
/// Frame, portal Flags/StabList, and the drawing BSP the frame walk needs —
|
||||
/// this factory reads the SAME <c>BuildingInfo</c> plus the GfxObj/
|
||||
/// GfxObjDegradeInfo chain, on the worker thread inside
|
||||
/// <c>LandblockBuildFactory</c> where <see cref="IDatReaderWriter"/> access
|
||||
/// is legal (<c>BuildingLoader</c> itself never touches DATs — it runs
|
||||
/// update-thread, after the streaming build already completed).
|
||||
///
|
||||
/// Adjudication toggles the FW1 harness carried as public mutable fields for
|
||||
/// sweep testing are PINNED here to their winning arms (2026-08-30 data-pin,
|
||||
/// verified against the ten oracle traces):
|
||||
/// <list type="bullet">
|
||||
/// <item>GfxObj portal-polygon planes are NOT flipped (the plain
|
||||
/// first-three-vertices cross survives every building-portal fixture).</item>
|
||||
/// <item>Building portal_side is the INVERSE of the cell decode's 0x2 bit
|
||||
/// (<c>WalkWorldDatAdapter.BuildingSideMode == 1</c>'s winning arm).</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static class WalkBuildingFactory
|
||||
{
|
||||
/// <summary>One built <see cref="WalkBuilding"/> plus its landblock-local
|
||||
/// placement transform (<paramref name="WorldTransform"/> already has the
|
||||
/// caller's block offset baked in — see <see cref="Build"/>).</summary>
|
||||
public sealed record Entry(
|
||||
WalkBuilding Building, Matrix4x4 WorldTransform, Matrix4x4 InverseWorldTransform);
|
||||
|
||||
/// <summary>Builds every building of one landblock. <paramref name="lbOffset"/>
|
||||
/// is the SAME landblock-local world offset every other production
|
||||
/// builder in this streaming job uses (<c>LandblockBuildFactory</c>'s
|
||||
/// <c>lbOffset</c>/<c>worldOffset</c> convention) — baked directly into
|
||||
/// each building's world transform, which is mathematically identical to
|
||||
/// the FW1 harness's two-step compose (build landblock-local, then
|
||||
/// post-multiply the block offset translation): translations commute,
|
||||
/// so <c>R·T(origin)·T(lbOffset) == R·T(origin+lbOffset)</c>.</summary>
|
||||
public static List<Entry> Build(
|
||||
IDatReaderWriter dats, uint landblockId,
|
||||
IReadOnlyList<BuildingInfo>? buildingInfos, Vector3 lbOffset)
|
||||
{
|
||||
var result = new List<Entry>();
|
||||
if (buildingInfos is null)
|
||||
return result;
|
||||
uint lbMask = landblockId & 0xFFFF0000u;
|
||||
|
||||
foreach (BuildingInfo buildingInfo in buildingInfos)
|
||||
{
|
||||
Vector3 origin = new(
|
||||
buildingInfo.Frame.Origin.X,
|
||||
buildingInfo.Frame.Origin.Y,
|
||||
buildingInfo.Frame.Origin.Z);
|
||||
int cellX = (int)MathF.Floor(origin.X / 24f);
|
||||
int cellY = (int)MathF.Floor(origin.Y / 24f);
|
||||
uint positionCellId = lbMask | (uint)(cellX * 8 + cellY + 1);
|
||||
|
||||
var portals = new WalkBldPortal[buildingInfo.Portals.Count];
|
||||
for (int i = 0; i < portals.Length; i++)
|
||||
{
|
||||
BuildingPortal portal = buildingInfo.Portals[i];
|
||||
portals[i] = new WalkBldPortal
|
||||
{
|
||||
PortalSide = DecodeBuildingSide((ushort)portal.Flags),
|
||||
ExactMatch = ((ushort)portal.Flags & 0x1) != 0,
|
||||
OtherCellId = portal.OtherCellId == 0xFFFF
|
||||
? 0xFFFFFFFFu
|
||||
: lbMask | portal.OtherCellId,
|
||||
OtherPortalId = unchecked((short)portal.OtherPortalId),
|
||||
StabList = portal.StabList.Select(s => lbMask | s).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
WalkBspNode? bsp = null;
|
||||
Vector3 sortCenter = Vector3.Zero;
|
||||
var degradeLevels = new List<WalkBuildingDegradeLevel>();
|
||||
if (dats.Get<GfxObj>(buildingInfo.ModelId) is GfxObj gfxObj)
|
||||
{
|
||||
bsp = ConvertDrawingBsp(gfxObj, gfxObj.DrawingBSP?.Root);
|
||||
sortCenter = new Vector3(gfxObj.SortCenter.X, gfxObj.SortCenter.Y, gfxObj.SortCenter.Z);
|
||||
if (gfxObj.DIDDegrade != 0
|
||||
&& dats.Get<GfxObjDegradeInfo>(gfxObj.DIDDegrade)
|
||||
is GfxObjDegradeInfo degradeInfo)
|
||||
{
|
||||
foreach (GfxObjInfo level in degradeInfo.Degrades)
|
||||
{
|
||||
WalkBspNode? levelBsp = null;
|
||||
if (level.Id != 0
|
||||
&& dats.Get<GfxObj>((uint)level.Id) is GfxObj levelGfx)
|
||||
{
|
||||
levelBsp = ConvertDrawingBsp(levelGfx, levelGfx.DrawingBSP?.Root);
|
||||
}
|
||||
degradeLevels.Add(
|
||||
new WalkBuildingDegradeLevel(level.MinDist, level.IdealDist, level.MaxDist, levelBsp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Matrix4x4 worldTransform =
|
||||
Matrix4x4.CreateFromQuaternion(buildingInfo.Frame.Orientation)
|
||||
* Matrix4x4.CreateTranslation(origin + lbOffset);
|
||||
Matrix4x4.Invert(worldTransform, out Matrix4x4 inverse);
|
||||
result.Add(new Entry(
|
||||
new WalkBuilding
|
||||
{
|
||||
PositionCellId = positionCellId,
|
||||
Portals = portals,
|
||||
DrawingBsp = bsp,
|
||||
DegradeLevels = degradeLevels.ToArray(),
|
||||
SortCenter = sortCenter,
|
||||
},
|
||||
worldTransform,
|
||||
inverse));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int DecodeBuildingSide(ushort flags) => (flags & 0x2) != 0 ? 0 : 1;
|
||||
|
||||
private static WalkBspNode? ConvertDrawingBsp(GfxObj gfxObj, DrawingBSPNode? node)
|
||||
{
|
||||
if (node is null) return null;
|
||||
var converted = new WalkBspNode
|
||||
{
|
||||
SplittingPlane = new WalkPlane(node.SplittingPlane.Normal, node.SplittingPlane.D),
|
||||
IsFail = node.Type == BSPNodeType.Leaf,
|
||||
PosNode = ConvertDrawingBsp(gfxObj, node.PosNode),
|
||||
NegNode = ConvertDrawingBsp(gfxObj, node.NegNode),
|
||||
};
|
||||
if (node.Type == BSPNodeType.Portal && node.Portals is not null)
|
||||
{
|
||||
var refs = new List<WalkPortalRef>(node.Portals.Count);
|
||||
foreach (PortalRef portalRef in node.Portals)
|
||||
{
|
||||
WalkPolygon? polygon = BuildGfxPolygon(gfxObj, portalRef.PolyId);
|
||||
if (polygon is not null)
|
||||
refs.Add(new WalkPortalRef
|
||||
{
|
||||
PortalIndex = portalRef.PortalIndex,
|
||||
Polygon = polygon,
|
||||
});
|
||||
}
|
||||
converted.InPortals = refs.ToArray();
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
private static WalkPolygon? BuildGfxPolygon(GfxObj gfxObj, ushort polygonId)
|
||||
{
|
||||
if (!gfxObj.Polygons.TryGetValue(polygonId, out Polygon? poly)
|
||||
|| poly is null || poly.VertexIds.Count < 3)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return BuildPolygonFromVertices(
|
||||
poly.VertexIds,
|
||||
id => gfxObj.VertexArray.Vertices.TryGetValue((ushort)id, out SWVertex? v)
|
||||
? new Vector3(v.Origin.X, v.Origin.Y, v.Origin.Z)
|
||||
: null);
|
||||
}
|
||||
|
||||
private static WalkPolygon? BuildPolygonFromVertices(
|
||||
IReadOnlyList<short> vertexIds, Func<short, Vector3?> resolve)
|
||||
{
|
||||
var vertices = new Vector3[vertexIds.Count];
|
||||
for (int i = 0; i < vertexIds.Count; i++)
|
||||
{
|
||||
Vector3? v = resolve(vertexIds[i]);
|
||||
if (v is null) return null;
|
||||
vertices[i] = v.Value;
|
||||
}
|
||||
Vector3 normal = Vector3.Normalize(
|
||||
Vector3.Cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
return new WalkPolygon
|
||||
{
|
||||
Vertices = vertices,
|
||||
Plane = new WalkPlane(normal, -Vector3.Dot(normal, vertices[0])),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.1 — the walk's per-landblock <see cref="WalkBuilding"/>
|
||||
/// registry: the production sibling of
|
||||
/// <see cref="AcDream.App.Rendering.Wb.BuildingRegistry"/>. Publish/retire
|
||||
/// mirror that registry's landblock lifecycle exactly — both are committed
|
||||
/// together in <c>LandblockRenderPublisher.AdvanceCompleteOne</c>'s
|
||||
/// <c>BuildingRegistryCommitted</c> step and retired together from
|
||||
/// <c>LandblockRenderPublisher.RemoveBuildingRegistry</c> — because a walk
|
||||
/// building and its BFS-derived <c>Wb.Building</c> counterpart come from the
|
||||
/// SAME <c>BuildingInfo</c> array at the SAME landblock commit.
|
||||
///
|
||||
/// The reverse index (<see cref="TryGetEntry"/>) exists because
|
||||
/// <see cref="WalkProductionFrameContext"/> resolves a building's placement
|
||||
/// by REFERENCE during the portal pass — <c>ViewpointInBuilding</c>,
|
||||
/// <c>ViewerDistanceTo</c>, and <c>ClipBuildingPolygon</c> are called many
|
||||
/// times per building per frame once FW3.2 wires the walk into the render
|
||||
/// loop, so this must be O(1), not a per-landblock scan.
|
||||
/// </summary>
|
||||
public sealed class WalkBuildingRegistry
|
||||
{
|
||||
private readonly Dictionary<uint, IReadOnlyList<WalkBuildingFactory.Entry>> _byLandblock = new();
|
||||
private readonly Dictionary<WalkBuilding, WalkBuildingFactory.Entry> _byBuilding = new();
|
||||
|
||||
/// <summary>Atomically replaces one landblock's complete building set.
|
||||
/// Mirrors <c>Wb.BuildingRegistry</c>'s "no partial landblock" commit
|
||||
/// discipline — <paramref name="entries"/> is the immutable, fully-built
|
||||
/// list carried by the streaming worker's
|
||||
/// <c>Wb.EnvCellLandblockBuild.WalkBuildings</c>.</summary>
|
||||
public void Publish(uint landblockId, IReadOnlyList<WalkBuildingFactory.Entry> entries)
|
||||
{
|
||||
uint key = landblockId & 0xFFFF0000u;
|
||||
if (_byLandblock.TryGetValue(key, out IReadOnlyList<WalkBuildingFactory.Entry>? previous))
|
||||
{
|
||||
foreach (WalkBuildingFactory.Entry entry in previous)
|
||||
_byBuilding.Remove(entry.Building);
|
||||
}
|
||||
_byLandblock[key] = entries;
|
||||
foreach (WalkBuildingFactory.Entry entry in entries)
|
||||
_byBuilding[entry.Building] = entry;
|
||||
}
|
||||
|
||||
/// <summary>Removes every building of one landblock. Safe to call on a
|
||||
/// landblock that never published (no-op).</summary>
|
||||
public void Retire(uint landblockId)
|
||||
{
|
||||
uint key = landblockId & 0xFFFF0000u;
|
||||
if (_byLandblock.Remove(key, out IReadOnlyList<WalkBuildingFactory.Entry>? previous))
|
||||
{
|
||||
foreach (WalkBuildingFactory.Entry entry in previous)
|
||||
_byBuilding.Remove(entry.Building);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The buildings of one landblock, or empty when none are
|
||||
/// published (unloaded, far-tier, or no <c>BuildingInfo</c> entries).</summary>
|
||||
public IReadOnlyList<WalkBuildingFactory.Entry> GetBuildings(uint landblockId) =>
|
||||
_byLandblock.TryGetValue(landblockId & 0xFFFF0000u, out IReadOnlyList<WalkBuildingFactory.Entry>? list)
|
||||
? list
|
||||
: Array.Empty<WalkBuildingFactory.Entry>();
|
||||
|
||||
/// <summary>O(1) reverse lookup: this building's committed placement.
|
||||
/// False means the building is not (or no longer) committed — the walk
|
||||
/// must treat that as a hard desync, never a silent skip (the FW
|
||||
/// fail-loud rule).</summary>
|
||||
public bool TryGetEntry(
|
||||
WalkBuilding building, [MaybeNullWhen(false)] out WalkBuildingFactory.Entry entry) =>
|
||||
_byBuilding.TryGetValue(building, out entry);
|
||||
|
||||
/// <summary>Number of landblocks with committed buildings (diagnostics).</summary>
|
||||
public int LandblockCount => _byLandblock.Count;
|
||||
}
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Content;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
using Environment = DatReaderWriter.DBObjs.Environment;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.1 — production <see cref="WalkCell"/> construction, ported
|
||||
/// from the FW1 conformance harness
|
||||
/// (tests/AcDream.App.Tests/Rendering/Walk/WalkWorldDatAdapter.cs::BuildCell)
|
||||
/// to the legal <see cref="IDatReaderWriter"/> seam
|
||||
/// (<c>RuntimeDatAccessArchitectureTests</c> forbids raw
|
||||
/// <c>DatCollection</c> anywhere in this assembly outside
|
||||
/// <c>RuntimeDatCollectionFactory</c>/<c>DatCollectionAdapter</c>).
|
||||
///
|
||||
/// <see cref="FromParsed"/> is the shared core: it takes an ALREADY-FETCHED
|
||||
/// <see cref="EnvCell"/>/<see cref="CellStruct"/> pair and the production
|
||||
/// UNLIFTED cell transform. <c>EnvCellLandblockBuildBuilder.BuildVisibilityCell</c>
|
||||
/// (src/AcDream.App/Rendering/Wb/EnvCellLandblockBuild.cs) calls this
|
||||
/// directly — it already has both parsed and the transform in hand from its
|
||||
/// own <see cref="LoadedCell"/> construction, so no extra DAT read is spent.
|
||||
/// <see cref="BuildCell"/> is the standalone DAT-reading convenience for
|
||||
/// callers that only have a cell id (the walk landscape's stab-list
|
||||
/// look-in cells, and the FW3 conformance harness).
|
||||
///
|
||||
/// Conventions (fixture-pinned; the ten oracle traces are the referee — see
|
||||
/// <c>docs/research/2026-08-30-fw-walk-oracle/</c>):
|
||||
/// <list type="bullet">
|
||||
/// <item>portal_side = <c>(Flags & 0x2) != 0 ? 0 : 1</c> — the INVERSE of
|
||||
/// the bit, matching <c>EnvCellLandblockBuildBuilder.BuildVisibilityCell</c>'s
|
||||
/// existing <c>PortalClipPlane.InsideSide</c> decode
|
||||
/// (<c>(Flags & 0x2) == 0 ? 1 : 0</c> — the same formula).</item>
|
||||
/// <item>Exit-portal sentinel widening: dat <c>0xFFFF</c> →
|
||||
/// <c>0xFFFFFFFF</c> (<see cref="WalkCellPortal.OtherCellId"/> is a full
|
||||
/// cell id; the dat field is a 16-bit local id).</item>
|
||||
/// <item>Portal-polygon plane: first-three-vertices cross,
|
||||
/// <c>d = −dot(N, p0)</c> — the same formula
|
||||
/// <c>BuildVisibilityCell</c>'s <c>PortalClipPlane</c> already uses.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static class WalkCellFactory
|
||||
{
|
||||
/// <summary>DAT-reading entry point: fetches EnvCell/Environment/CellStruct
|
||||
/// and builds their landblock-local (<paramref name="blockOffset"/>
|
||||
/// added) UNLIFTED transform before delegating to <see cref="FromParsed"/>.
|
||||
/// Returns null when the cell, its environment, or its cell structure
|
||||
/// cannot be resolved (unregistered/degenerate cell — same as the FW1
|
||||
/// test adapter).</summary>
|
||||
public static WalkCell? BuildCell(IDatReaderWriter dats, uint cellId, Vector3 blockOffset)
|
||||
{
|
||||
if (dats.Get<EnvCell>(cellId) is not EnvCell envCell)
|
||||
return null;
|
||||
if (dats.Get<Environment>(0x0D000000u | envCell.EnvironmentId) is not Environment environment
|
||||
|| !environment.Cells.TryGetValue(envCell.CellStructure, out CellStruct? cellStruct)
|
||||
|| cellStruct is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Matrix4x4 worldTransform =
|
||||
Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation)
|
||||
* Matrix4x4.CreateTranslation(
|
||||
envCell.Position.Origin.X + blockOffset.X,
|
||||
envCell.Position.Origin.Y + blockOffset.Y,
|
||||
envCell.Position.Origin.Z + blockOffset.Z);
|
||||
Matrix4x4.Invert(worldTransform, out Matrix4x4 inverse);
|
||||
return FromParsed(cellId, envCell, cellStruct, worldTransform, inverse);
|
||||
}
|
||||
|
||||
/// <summary>Builds a <see cref="WalkCell"/> from an already-fetched
|
||||
/// EnvCell/CellStruct pair and the production cell transform. No DAT
|
||||
/// access — safe to call from a context that already holds the reader
|
||||
/// lock for an unrelated read.</summary>
|
||||
public static WalkCell FromParsed(
|
||||
uint cellId, EnvCell envCell, CellStruct cellStruct,
|
||||
Matrix4x4 worldTransform, Matrix4x4 inverseWorldTransform)
|
||||
{
|
||||
uint lbMask = cellId & 0xFFFF0000u;
|
||||
int portalCount = envCell.CellPortals.Count;
|
||||
var portals = new WalkCellPortal[portalCount];
|
||||
var polygons = new WalkPolygon[portalCount];
|
||||
for (int i = 0; i < portalCount; i++)
|
||||
{
|
||||
CellPortal portal = envCell.CellPortals[i];
|
||||
portals[i] = new WalkCellPortal
|
||||
{
|
||||
OtherCellId = portal.OtherCellId == 0xFFFF
|
||||
? 0xFFFFFFFFu
|
||||
: lbMask | portal.OtherCellId,
|
||||
PolygonIndex = i,
|
||||
PortalSide = ((ushort)portal.Flags & 0x2) != 0 ? 0 : 1,
|
||||
ExactMatch = ((ushort)portal.Flags & 0x1) != 0,
|
||||
OtherPortalId = unchecked((short)portal.OtherPortalId),
|
||||
};
|
||||
polygons[i] = BuildPolygon(cellStruct, portal.PolygonId) ?? new WalkPolygon();
|
||||
}
|
||||
|
||||
return new WalkCell
|
||||
{
|
||||
CellId = cellId,
|
||||
Portals = portals,
|
||||
PortalPolygons = polygons,
|
||||
StabList = envCell.VisibleCells.Select(v => lbMask | v).ToArray(),
|
||||
WorldTransform = worldTransform,
|
||||
InverseWorldTransform = inverseWorldTransform,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Every interior cell of one landblock, bounded by
|
||||
/// <c>LandBlockInfo.NumCells</c> (the production bound —
|
||||
/// <c>LandblockBuildFactory.BuildInteriorEntitiesForStreaming</c> uses
|
||||
/// the same count; the FW1 test adapter instead scans until the first
|
||||
/// miss, which is equivalent for well-formed installed DATs).</summary>
|
||||
public static Dictionary<uint, WalkCell> BuildInteriorCells(
|
||||
IDatReaderWriter dats, uint landblockId, Vector3 blockOffset,
|
||||
Dictionary<uint, WalkCell>? into = null)
|
||||
{
|
||||
uint lbMask = landblockId & 0xFFFF0000u;
|
||||
Dictionary<uint, WalkCell> cells = into ?? new Dictionary<uint, WalkCell>();
|
||||
if (dats.Get<LandBlockInfo>(lbMask | 0xFFFEu) is not LandBlockInfo info)
|
||||
return cells;
|
||||
|
||||
uint firstCellId = lbMask | 0x0100u;
|
||||
for (uint offset = 0; offset < info.NumCells; offset++)
|
||||
{
|
||||
WalkCell? cell = BuildCell(dats, firstCellId + offset, blockOffset);
|
||||
if (cell is not null)
|
||||
cells[cell.CellId] = cell;
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
private static WalkPolygon? BuildPolygon(CellStruct cellStruct, ushort polygonId)
|
||||
{
|
||||
if (!cellStruct.Polygons.TryGetValue(polygonId, out Polygon? poly)
|
||||
|| poly is null || poly.VertexIds.Count < 3)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return BuildPolygonFromVertices(
|
||||
poly.VertexIds,
|
||||
id => cellStruct.VertexArray.Vertices.TryGetValue((ushort)id, out SWVertex? v)
|
||||
? new Vector3(v.Origin.X, v.Origin.Y, v.Origin.Z)
|
||||
: null);
|
||||
}
|
||||
|
||||
private static WalkPolygon? BuildPolygonFromVertices(
|
||||
IReadOnlyList<short> vertexIds, Func<short, Vector3?> resolve)
|
||||
{
|
||||
var vertices = new Vector3[vertexIds.Count];
|
||||
for (int i = 0; i < vertexIds.Count; i++)
|
||||
{
|
||||
Vector3? v = resolve(vertexIds[i]);
|
||||
if (v is null) return null;
|
||||
vertices[i] = v.Value;
|
||||
}
|
||||
Vector3 normal = Vector3.Normalize(
|
||||
Vector3.Cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
return new WalkPolygon
|
||||
{
|
||||
Vertices = vertices,
|
||||
Plane = new WalkPlane(normal, -Vector3.Dot(normal, vertices[0])),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW1 — the frame walk's observable event stream. These events
|
||||
/// mirror the FW0 oracle-trace vocabulary one-to-one
|
||||
/// (docs/research/2026-08-30-fw-walk-oracle/README.md): the conformance
|
||||
/// contract is that <c>RetailFrameWalk</c>, fed the same world state and
|
||||
/// camera pose as a capture, emits the identical sequence. Richer draw
|
||||
/// commands (mesh/state/stage tuples for the FW2 ordered stream) layer on
|
||||
/// later; this vocabulary stays the conformance surface.
|
||||
/// </summary>
|
||||
public enum WalkEventKind
|
||||
{
|
||||
/// <summary><c>LScape::draw</c> @0x00506330 ran (outdoor root, or drawn
|
||||
/// through an interior frame's exit views).</summary>
|
||||
Landscape,
|
||||
|
||||
/// <summary><c>RenderDeviceD3D::DrawBuilding</c> @0x0059f2a0 entered for
|
||||
/// one building (identified by its position cell id).</summary>
|
||||
Building,
|
||||
|
||||
/// <summary><c>PView::DrawInside</c> @0x005a5860 entered — the frame is
|
||||
/// rooted at this interior cell.</summary>
|
||||
DrawInside,
|
||||
|
||||
/// <summary><c>PView::DrawCells</c> @0x005a4840 entered with the flood's
|
||||
/// cell list (far-to-near iteration happens inside; the event carries the
|
||||
/// list in storage order, matching the oracle dump).</summary>
|
||||
DrawCells,
|
||||
}
|
||||
|
||||
public readonly record struct WalkEvent(
|
||||
WalkEventKind Kind,
|
||||
uint CellId,
|
||||
int OutsideViewCount,
|
||||
IReadOnlyList<uint> Cells)
|
||||
{
|
||||
/// <summary>FW4 slice 1: the landscape turn carries the ACTIVE view
|
||||
/// set's <c>ViewCount</c> (retail <c>LScape::draw</c> iterates the
|
||||
/// views the walk itself installed — 1 for the outdoor root's
|
||||
/// full-screen default view, the interior root's own surviving
|
||||
/// exit-view count otherwise). The driver fans exactly this many
|
||||
/// terrain slices; the old clip apparatus no longer supplies the
|
||||
/// count. Reuses the record's existing <see cref="WalkEvent.OutsideViewCount"/>
|
||||
/// field; the conformance trace mapping compares the kind only
|
||||
/// (<c>LS</c>), so oracle fixtures are unaffected.</summary>
|
||||
public static WalkEvent Landscape(int activeViewCount)
|
||||
=> new(WalkEventKind.Landscape, 0, activeViewCount, Array.Empty<uint>());
|
||||
|
||||
public static WalkEvent Building(uint positionCellId)
|
||||
=> new(WalkEventKind.Building, positionCellId, 0, Array.Empty<uint>());
|
||||
|
||||
public static WalkEvent DrawInside(uint cellId)
|
||||
=> new(WalkEventKind.DrawInside, cellId, 0, Array.Empty<uint>());
|
||||
|
||||
public static WalkEvent DrawCells(int outsideViewCount, IReadOnlyList<uint> cells)
|
||||
=> new(WalkEventKind.DrawCells, 0, outsideViewCount, cells);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The sink a walk emits into. FW1 conformance tests collect events; FW2's
|
||||
/// production sink will additionally receive the ordered draw stream.
|
||||
///
|
||||
/// <para>Campaign FW3.2b-1 additive seam: <see cref="RetailFrameWalk"/> also
|
||||
/// calls the five richer, default-no-op members below at turns the
|
||||
/// vocabulary-only <see cref="WalkEvent"/> stream cannot express (a visited
|
||||
/// landscape cell with no building emits no <see cref="WalkEvent"/> at all;
|
||||
/// <see cref="WalkEventKind.Building"/> carries only a cell id, not the
|
||||
/// <see cref="WalkBuilding"/> reference a driver needs to look up shell
|
||||
/// content, degrade state, or world transform; and punches are not part of
|
||||
/// the FW1 conformance vocabulary at all — the oracle traces do not log
|
||||
/// them). Every FW1/FW2 sink that only implements <see cref="Emit"/>
|
||||
/// continues to compile and behave identically — these are C# default
|
||||
/// interface members, never called by any pre-FW3.2b-1 code path.</para>
|
||||
/// </summary>
|
||||
public interface IWalkEventSink
|
||||
{
|
||||
void Emit(in WalkEvent walkEvent);
|
||||
|
||||
/// <summary>
|
||||
/// Fires once per visited landscape cell, AFTER that cell's building
|
||||
/// turn (if any) — <c>RenderDeviceD3D::DrawSortCell</c> @0x0059f140
|
||||
/// calls <c>DrawBuilding(building)</c> first, then
|
||||
/// <c>DrawObjCell(cell)</c> UNCONDITIONALLY (decomp-confirmed
|
||||
/// 2026-08-30). <paramref name="cellId"/> follows retail's outdoor cell
|
||||
/// encoding — <c>(landblockId & 0xFFFF0000) | (cellIndex + 1)</c> —
|
||||
/// the same convention <see cref="WalkLandscapeAssembler.BuildSlot"/>
|
||||
/// and <see cref="WalkProductionFrameContext.SetViewer"/> +
|
||||
/// <see cref="WalkLandscapeAssembler.SetViewer"/> already use. Default
|
||||
/// no-op.
|
||||
/// </summary>
|
||||
void OnLandscapeCellTurn(uint cellId) { }
|
||||
|
||||
/// <summary>
|
||||
/// Fires at <see cref="RetailFrameWalk.DrawBuilding"/> once retail's own
|
||||
/// gate has passed — <c>RenderDeviceD3D::DrawBuilding</c> @0x0059f2a0
|
||||
/// wraps its ENTIRE body (the alpha barrier, the portal pass, the shell
|
||||
/// draw) in <c>if (part->gfxobj[part->deg_level] != 0)</c>
|
||||
/// @0x0059f2d3 — a degraded-out slot draws NOTHING beyond the
|
||||
/// unconditional <see cref="WalkEventKind.Building"/> <see cref="Emit"/>
|
||||
/// call. <see cref="WalkBuilding.HasGeometry"/> and a non-null
|
||||
/// <see cref="WalkBuilding.SelectDrawingBsp"/> result together model that
|
||||
/// one gate, so this hook fires only after both have passed. This is the
|
||||
/// ALPHA BARRIER turn (<c>D3DPolyRender::FlushAlphaList(0f)</c>
|
||||
/// @0x0059f30b) — retail's own order runs it BEFORE the portal pass
|
||||
/// (<c>CPhysicsPart::Draw(parts, 1)</c>), which in turn runs BEFORE the
|
||||
/// shell draw (<c>CPhysicsPart::Draw(parts, 0)</c> — see
|
||||
/// <see cref="OnBuildingShellTurn"/>, fired separately, after the portal
|
||||
/// pass completes). Default no-op.
|
||||
/// </summary>
|
||||
void OnBuildingTurn(WalkBuilding building) { }
|
||||
|
||||
/// <summary>
|
||||
/// Fires at the END of <see cref="RetailFrameWalk.DrawBuilding"/>, after
|
||||
/// its two-pass portal walk (punches + look-ins, across every active
|
||||
/// view) has fully completed — <c>CPhysicsPart::Draw(parts, 0)</c>
|
||||
/// @0x0059f331, retail's plain-mesh draw of the building's own shell,
|
||||
/// which runs strictly AFTER <c>CPhysicsPart::Draw(parts, 1)</c> (the
|
||||
/// portal flavor) per the decomp sequence at @0x0059f30b–0x0059f345. Only
|
||||
/// fires when <see cref="OnBuildingTurn"/> also fired (same gate; see its
|
||||
/// doc comment) — a degraded-out slot reaches neither. Default no-op.
|
||||
/// </summary>
|
||||
void OnBuildingShellTurn(WalkBuilding building) { }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when the two-pass portal machinery
|
||||
/// (<see cref="WalkBuildingPortals"/>) would draw a far-Z punch fan
|
||||
/// (<c>DrawPortalPolyInternal</c> @0x0059bc90, pass 1) — carries the
|
||||
/// owning building and the polygon in BUILDING-LOCAL space (as stored on
|
||||
/// the emitted <see cref="WalkPortalRef"/>) so a driver can resolve the
|
||||
/// world transform itself. The FW1 oracle traces never log punches
|
||||
/// (<see cref="RetailFrameWalk"/>'s internal <c>PortalPassSink.OnPunch</c>
|
||||
/// stayed a no-op through FW1/FW2 for exactly that reason). Default
|
||||
/// no-op.
|
||||
/// </summary>
|
||||
/// <param name="building">The building whose portal walk emitted the
|
||||
/// punch.</param>
|
||||
/// <param name="polygon">The portal polygon, building-local.</param>
|
||||
/// <param name="activeViewIndex">The active-view index the two-pass walk
|
||||
/// is pinned to (retail <c>building_view</c> @0x0059f3bf) — the punch
|
||||
/// fan clips against THIS view's slice planes in production.</param>
|
||||
void OnPunchGeometry(
|
||||
WalkBuilding building, WalkPolygon polygon, int activeViewIndex)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Fires once per interior root, at the point <c>PView::DrawCells</c>
|
||||
/// @0x005a4840 actually DRAWS the root flood's own cells — NOT where the
|
||||
/// <see cref="WalkEventKind.DrawCells"/> <see cref="Emit"/> call for the
|
||||
/// SAME flood fires (that one sits at breakpoint-ENTRY order, matching
|
||||
/// the FW0 oracle traces; it only RECORDS the flood list). Retail's own
|
||||
/// order inside <c>DrawCells</c> is: <c>LScape::draw</c> FIRST
|
||||
/// (pc:432719, only when exit views survived — see
|
||||
/// <see cref="RetailFrameWalk.DrawLandscape"/> and
|
||||
/// <see cref="WalkEventKind.Landscape"/>), then a full depth clear
|
||||
/// (pc:432731-432732), then the exit-portal seals (pc:432785-432786) —
|
||||
/// BOTH unconditional for an interior root's
|
||||
/// own flood, whether or not a landscape turn just ran — and ONLY THEN
|
||||
/// the flood's cells far-to-near (shell then contents per cell, same
|
||||
/// discipline as <see cref="OnLandscapeCellTurn"/>'s per-cell contents
|
||||
/// and a building's look-in). This hook fires at that later point, so
|
||||
/// this is where a driver should actually draw <paramref name="cells"/>.
|
||||
/// Building look-in floods are UNAFFECTED — retail calls
|
||||
/// <c>DrawCells</c> re-entrantly there with <c>ov==0</c> and no
|
||||
/// landscape/clear/seal step, so their <see cref="WalkEventKind.DrawCells"/>
|
||||
/// <see cref="Emit"/> call still fires at the actual draw point (a
|
||||
/// driver may keep drawing those immediately, as before). Default no-op.
|
||||
/// </summary>
|
||||
void OnInteriorFloodDrawTurn(IReadOnlyList<uint> cells) { }
|
||||
}
|
||||
|
|
@ -1,936 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2b-1: one cell's or one building's already-queried
|
||||
/// static content, ready for <see cref="WalkStaticStreamPopulator"/> —
|
||||
/// caller-built, never read from a retained scene (see
|
||||
/// <see cref="IWalkFrameWorldData"/>'s doc comment).
|
||||
/// </summary>
|
||||
/// <param name="Records">Already-classified <see cref="RenderProjectionRecord"/>s
|
||||
/// for this turn's cell/building, in the SAME order they must enter the walk
|
||||
/// stream (never re-sorted downstream — <see cref="WalkStaticStreamPopulator"/>'s
|
||||
/// own contract). Campaign FW3.4a: a segment INTO <see cref="WalkProductionWorldData"/>'s
|
||||
/// per-frame arena, not a freshly allocated array — see that type's own doc
|
||||
/// comment.</param>
|
||||
/// <param name="TupleLandblockId">The clip-slot-resolving landblock id
|
||||
/// <c>WbDrawDispatcher.ClassifyEntityForWalk</c> needs per record (FW3.2a's
|
||||
/// <c>tupleLandblockId</c> parameter) — carried per-turn rather than once per
|
||||
/// frame because a single frame's cells/buildings can span more than one
|
||||
/// committed landblock.</param>
|
||||
internal readonly record struct WalkFrameStaticRecords(
|
||||
ArraySegment<RenderProjectionRecord> Records, uint TupleLandblockId)
|
||||
{
|
||||
public static readonly WalkFrameStaticRecords Empty =
|
||||
new(ArraySegment<RenderProjectionRecord>.Empty, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2b-1: the world-data lookups <see cref="WalkFrameDriver"/>
|
||||
/// needs at each walk turn, entirely caller-built — the driver reads no
|
||||
/// retained scene state of its own (mirrors <see cref="WalkStaticStreamPopulator"/>'s
|
||||
/// own "reads no retained scene state itself" contract one layer up). FW3.2b-2
|
||||
/// wires the real production implementation (<c>RenderSceneQuery.CopyCellStaticsTo</c>
|
||||
/// / <c>CopyIndexTo</c> + <see cref="WalkBuildingRegistry"/>); this stage's
|
||||
/// headless referee tests wire a synthetic fake instead.
|
||||
/// </summary>
|
||||
internal interface IWalkFrameWorldData
|
||||
{
|
||||
/// <summary>An indoor <c>PView::DrawCells</c> flood cell's (or a building
|
||||
/// look-in's) static content — <c>RenderProjectionClass.IndoorCellStatic</c>.</summary>
|
||||
WalkFrameStaticRecords GetCellStatics(uint cellId);
|
||||
|
||||
/// <summary>One visited landscape (outdoor) cell's static content —
|
||||
/// <c>RenderProjectionClass.OutdoorStatic</c>, keyed by the SAME
|
||||
/// <c>(landblockId & 0xFFFF0000) | (cellIndex+1)</c> id
|
||||
/// <see cref="IWalkEventSink.OnLandscapeCellTurn"/> computes.</summary>
|
||||
WalkFrameStaticRecords GetOutdoorStatics(uint cellId);
|
||||
|
||||
/// <summary>One building's own exterior shell content (<c>IsBuildingShell</c>
|
||||
/// records anchored at the building's position cell).</summary>
|
||||
WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building);
|
||||
|
||||
/// <summary>Building-local → world, for transforming a punch polygon
|
||||
/// before <see cref="IWalkFrameLeafRenderer.DrawPunchFan"/> — the
|
||||
/// production implementation is <see cref="WalkBuildingRegistry.TryGetEntry"/>'s
|
||||
/// <c>WorldTransform</c> (FW3.2b-2 wiring).</summary>
|
||||
Matrix4x4 GetBuildingWorldTransform(WalkBuilding building);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2b-1: the leaf GPU-adjacent actions
|
||||
/// <see cref="WalkFrameDriver"/> calls at walk turns that have no
|
||||
/// <see cref="OrderedDrawStream"/> submission path YET (sky, terrain, an
|
||||
/// EnvCell shell, a portal punch fan, the interior depth clear, the exit-
|
||||
/// portal seals) or that aren't a draw at all (the
|
||||
/// <see cref="RetailAlphaQueue"/> barrier). Kept as its own seam — rather
|
||||
/// than folding these into <see cref="WalkFrameDriver"/> directly — so the
|
||||
/// FW3.2b-1 headless referee suite can wire a fake and prove turn ORDER
|
||||
/// without standing up the real renderers <c>EnvCellRenderer</c>,
|
||||
/// <c>TerrainModernRenderer</c>, <c>GameSky</c>, and
|
||||
/// <c>PortalDepthMaskRenderer.DrawDepthFan</c> — FW3.2b-2's job.
|
||||
///
|
||||
/// <para>Stream submission itself (<c>WbDrawDispatcher.PrepareOrderedStream</c>/
|
||||
/// <c>DrawOrderedRange</c>) is deliberately NOT part of this interface: it is
|
||||
/// already real, production-tested machinery, so <see cref="WalkFrameDriver"/>
|
||||
/// calls it directly rather than abstracting a method that would just
|
||||
/// forward to it one layer deeper.</para>
|
||||
/// </summary>
|
||||
internal interface IWalkFrameLeafRenderer
|
||||
{
|
||||
/// <summary><c>LScape::draw</c> draws <c>GameSky</c> once per outdoor
|
||||
/// walk (retail draws it once inside <c>LScape::draw</c>; the CURRENT
|
||||
/// executor's per-slice call is per-slice-equals-once only for the
|
||||
/// single-view outdoor case it handles today — the walk driver always
|
||||
/// calls this exactly once per frame's Landscape turn).</summary>
|
||||
void DrawSky();
|
||||
|
||||
/// <summary><c>LScape::grab_visible_cells</c>'s terrain mesh, once per
|
||||
/// ACTIVE clip slice — <paramref name="sliceIndex"/> is caller-supplied
|
||||
/// (<see cref="WalkFrameDriver.Collect"/>'s <c>activeTerrainSliceCount</c>)
|
||||
/// since FW3.2b-1 does not wire <c>ClipFrameAssembler</c>/
|
||||
/// <c>ViewconeCuller</c> (FW3.2b-2's job — see plan §FW3.2's dynamic-route
|
||||
/// survival note). Terrain draws FULLY before any per-cell building/
|
||||
/// outdoor-static turn in this stage's turn order — an intra-stage
|
||||
/// simplification of retail's true per-cell <c>DrawLandCell</c>/
|
||||
/// <c>DrawObjCell</c> interleave, recorded here rather than ported, since
|
||||
/// terrain itself carries no walk event today.</summary>
|
||||
void DrawTerrainSlice(int sliceIndex);
|
||||
|
||||
/// <summary>One committed cell's EnvCell shell —
|
||||
/// <c>PView::DrawCells</c>'s <c>DrawEnvCell</c> @0x005a4abe, which
|
||||
/// precedes <c>DrawObjCellForDummies</c> @0x005a4b0d (the cell's static
|
||||
/// contents, appended to the stream instead — see
|
||||
/// <see cref="WalkFrameDriver"/>'s type doc comment) for every cell of
|
||||
/// EVERY flood this stage drives (the ordinary interior root's own
|
||||
/// <c>DrawCells</c> AND a building's look-in <c>DrawCells</c> both walk
|
||||
/// this same shell-then-contents order).</summary>
|
||||
void DrawCellShell(uint cellId);
|
||||
|
||||
/// <summary>One landscape cell's or building shell's static-owner
|
||||
/// particle submission, at its own walk turn — see
|
||||
/// <see cref="WalkFrameEventKind.StaticParticles"/> for the retail
|
||||
/// positional invariant this carries (the #132 falls containment).</summary>
|
||||
void DrawStaticParticles(IReadOnlySet<uint> ownerIds);
|
||||
|
||||
/// <summary><c>PView::DrawCells</c> @0x005a4840's gated full depth clear
|
||||
/// (pc:432731-432732) between the outside stage and the interior root's
|
||||
/// own flood — production maps this to <c>IWorldPassScope.ClearInteriorDepth</c>
|
||||
/// (see that interface's own member of the same name in
|
||||
/// <c>RetailPViewRenderer.cs</c>, staged there on <c>OutsideViewSlices.Length
|
||||
/// > 0</c> — an ACKNOWLEDGED approximation of retail's true
|
||||
/// <c>portalsDrawnCount</c> gate per that file's own comment). This walk
|
||||
/// driver instead fires unconditionally for every interior root (per the
|
||||
/// 2026-08-30 decomp correction: the coordinator's directive supersedes
|
||||
/// the packed path's staged gate — reconcile the two if a firmer
|
||||
/// <c>portalsDrawnCount</c> reading ever lands). Only called for an
|
||||
/// INTERIOR root, never outdoors (retail has no depth clear there —
|
||||
/// <c>portalsDrawnCount</c> never applies to <c>LScape::draw</c>'s own
|
||||
/// top-level walk).</summary>
|
||||
void ClearInteriorDepth();
|
||||
|
||||
/// <summary>The exit-portal seals (pc:432785-432786) — re-stamping every
|
||||
/// outside-leading portal's TRUE depth right after
|
||||
/// <see cref="ClearInteriorDepth"/>, so the aperture the clear just wiped
|
||||
/// stays occluded by the world beyond it rather than by whatever draws
|
||||
/// next. Production maps this to the existing seal-fan machinery
|
||||
/// (<c>RetailPViewRenderer.DrawExitPortalMask</c>/
|
||||
/// <c>PortalDepthMaskRenderer</c>) — this driver only provides the TURN;
|
||||
/// the real per-portal fan geometry is FW3.2b-2's job. Only called for an
|
||||
/// INTERIOR root's own flood, never for a building look-in (those call
|
||||
/// <c>DrawCells</c> re-entrantly with no clear/seal step) and never
|
||||
/// outdoors.</summary>
|
||||
void DrawExitSeals();
|
||||
|
||||
/// <summary><c>DrawPortalPolyInternal</c> @0x0059bc90's depth-only far-Z
|
||||
/// punch fan — pass 1 of the building portal walk.
|
||||
/// <paramref name="worldPolygon"/> is already transformed building-local
|
||||
/// → world (<see cref="WalkFrameDriver"/> does the transform via
|
||||
/// <see cref="IWalkFrameWorldData.GetBuildingWorldTransform"/> at Collect
|
||||
/// time — see that type's own doc comment). The real implementation is
|
||||
/// <c>PortalDepthMaskRenderer.DrawDepthFan</c> with <c>forceFarZ</c>
|
||||
/// (FW3.2b-2 wiring) — Replay calls this at exactly the point Collect
|
||||
/// recorded it: any content queued ahead of the punch (a preceding
|
||||
/// cell's/building's contents — never this building's OWN shell, which
|
||||
/// retail draws only after the whole portal walk completes; see
|
||||
/// <see cref="WalkFrameDriver"/>'s type doc comment) reaches the GPU
|
||||
/// first. <paramref name="activeViewIndex"/> is the view the emitting
|
||||
/// two-pass walk was pinned to (retail <c>building_view =
|
||||
/// Render::portal_view_num</c> @0x0059f3bf) — production clips the fan by
|
||||
/// that view's slice planes.</summary>
|
||||
void DrawPunchFan(WalkPolygon worldPolygon, int activeViewIndex);
|
||||
|
||||
/// <summary><c>RetailAlphaQueue.FlushFartherThan</c>'s <c>DrawBuilding</c>
|
||||
/// barrier — retail's own call site is
|
||||
/// <c>D3DPolyRender::FlushAlphaList(0f)</c> @0x0059f30b, a FLUSH-ALL, not
|
||||
/// a distance-gated flush; this stage keeps the DISPATCHED
|
||||
/// <c>FlushFartherThan(viewerDistanceTo(building))</c> shape (the two
|
||||
/// coincide under the walk's far-to-near landscape order, since a nearer
|
||||
/// emitter has not been inserted into the alpha queue yet — see
|
||||
/// <c>RetailAlphaQueue.FlushFartherThan</c>'s own doc comment) and flags
|
||||
/// the 0f/address detail as an FW4 adjudication candidate rather than
|
||||
/// silently reinterpreting the dispatched design.</summary>
|
||||
void AlphaBarrier(float viewerDistance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2b-1 test seam: an optional, diagnostic-only
|
||||
/// observer of every ordered-stream range <see cref="WalkFrameDriver.Replay"/>
|
||||
/// draws. Production callers pass <see langword="null"/> (the default) — this
|
||||
/// exists purely so the headless referee suite can assert range COUNT,
|
||||
/// per-range command count, and per-range stage without re-deriving them
|
||||
/// from <c>RecordingGpuDevice.Calls</c>' lower-level RHI call log.
|
||||
/// </summary>
|
||||
internal interface IWalkFrameDriverTrace
|
||||
{
|
||||
/// <summary><paramref name="stages"/> is a snapshot (never a live,
|
||||
/// about-to-mutate list) of every command's <see cref="WalkDrawStage"/> in
|
||||
/// the drawn segment, in stream order — by this stage's own segmenting
|
||||
/// discipline (a segment boundary before every non-stream leaf action) a
|
||||
/// segment is always single-stage in practice, but the full list is
|
||||
/// passed so a test can assert that invariant itself instead of trusting
|
||||
/// it.</summary>
|
||||
void OnFlush(int commandCount, IReadOnlyList<WalkDrawStage> stages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.4a: one turn Collect recorded, replayed by
|
||||
/// <see cref="WalkFrameDriver.Replay"/> in the exact order Collect saw it.
|
||||
/// <see cref="WalkFrameEventKind.StreamMark"/> is the collect-time analogue of
|
||||
/// the old immediate driver's flush point — see <see cref="WalkFrameEventKind"/>'s
|
||||
/// own doc comment for the full list and what each carries.
|
||||
/// </summary>
|
||||
internal enum WalkFrameEventKind : byte
|
||||
{
|
||||
/// <summary>The accumulated <see cref="OrderedDrawStream"/> grew since the
|
||||
/// last mark and must be drawn, via <c>WbDrawDispatcher.DrawOrderedRange</c>,
|
||||
/// before whatever leaf event follows. <see cref="WalkFrameEvent.IntArg"/>
|
||||
/// is the stream's exclusive-end command index at the moment this event
|
||||
/// was recorded.</summary>
|
||||
StreamMark,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawSky"/>.</summary>
|
||||
Sky,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawTerrainSlice"/> —
|
||||
/// <see cref="WalkFrameEvent.IntArg"/> is the slice index.</summary>
|
||||
TerrainSlice,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawCellShell"/> —
|
||||
/// <see cref="WalkFrameEvent.CellId"/> is the cell.</summary>
|
||||
CellShell,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawPunchFan"/> —
|
||||
/// <see cref="WalkFrameEvent.Polygon"/> is the already-world-transformed
|
||||
/// polygon (transformed at Collect time, exactly as the pre-FW3.4a driver
|
||||
/// transformed it before its own immediate call), <see cref="WalkFrameEvent.IntArg"/>
|
||||
/// is the active view index.</summary>
|
||||
PunchFan,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.AlphaBarrier"/> —
|
||||
/// <see cref="WalkFrameEvent.FloatArg"/> is the viewer distance, computed
|
||||
/// at Collect time (the context that supplies it does not outlive Collect).</summary>
|
||||
AlphaBarrier,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.ClearInteriorDepth"/>.</summary>
|
||||
ClearInteriorDepth,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawExitSeals"/>.</summary>
|
||||
ExitSeals,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawStaticParticles"/> —
|
||||
/// ONE landscape cell's (<see cref="WalkFrameEvent.CellId"/>) or ONE
|
||||
/// building shell's (<see cref="WalkFrameEvent.Building"/>) static-owner
|
||||
/// particle submission, emitted AT ITS OWN WALK TURN. Retail's falls
|
||||
/// containment is positional: an outdoor emitter's polys join the one
|
||||
/// alpha list during its owner cell's <c>DrawObjCell</c> in the
|
||||
/// far-to-near landscape walk, so every nearer building's pre-punch
|
||||
/// alpha barrier (<c>DrawBuilding</c> @0x0059f2a0's
|
||||
/// <c>FlushAlphaList</c> @0x0059f30b) drains the already-queued FARTHER
|
||||
/// content against still-true depth BEFORE the punch stamps far-Z into
|
||||
/// the aperture. The former single batched submission at the pre-clear
|
||||
/// closure ran AFTER every punch — the barriers fired over an empty
|
||||
/// queue and the falls drained against punched-far aperture pixels (the
|
||||
/// cathedral bleed; the old pipeline's user-verified #132 fix
|
||||
/// `e102fb36` encoded the same invariant).</summary>
|
||||
StaticParticles,
|
||||
}
|
||||
|
||||
/// <summary>See <see cref="WalkFrameEventKind"/> for what each field means per
|
||||
/// kind. A single struct (rather than a kind hierarchy) keeps Collect's
|
||||
/// per-turn list a flat, allocation-cheap <c>List<WalkFrameEvent></c> —
|
||||
/// only <see cref="Polygon"/> (a punch fan's already-transformed geometry)
|
||||
/// allocates, and only once per punch, which is rare enough per frame to be
|
||||
/// unconditionally acceptable (plan §FW3.4a's own call).</summary>
|
||||
internal readonly struct WalkFrameEvent
|
||||
{
|
||||
private WalkFrameEvent(
|
||||
WalkFrameEventKind kind, int intArg, uint cellId, float floatArg, WalkPolygon? polygon,
|
||||
WalkBuilding? building = null)
|
||||
{
|
||||
Kind = kind;
|
||||
IntArg = intArg;
|
||||
CellId = cellId;
|
||||
FloatArg = floatArg;
|
||||
Polygon = polygon;
|
||||
Building = building;
|
||||
}
|
||||
|
||||
internal WalkFrameEventKind Kind { get; }
|
||||
|
||||
internal int IntArg { get; }
|
||||
|
||||
internal uint CellId { get; }
|
||||
|
||||
internal float FloatArg { get; }
|
||||
|
||||
internal WalkPolygon? Polygon { get; }
|
||||
|
||||
/// <summary><see cref="WalkFrameEventKind.StaticParticles"/> only: the
|
||||
/// building whose shell statics' owners submit at this turn; null for a
|
||||
/// landscape cell's turn (then <see cref="CellId"/> names the cell).</summary>
|
||||
internal WalkBuilding? Building { get; }
|
||||
|
||||
internal static WalkFrameEvent Mark(int exclusiveEnd) =>
|
||||
new(WalkFrameEventKind.StreamMark, exclusiveEnd, 0, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent Sky() =>
|
||||
new(WalkFrameEventKind.Sky, 0, 0, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent TerrainSlice(int sliceIndex) =>
|
||||
new(WalkFrameEventKind.TerrainSlice, sliceIndex, 0, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent CellShell(uint cellId) =>
|
||||
new(WalkFrameEventKind.CellShell, 0, cellId, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent PunchFan(WalkPolygon worldPolygon, int activeViewIndex) =>
|
||||
new(WalkFrameEventKind.PunchFan, activeViewIndex, 0, 0f, worldPolygon);
|
||||
|
||||
internal static WalkFrameEvent AlphaBarrier(float viewerDistance) =>
|
||||
new(WalkFrameEventKind.AlphaBarrier, 0, 0, viewerDistance, null);
|
||||
|
||||
internal static WalkFrameEvent LandscapeCellParticles(uint cellId) =>
|
||||
new(WalkFrameEventKind.StaticParticles, 0, cellId, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent BuildingShellParticles(WalkBuilding building) =>
|
||||
new(WalkFrameEventKind.StaticParticles, 0, 0, 0f, null, building);
|
||||
|
||||
internal static WalkFrameEvent ClearInteriorDepth() =>
|
||||
new(WalkFrameEventKind.ClearInteriorDepth, 0, 0, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent ExitSeals() =>
|
||||
new(WalkFrameEventKind.ExitSeals, 0, 0, 0f, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2b-1 — THE WALK FRAME DRIVER. Executes one full
|
||||
/// static-content frame by driving <see cref="RetailFrameWalk"/> with itself
|
||||
/// as the <see cref="IWalkEventSink"/>, so that GPU command-buffer order
|
||||
/// equals retail's walk order (plan §FW3.2b-1's "INTERLEAVING RULE").
|
||||
///
|
||||
/// <para><b>Campaign FW3.4a — the ONE-walk split.</b> Before this stage, a
|
||||
/// single frame ran <see cref="RetailFrameWalk"/> TWICE — once with a
|
||||
/// set-collecting sink to learn the flood/visited-cell set before
|
||||
/// <c>PrepareCellBatches</c>, once more through this driver to actually
|
||||
/// submit — and each walk turn's stream content flushed IMMEDIATELY through
|
||||
/// its own full <c>WbDrawDispatcher.SubmitOrderedStream</c> call (~40 of
|
||||
/// those per frame at a town, each rewriting and rebinding all nine
|
||||
/// per-instance sections for that turn's handful of instances). The FW3.4
|
||||
/// perf checkpoint measured +33.5% CPU p50 and 14× frame allocation from
|
||||
/// exactly those two costs (plus a third, unrelated one — see
|
||||
/// <see cref="WalkProductionWorldData"/>'s own doc comment) and tripped the
|
||||
/// campaign's ±20% stop rule. This stage collapses both: <see cref="Collect"/>
|
||||
/// runs <see cref="RetailFrameWalk"/> ONCE, doing everything the immediate
|
||||
/// driver used to do EXCEPT the actual GPU submission — stream appends
|
||||
/// accumulate without flushing, every former immediate leaf call records a
|
||||
/// <see cref="WalkFrameEvent"/> instead, and the driver keeps its
|
||||
/// visited-set bookkeeping (absorbing the renderer's old dedicated
|
||||
/// set-collecting sink) so the SAME walk answers both questions.
|
||||
/// <see cref="Replay"/> then performs the actual GPU work afterward:
|
||||
/// <c>WbDrawDispatcher.PrepareOrderedStream</c> uploads the WHOLE frame's
|
||||
/// stream once, and each recorded <see cref="WalkFrameEventKind.StreamMark"/>
|
||||
/// becomes one cheap <c>DrawOrderedRange</c> call over the already-uploaded
|
||||
/// payload — interleaved, in the exact recorded order, with the leaf
|
||||
/// renderer calls the OLD immediate driver made inline. Because Replay walks
|
||||
/// the SAME event sequence Collect recorded at the SAME points the old code
|
||||
/// flushed, GPU command order is unchanged bit-for-bit; only the number of
|
||||
/// walks (two → one) and the shape of the GPU submission (many small
|
||||
/// rebind-and-draw calls → one bind, many cheap draws) changes.
|
||||
/// <see cref="RunFrame"/> remains Collect immediately followed by Replay, for
|
||||
/// callers (today: the headless referee suite) that do not need the split;
|
||||
/// <c>RetailPViewRenderer</c> uses the split directly, since it must run
|
||||
/// <c>PrepareCellBatches</c>/<c>BuildAndBorrow</c> BETWEEN them.</para>
|
||||
///
|
||||
/// <para><b>The one mark rule that reproduces the whole frame script:</b>
|
||||
/// before EVERY leaf-renderer event (<see cref="WalkFrameEventKind.Sky"/>,
|
||||
/// <c>TerrainSlice</c>, <c>CellShell</c>, <c>ClearInteriorDepth</c>,
|
||||
/// <c>ExitSeals</c>, <c>PunchFan</c>) and before every
|
||||
/// <see cref="WalkFrameEventKind.AlphaBarrier"/> event, Collect records a
|
||||
/// <see cref="WalkFrameEventKind.StreamMark"/> if the stream grew since the
|
||||
/// last one (a no-op otherwise — "empty segments submit nothing"); a
|
||||
/// building's own shell content is APPENDED (not marked) the moment
|
||||
/// <see cref="IWalkEventSink.OnBuildingShellTurn"/> fires, so it only gets a
|
||||
/// mark ahead of whatever non-stream event comes next (the next building's
|
||||
/// alpha barrier, or the final mark at <see cref="Replay"/>'s prepare step).
|
||||
/// This single rule, combined with "shell before contents" per cell,
|
||||
/// retail's own building order (alpha barrier → portal pass → shell — see
|
||||
/// <see cref="RetailFrameWalk.DrawBuilding"/>'s doc comment), and retail's
|
||||
/// own interior-root DRAW order (landscape → clear → seals → the flood's own
|
||||
/// cells — see <see cref="IWalkEventSink.OnInteriorFloodDrawTurn"/>'s doc
|
||||
/// comment; this is NOT the order the walk's EVENTS fire in, which is
|
||||
/// breakpoint-entry order matching the FW0 oracle traces), is what produces
|
||||
/// every ordering constraint the plan's frame script names: [cell1 shell]
|
||||
/// [cell1 contents mark] [cell2 shell] …, [alpha barrier] [punch fan(s) +
|
||||
/// look-in flood(s), each following the SAME shell-then-contents per-cell
|
||||
/// discipline] [building shell content mark], [landscape (if exit views
|
||||
/// survived)] [interior depth clear] [exit-portal seals] [the interior
|
||||
/// root's own flood cells], and a final mark at Replay's prepare step. No
|
||||
/// special-casing per turn kind is needed beyond that.</para>
|
||||
///
|
||||
/// <para>Retail anchors: <c>SmartBox::RenderNormalMode</c> @0x00453aa0 (the
|
||||
/// root <see cref="RetailFrameWalk.WalkFrame"/> already ports),
|
||||
/// <c>RenderDeviceD3D::DrawSortCell</c> @0x0059f140 (building-before-
|
||||
/// DrawObjCell per landscape cell), <c>PView::DrawCells</c> @0x005a4840
|
||||
/// (<c>DrawEnvCell</c> @0x005a4abe before <c>DrawObjCellForDummies</c>
|
||||
/// @0x005a4b0d per flooded cell; <c>LScape::draw</c> pc:432719, the depth
|
||||
/// clear pc:432731-432732, and the exit-portal seals pc:432785-432786 —
|
||||
/// ALL strictly before the flood's own cells, though the event marking
|
||||
/// <c>DrawCells</c> entry fires before all three), <c>RenderDeviceD3D::DrawBuilding</c>
|
||||
/// @0x0059f2a0 (the <c>part->gfxobj[deg_level]!=0</c> gate @0x0059f2d3
|
||||
/// and the alpha-barrier → portal-pass → shell order @0x0059f30b–0x0059f345).</para>
|
||||
/// </summary>
|
||||
internal sealed class WalkFrameDriver : IWalkEventSink
|
||||
{
|
||||
private readonly WbDrawDispatcher _dispatcher;
|
||||
private readonly WalkStaticStreamPopulator _populator;
|
||||
private readonly IWalkFrameLeafRenderer _leafRenderer;
|
||||
private readonly IWalkFrameWorldData _worldData;
|
||||
private readonly IWalkFrameDriverTrace? _trace;
|
||||
private readonly OrderedDrawStream _stream = new();
|
||||
private readonly List<WalkFrameEvent> _events = new();
|
||||
private readonly List<int> _markPositions = new();
|
||||
|
||||
// Campaign FW3.4a: visited-set collection, absorbed from the renderer's
|
||||
// former dedicated set-collecting sink (RetailPViewRenderer's old
|
||||
// WalkVisitedSetCollector) — the SAME shapes that sink produced, now
|
||||
// populated by the ONE walk Collect already runs instead of a second
|
||||
// walk pass dedicated to nothing but set-gathering.
|
||||
internal HashSet<uint> VisitedCells { get; } = new();
|
||||
|
||||
/// <summary>FW4 slice 2: the interior root's ORDERED flood cell list,
|
||||
/// exactly as retail's <c>PView::DrawCells</c> iterates it for the
|
||||
/// exit-portal seals (pc:432785-432786) — captured at
|
||||
/// <see cref="IWalkEventSink.OnInteriorFloodDrawTurn"/>. Empty for an
|
||||
/// outdoor-rooted frame. The production seal draw iterates THIS list
|
||||
/// (the walk's own flood), not the old visibility apparatus's — at the
|
||||
/// #456 seam band the old builder's flood misses exit portals the walk
|
||||
/// provably floods, leaving unsealed aperture depth the end-of-frame
|
||||
/// alpha drain splats through (the cathedral falls shine-through).</summary>
|
||||
internal List<uint> InteriorFloodCells { get; } = new();
|
||||
|
||||
// Replay scratch for StaticParticles events (sequential replay — one
|
||||
// reused set is safe).
|
||||
private readonly HashSet<uint> _staticParticleOwnerScratch = new();
|
||||
|
||||
private static void UnionOwners(
|
||||
in WalkFrameStaticRecords records, HashSet<uint> destination)
|
||||
{
|
||||
foreach (RenderProjectionRecord record in records.Records)
|
||||
{
|
||||
if (record.Source.LocalEntityId != 0)
|
||||
destination.Add(record.Source.LocalEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
internal List<WalkBuilding> VisitedBuildings { get; } = new();
|
||||
|
||||
internal HashSet<uint> VisitedLandscapeCellIds { get; } = new();
|
||||
|
||||
// ---- transient per-Collect state (set in BeginFrame, read by Replay,
|
||||
// cleared by Replay's own completion) ----
|
||||
private IWalkBuildingFrameContext? _ctx;
|
||||
private Matrix4x4 _viewProjection;
|
||||
private Vector3 _cameraWorldPosition;
|
||||
private bool _skyDrawnThisFrame;
|
||||
private WalkDrawStage? _currentDcStage;
|
||||
private bool _readyToReplay;
|
||||
|
||||
internal WalkFrameDriver(
|
||||
WbDrawDispatcher dispatcher,
|
||||
IWalkFrameLeafRenderer leafRenderer,
|
||||
IWalkFrameWorldData worldData,
|
||||
IWalkFrameDriverTrace? trace = null)
|
||||
{
|
||||
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||
_leafRenderer = leafRenderer ?? throw new ArgumentNullException(nameof(leafRenderer));
|
||||
_worldData = worldData ?? throw new ArgumentNullException(nameof(worldData));
|
||||
_trace = trace;
|
||||
_populator = new WalkStaticStreamPopulator(dispatcher);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drives one complete frame at retail's root (<c>SmartBox::RenderNormalMode</c>):
|
||||
/// <see cref="Collect"/> immediately followed by <see cref="Replay"/>. Kept
|
||||
/// for callers that don't need the split (today: the headless referee
|
||||
/// suite) — <c>RetailPViewRenderer</c> calls the pair directly, since it
|
||||
/// must run other frame work BETWEEN them (plan §FW3.4a).
|
||||
/// </summary>
|
||||
internal void RunFrame(
|
||||
RetailFrameWalk walk,
|
||||
uint cameraCellId,
|
||||
WalkCell? cameraCell,
|
||||
WalkLandscape landscape,
|
||||
IRetailFrameWalkContext ctx,
|
||||
IGpuFrame frame,
|
||||
IGpuPassEncoder encoder,
|
||||
Matrix4x4 viewProjection,
|
||||
Vector3 cameraWorldPosition)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(frame);
|
||||
ArgumentNullException.ThrowIfNull(encoder);
|
||||
|
||||
Collect(
|
||||
walk, cameraCellId, cameraCell, landscape, ctx,
|
||||
viewProjection, cameraWorldPosition);
|
||||
Replay(frame, encoder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.4a Phase 1 — THE ONE WALK. Drives
|
||||
/// <see cref="RetailFrameWalk.WalkFrame"/> with this driver as its sink,
|
||||
/// sandwiched between <see cref="BeginFrame"/>/<see cref="EndFrame"/>,
|
||||
/// performing NO GPU work: see this type's own doc comment.
|
||||
/// </summary>
|
||||
internal void Collect(
|
||||
RetailFrameWalk walk,
|
||||
uint cameraCellId,
|
||||
WalkCell? cameraCell,
|
||||
WalkLandscape landscape,
|
||||
IRetailFrameWalkContext ctx,
|
||||
Matrix4x4 viewProjection,
|
||||
Vector3 cameraWorldPosition)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(walk);
|
||||
ArgumentNullException.ThrowIfNull(landscape);
|
||||
ArgumentNullException.ThrowIfNull(ctx);
|
||||
|
||||
BeginFrame(ctx, viewProjection, cameraWorldPosition);
|
||||
walk.WalkFrame(cameraCellId, cameraCell, landscape, ctx, this);
|
||||
EndFrame();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a collect scope without driving the walk itself — for a caller
|
||||
/// (or a test) that already holds an isolated walk entry point (e.g. one
|
||||
/// <see cref="RetailFrameWalk.DrawBuilding"/> or
|
||||
/// <see cref="RetailFrameWalk.DrawLandscape"/> call) and wants this
|
||||
/// driver's turn handling without going through the top-level root.
|
||||
/// <see cref="Collect"/> is implemented in terms of this pair. Performs no
|
||||
/// GPU work — see this type's own doc comment.
|
||||
/// </summary>
|
||||
internal void BeginFrame(
|
||||
IWalkBuildingFrameContext ctx,
|
||||
Matrix4x4 viewProjection,
|
||||
Vector3 cameraWorldPosition)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ctx);
|
||||
if (_ctx is not null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"WalkFrameDriver.BeginFrame was called while a previous frame was still open — "
|
||||
+ "the driver is not re-entrant (Campaign FW3.2b-1 fail-loud rule); call "
|
||||
+ "EndFrame (or let a thrown exception's cleanup run) before starting the next.");
|
||||
}
|
||||
|
||||
_ctx = ctx;
|
||||
_viewProjection = viewProjection;
|
||||
_cameraWorldPosition = cameraWorldPosition;
|
||||
_skyDrawnThisFrame = false;
|
||||
_currentDcStage = null;
|
||||
_readyToReplay = false;
|
||||
_stream.Reset();
|
||||
_events.Clear();
|
||||
_markPositions.Clear();
|
||||
VisitedCells.Clear();
|
||||
VisitedBuildings.Clear();
|
||||
VisitedLandscapeCellIds.Clear();
|
||||
InteriorFloodCells.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Records the final segment mark (plan §FW3.2b-1's "at frame
|
||||
/// end: final segment flush", now a mark rather than a draw — see this
|
||||
/// type's own doc comment), then closes the collect scope. The recorded
|
||||
/// stream/events survive this call — <see cref="Replay"/> consumes them —
|
||||
/// which is the one behavioral difference from the pre-FW3.4a EndFrame,
|
||||
/// which reset the stream here because it had just drawn it.</summary>
|
||||
internal void EndFrame()
|
||||
{
|
||||
try
|
||||
{
|
||||
MarkIfGrown();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ctx = null;
|
||||
_readyToReplay = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.4a Phase 2. Requires a completed Collect (an
|
||||
/// <see cref="EndFrame"/> having run since the last Replay) — throws
|
||||
/// otherwise, rather than silently replaying a stale or empty event list.
|
||||
/// Uploads the WHOLE collected stream exactly once (skipped when it is
|
||||
/// empty), then walks the recorded events in order: a
|
||||
/// <see cref="WalkFrameEventKind.StreamMark"/> issues one
|
||||
/// <c>WbDrawDispatcher.DrawOrderedRange</c> call over the segment it
|
||||
/// closes off; every other event kind issues its corresponding
|
||||
/// <see cref="IWalkFrameLeafRenderer"/> call. Because Collect recorded
|
||||
/// these events at EXACTLY the points the pre-FW3.4a immediate driver
|
||||
/// used to flush/draw, this reproduces the SAME interleaved GPU command
|
||||
/// order — the campaign invariant — from one walk instead of two.
|
||||
/// </summary>
|
||||
internal void Replay(IGpuFrame frame, IGpuPassEncoder encoder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(frame);
|
||||
ArgumentNullException.ThrowIfNull(encoder);
|
||||
if (!_readyToReplay)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"WalkFrameDriver.Replay was called without a completed Collect (BeginFrame/"
|
||||
+ "EndFrame, or Collect/RunFrame) preceding it — there is nothing recorded to "
|
||||
+ "replay.");
|
||||
}
|
||||
|
||||
if (_stream.Count > 0)
|
||||
_dispatcher.PrepareOrderedStream(frame, _stream, _viewProjection, _markPositions);
|
||||
|
||||
int cursor = 0;
|
||||
for (int i = 0; i < _events.Count; i++)
|
||||
{
|
||||
WalkFrameEvent e = _events[i];
|
||||
switch (e.Kind)
|
||||
{
|
||||
case WalkFrameEventKind.StreamMark:
|
||||
int end = e.IntArg;
|
||||
int count = end - cursor;
|
||||
if (_trace is not null)
|
||||
_trace.OnFlush(count, _stream.Stages.GetRange(cursor, count));
|
||||
_dispatcher.DrawOrderedRange(encoder, cursor, count);
|
||||
cursor = end;
|
||||
break;
|
||||
case WalkFrameEventKind.Sky:
|
||||
_leafRenderer.DrawSky();
|
||||
break;
|
||||
case WalkFrameEventKind.TerrainSlice:
|
||||
_leafRenderer.DrawTerrainSlice(e.IntArg);
|
||||
break;
|
||||
case WalkFrameEventKind.CellShell:
|
||||
_leafRenderer.DrawCellShell(e.CellId);
|
||||
break;
|
||||
case WalkFrameEventKind.PunchFan:
|
||||
_leafRenderer.DrawPunchFan(e.Polygon!, e.IntArg);
|
||||
break;
|
||||
case WalkFrameEventKind.AlphaBarrier:
|
||||
_leafRenderer.AlphaBarrier(e.FloatArg);
|
||||
break;
|
||||
case WalkFrameEventKind.ClearInteriorDepth:
|
||||
_leafRenderer.ClearInteriorDepth();
|
||||
break;
|
||||
case WalkFrameEventKind.ExitSeals:
|
||||
_leafRenderer.DrawExitSeals();
|
||||
break;
|
||||
case WalkFrameEventKind.StaticParticles:
|
||||
_staticParticleOwnerScratch.Clear();
|
||||
UnionOwners(
|
||||
e.Building is WalkBuilding shellOwner
|
||||
? _worldData.GetBuildingShellStatics(shellOwner)
|
||||
: _worldData.GetOutdoorStatics(e.CellId),
|
||||
_staticParticleOwnerScratch);
|
||||
if (_staticParticleOwnerScratch.Count > 0)
|
||||
_leafRenderer.DrawStaticParticles(_staticParticleOwnerScratch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_stream.Reset();
|
||||
_events.Clear();
|
||||
_markPositions.Clear();
|
||||
_readyToReplay = false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// IWalkEventSink
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
void IWalkEventSink.Emit(in WalkEvent walkEvent)
|
||||
{
|
||||
switch (walkEvent.Kind)
|
||||
{
|
||||
case WalkEventKind.DrawInside:
|
||||
_currentDcStage = WalkDrawStage.CellStatic;
|
||||
VisitedCells.Add(walkEvent.CellId);
|
||||
break;
|
||||
case WalkEventKind.Landscape:
|
||||
HandleLandscapeTurn(walkEvent.OutsideViewCount);
|
||||
break;
|
||||
case WalkEventKind.DrawCells:
|
||||
foreach (uint id in walkEvent.Cells)
|
||||
VisitedCells.Add(id);
|
||||
HandleDrawCellsTurn(walkEvent.Cells);
|
||||
break;
|
||||
case WalkEventKind.Building:
|
||||
// OnBuildingTurn (below) carries the actual side effects —
|
||||
// this vocabulary-only event needs no driver action.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IWalkEventSink.OnLandscapeCellTurn(uint cellId)
|
||||
{
|
||||
RequireOpenFrame();
|
||||
VisitedLandscapeCellIds.Add(cellId);
|
||||
WalkFrameStaticRecords records = _worldData.GetOutdoorStatics(cellId);
|
||||
_populator.PopulateOutdoorStatics(
|
||||
_stream, cellId, records.Records, records.TupleLandblockId,
|
||||
_cameraWorldPosition, _viewProjection);
|
||||
// FW4 (the #132 positional invariant): this cell's emitter owners
|
||||
// submit AT THIS TURN, so nearer buildings' pre-punch barriers
|
||||
// drain them against still-true depth — see
|
||||
// WalkFrameEventKind.StaticParticles. Mark first so the cell's own
|
||||
// meshes flush ahead of its particle submission (retail's
|
||||
// per-object DrawObjCell order).
|
||||
if (HasAnyOwner(records))
|
||||
{
|
||||
MarkIfGrown();
|
||||
_events.Add(WalkFrameEvent.LandscapeCellParticles(cellId));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasAnyOwner(in WalkFrameStaticRecords records)
|
||||
{
|
||||
foreach (RenderProjectionRecord record in records.Records)
|
||||
{
|
||||
if (record.Source.LocalEntityId != 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void IWalkEventSink.OnBuildingTurn(WalkBuilding building)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(building);
|
||||
IWalkBuildingFrameContext ctx = RequireOpenFrame();
|
||||
VisitedBuildings.Add(building);
|
||||
|
||||
// D3DPolyRender::FlushAlphaList(0f) @0x0059f30b — retail's alpha
|
||||
// barrier, first inside the gate. The portal pass (punches +
|
||||
// look-ins) follows this call; the building's own shell content is
|
||||
// appended only once that pass completes (OnBuildingShellTurn).
|
||||
MarkIfGrown();
|
||||
_events.Add(WalkFrameEvent.AlphaBarrier(ctx.ViewerDistanceTo(building)));
|
||||
|
||||
_currentDcStage = WalkDrawStage.LookInStatic;
|
||||
}
|
||||
|
||||
void IWalkEventSink.OnBuildingShellTurn(WalkBuilding building)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(building);
|
||||
RequireOpenFrame();
|
||||
|
||||
// CPhysicsPart::Draw(parts, 0) @0x0059f331 — retail's plain-mesh
|
||||
// shell draw, strictly after the portal pass (CPhysicsPart::Draw
|
||||
// (parts, 1)). Mark first so this building's shell content never
|
||||
// shares a replayed range with whatever the portal pass's last
|
||||
// look-in flood appended (keeps every range single-stage).
|
||||
MarkIfGrown();
|
||||
WalkFrameStaticRecords shell = _worldData.GetBuildingShellStatics(building);
|
||||
_populator.PopulateCell(
|
||||
_stream, WalkDrawStage.BuildingShell, building.PositionCellId,
|
||||
shell.Records, shell.TupleLandblockId, _cameraWorldPosition, _viewProjection);
|
||||
// FW4 (the #132 positional invariant): the building's own shell
|
||||
// emitters submit at the shell turn, after the shell content
|
||||
// flushes — see WalkFrameEventKind.StaticParticles.
|
||||
if (HasAnyOwner(shell))
|
||||
{
|
||||
MarkIfGrown();
|
||||
_events.Add(WalkFrameEvent.BuildingShellParticles(building));
|
||||
}
|
||||
}
|
||||
|
||||
void IWalkEventSink.OnPunchGeometry(
|
||||
WalkBuilding building, WalkPolygon polygon, int activeViewIndex)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(building);
|
||||
ArgumentNullException.ThrowIfNull(polygon);
|
||||
RequireOpenFrame();
|
||||
|
||||
MarkIfGrown();
|
||||
Matrix4x4 worldTransform = _worldData.GetBuildingWorldTransform(building);
|
||||
_events.Add(
|
||||
WalkFrameEvent.PunchFan(TransformToWorld(polygon, worldTransform), activeViewIndex));
|
||||
}
|
||||
|
||||
void IWalkEventSink.OnInteriorFloodDrawTurn(IReadOnlyList<uint> cells)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(cells);
|
||||
RequireOpenFrame();
|
||||
|
||||
// PView::DrawCells @0x005a4840: the gated full depth clear
|
||||
// (pc:432731-432732) then the exit-portal seals (pc:432785-432786) —
|
||||
// both unconditional for an interior root's own flood, whether or
|
||||
// not a landscape turn just ran (see this driver's type doc
|
||||
// comment).
|
||||
MarkIfGrown();
|
||||
_events.Add(WalkFrameEvent.ClearInteriorDepth());
|
||||
|
||||
MarkIfGrown();
|
||||
_events.Add(WalkFrameEvent.ExitSeals());
|
||||
|
||||
// FW4 slice 2: retain the ordered flood for the seal draw (the
|
||||
// DrawExitSeals leaf runs at Replay, when Collect has long filled
|
||||
// this) — see the property's own doc comment.
|
||||
InteriorFloodCells.Clear();
|
||||
for (int i = 0; i < cells.Count; i++)
|
||||
InteriorFloodCells.Add(cells[i]);
|
||||
|
||||
for (int i = 0; i < cells.Count; i++)
|
||||
EmitCellTurn(WalkDrawStage.CellStatic, cells[i]);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Turn handlers
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private void HandleLandscapeTurn(int activeViewCount)
|
||||
{
|
||||
RequireOpenFrame();
|
||||
if (activeViewCount < 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"A Landscape turn fired with {activeViewCount} active views — "
|
||||
+ "RetailFrameWalk only draws the landscape through an installed view set "
|
||||
+ "(the outdoor root's full-screen default view, or an interior root's "
|
||||
+ "surviving exit views, both at least 1). A zero/negative count is a "
|
||||
+ "walk/driver desync (Campaign FW fail-loud rule).");
|
||||
}
|
||||
if (_skyDrawnThisFrame)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A second Landscape turn fired in one frame — RetailFrameWalk.WalkFrame/"
|
||||
+ "DrawInside's own call graph guarantees at most one Landscape turn per "
|
||||
+ "frame (outdoor root draws it once; an interior root draws it at most "
|
||||
+ "once more, through surviving exit views). A second occurrence is a walk/"
|
||||
+ "driver desync, not something to silently double-draw sky for (Campaign "
|
||||
+ "FW3.2b-1 fail-loud rule).");
|
||||
}
|
||||
|
||||
MarkIfGrown();
|
||||
_events.Add(WalkFrameEvent.Sky());
|
||||
_skyDrawnThisFrame = true;
|
||||
// FW4 slice 6 (correcting slice 1's per-view fan): retail's
|
||||
// LScape::draw draws the terrain blocks ONCE per landscape turn —
|
||||
// the active views feed only the block-level visibility union
|
||||
// (CheckBlocks); terrain cells are ordinary meshes and retail never
|
||||
// clips those per view (pixel exactness = the depth clear + seals +
|
||||
// interior repaint afterward). One terrain turn, always; the walk's
|
||||
// views still own the punch fans and dynamics apertures.
|
||||
_events.Add(WalkFrameEvent.TerrainSlice(0));
|
||||
}
|
||||
|
||||
private void HandleDrawCellsTurn(IReadOnlyList<uint> cells)
|
||||
{
|
||||
RequireOpenFrame();
|
||||
if (_currentDcStage is not { } stage)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A DrawCells turn fired before any DrawInside or Building turn established "
|
||||
+ "which stage its cells belong to — a walk/driver desync (Campaign FW3.2b-1 "
|
||||
+ "fail-loud rule): RetailFrameWalk only ever emits DrawCells after DrawInside "
|
||||
+ "(the interior root's own flood) or after a building's look-in portal pass.");
|
||||
}
|
||||
|
||||
if (stage == WalkDrawStage.CellStatic)
|
||||
{
|
||||
// The interior root's OWN flood: this DC event fired at
|
||||
// breakpoint-ENTRY order (matching the FW0 oracle traces), not
|
||||
// retail's actual draw point — RECORD only. RetailFrameWalk.
|
||||
// DrawInside hands the SAME cell list to
|
||||
// OnInteriorFloodDrawTurn later, at the point retail really
|
||||
// draws it (after the landscape turn, or immediately when no
|
||||
// exit views survived) — see that method's doc comment.
|
||||
return;
|
||||
}
|
||||
|
||||
// Any other stage (LookInStatic) is a building's look-in flood:
|
||||
// retail calls DrawCells re-entrantly there with no landscape/clear/
|
||||
// seal step, so its DC event already fires at the real draw point —
|
||||
// record immediately, unchanged from before this correction.
|
||||
for (int i = 0; i < cells.Count; i++)
|
||||
EmitCellTurn(stage, cells[i]);
|
||||
}
|
||||
|
||||
private void EmitCellTurn(WalkDrawStage stage, uint cellId)
|
||||
{
|
||||
MarkIfGrown();
|
||||
_events.Add(WalkFrameEvent.CellShell(cellId));
|
||||
WalkFrameStaticRecords records = _worldData.GetCellStatics(cellId);
|
||||
_populator.PopulateCell(
|
||||
_stream, stage, cellId, records.Records, records.TupleLandblockId,
|
||||
_cameraWorldPosition, _viewProjection);
|
||||
}
|
||||
|
||||
/// <summary>Campaign FW3.4a: the collect-time analogue of the old
|
||||
/// immediate driver's <c>FlushIfNonEmpty</c> — records a
|
||||
/// <see cref="WalkFrameEventKind.StreamMark"/> at the stream's current
|
||||
/// length if it grew since the last mark (a no-op otherwise, exactly
|
||||
/// like that method's own "empty segments submit nothing" rule). Also
|
||||
/// appends the boundary to <see cref="_markPositions"/>, which
|
||||
/// <see cref="Replay"/> hands to <c>PrepareOrderedStream</c> so a merge
|
||||
/// run can never span it — see <c>WbDrawDispatcher.BuildOrderedMergeRuns</c>'s
|
||||
/// <c>forcedBreaksAscending</c> parameter for why that matters.</summary>
|
||||
private void MarkIfGrown()
|
||||
{
|
||||
int count = _stream.Count;
|
||||
int last = _markPositions.Count > 0 ? _markPositions[^1] : 0;
|
||||
if (count == last)
|
||||
return;
|
||||
|
||||
_markPositions.Add(count);
|
||||
_events.Add(WalkFrameEvent.Mark(count));
|
||||
}
|
||||
|
||||
private IWalkBuildingFrameContext RequireOpenFrame() =>
|
||||
_ctx ?? throw new InvalidOperationException(
|
||||
"WalkFrameDriver received a walk turn outside BeginFrame/EndFrame — call "
|
||||
+ "BeginFrame (or Collect/RunFrame) before driving the walk with this driver as "
|
||||
+ "its IWalkEventSink.");
|
||||
|
||||
/// <summary><c>ConstructBuildingView</c>'s polygon is building-local; the
|
||||
/// punch fan needs world space. Vertices transform directly; the plane
|
||||
/// normal uses <see cref="Vector3.TransformNormal"/> (correct for the
|
||||
/// rigid, shear-free placements <see cref="WalkBuildingFactory"/> and
|
||||
/// <see cref="WalkCellFactory"/> build) and <c>D</c> is rederived from
|
||||
/// the transformed normal and the first transformed vertex — the SAME
|
||||
/// construction <see cref="WalkCellFactory"/>/<see cref="WalkBuildingFactory"/>
|
||||
/// already use for their own polygons (<c>Plane = new WalkPlane(normal,
|
||||
/// -Vector3.Dot(normal, vertices[0]))</c>).</summary>
|
||||
private static WalkPolygon TransformToWorld(WalkPolygon local, Matrix4x4 worldTransform)
|
||||
{
|
||||
var vertices = new Vector3[local.Vertices.Length];
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
vertices[i] = Vector3.Transform(local.Vertices[i], worldTransform);
|
||||
|
||||
Vector3 normal = local.Vertices.Length > 0
|
||||
? Vector3.Normalize(Vector3.TransformNormal(local.Plane.Normal, worldTransform))
|
||||
: Vector3.Zero;
|
||||
float d = vertices.Length > 0 ? -Vector3.Dot(normal, vertices[0]) : 0f;
|
||||
|
||||
return new WalkPolygon { Vertices = vertices, Plane = new WalkPlane(normal, d) };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>One landblock as the walk sees it (retail <c>CLandBlock</c>):
|
||||
/// static shape plus the per-frame visibility the landscape pass writes.</summary>
|
||||
public sealed class WalkLandBlock
|
||||
{
|
||||
/// <summary>Additive (Campaign FW3.2b-1): this block's landblock id in
|
||||
/// retail's <c>(bx<<24 | by<<16)</c> encoding —
|
||||
/// <see cref="WalkLandscapeAssembler.BuildSlot"/> already derives
|
||||
/// <c>(bx, by)</c> from this SAME encoding (its <c>BlockCoords</c>
|
||||
/// helper), and <see cref="WalkLandscapeAssembler.SetViewer"/> /
|
||||
/// <see cref="WalkProductionFrameContext"/>'s low-word convention
|
||||
/// (<c>low >= 1 && low <= 0x40</c>) both key off it too.
|
||||
/// This field lets a per-visited-cell walk-turn hook
|
||||
/// (<see cref="IWalkEventSink.OnLandscapeCellTurn"/>) recover a real
|
||||
/// outdoor cell id — <c>(LandblockId & 0xFFFF0000) | (cellIndex+1)</c>
|
||||
/// — without a second landblock lookup. Defaults to 0: purely additive,
|
||||
/// unread by every pre-FW3.2b-1 caller (the FW1 conformance harness's
|
||||
/// synthetic blocks never populate it).</summary>
|
||||
public uint LandblockId;
|
||||
|
||||
public int SideCellCount = 8;
|
||||
public float MaxZ;
|
||||
public float MinZ;
|
||||
|
||||
/// <summary>Per cell index (x * side + y): the building the cell's
|
||||
/// CSortCell owns, if any.</summary>
|
||||
public WalkBuilding?[] CellBuildings = [];
|
||||
|
||||
// ---- per-frame visibility (draw_check_blocks / landcell_check) ----
|
||||
public WalkBoundingType InView;
|
||||
public WalkBoundingType[] CellInView = [];
|
||||
|
||||
// ---- per-block cell draw order cache (calc_sq_draw_order) ----
|
||||
public int[] DrawArray = [];
|
||||
public int ClosestX = -1;
|
||||
public int ClosestY = -1;
|
||||
|
||||
public void EnsureCellArrays()
|
||||
{
|
||||
int n = SideCellCount * SideCellCount;
|
||||
if (CellInView.Length < n) CellInView = new WalkBoundingType[n];
|
||||
if (DrawArray.Length < n) DrawArray = new int[n];
|
||||
if (CellBuildings.Length < n) CellBuildings = new WalkBuilding?[n];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The viewer-centered block grid (retail <c>LScape</c>): mid_width
|
||||
/// × mid_width blocks (retail 11×11), the near-to-far block draw list, and
|
||||
/// the viewer's grid offsets (<c>calc_draw_order</c>).</summary>
|
||||
public sealed class WalkLandscape
|
||||
{
|
||||
public const float BlockLength = 192f; // 24 m cell × 8 (BN elided this to '0f')
|
||||
public const float CellLength = 24f;
|
||||
|
||||
public int MidWidth = 11;
|
||||
public WalkLandBlock?[] Blocks = []; // [x * MidWidth + y]
|
||||
public int ViewerBlockX;
|
||||
public int ViewerBlockY;
|
||||
public int ViewerCellX; // viewer cell & 7 per axis (SqCoord)
|
||||
public int ViewerCellY;
|
||||
|
||||
public int[] BlockDrawList = [];
|
||||
public int BlockDrawCount;
|
||||
|
||||
public WalkLandBlock? BlockAt(int gridX, int gridY)
|
||||
=> gridX >= 0 && gridX < MidWidth && gridY >= 0 && gridY < MidWidth
|
||||
? Blocks[gridX * MidWidth + gridY]
|
||||
: null;
|
||||
|
||||
/// <summary><c>LScape::get_block_order</c> + per-block
|
||||
/// <c>calc_draw_order</c>: rebuild the near-to-far block list and each
|
||||
/// block's far-to-near cell order for the current viewer placement.</summary>
|
||||
public void CalcDrawOrder()
|
||||
{
|
||||
if (BlockDrawList.Length < MidWidth * MidWidth)
|
||||
BlockDrawList = new int[MidWidth * MidWidth];
|
||||
BlockDrawCount = LandWalkOrder.GetBlockOrder(
|
||||
ViewerBlockX, ViewerBlockY, MidWidth, BlockDrawList);
|
||||
|
||||
for (int x = 0; x < MidWidth; x++)
|
||||
{
|
||||
for (int y = 0; y < MidWidth; y++)
|
||||
{
|
||||
WalkLandBlock? block = Blocks[x * MidWidth + y];
|
||||
if (block is null) continue;
|
||||
block.EnsureCellArrays();
|
||||
LandDirection dir = LandWalkOrder.GetDirection(
|
||||
x - ViewerBlockX, y - ViewerBlockY);
|
||||
(int cx, int cy) = LandWalkOrder.ClosestCell(
|
||||
dir, ViewerCellX, ViewerCellY, block.SideCellCount);
|
||||
// Retail's early-out: same closest cell keeps the stale order
|
||||
// (the dir field is NOT compared — keep the quirk).
|
||||
if (cx == block.ClosestX && cy == block.ClosestY) continue;
|
||||
block.ClosestX = cx;
|
||||
block.ClosestY = cy;
|
||||
LandWalkOrder.FillCellOrderFarToNear(
|
||||
cx, cy, block.SideCellCount,
|
||||
block.DrawArray.AsSpan(0, block.SideCellCount * block.SideCellCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>LScape::draw_check_blocks</c> @0x00505f80 + <c>landcell_check</c>
|
||||
/// @0x005050a0: clear all visibility, then for EACH view of the active
|
||||
/// portal view union block/cell visibility from clip-height interval
|
||||
/// grids at 192 m block / 24 m cell pitch in viewer-block-relative
|
||||
/// coordinates. A block/cell marked by an earlier view is never
|
||||
/// downgraded (blocks are only written non-Outside; cells are skipped
|
||||
/// once in view).
|
||||
/// </summary>
|
||||
public void CheckBlocks(in WalkPlane cyPlane, WalkPortalView activeViews)
|
||||
{
|
||||
foreach (WalkLandBlock? block in Blocks)
|
||||
{
|
||||
if (block is null) continue;
|
||||
block.EnsureCellArrays();
|
||||
block.InView = WalkBoundingType.Outside;
|
||||
Array.Clear(block.CellInView, 0, block.SideCellCount * block.SideCellCount);
|
||||
}
|
||||
|
||||
int viewCount = activeViews.ViewCount;
|
||||
Span<float> boundsScratch = stackalloc float[32];
|
||||
// Corner interval grids: 2 rolling rows of (MidWidth+1) corners,
|
||||
// each corner a plane-classification vector.
|
||||
int cornerRow = MidWidth + 1;
|
||||
var intervals = new float[2 * cornerRow][];
|
||||
for (int i = 0; i < intervals.Length; i++) intervals[i] = new float[32];
|
||||
|
||||
int v = 0;
|
||||
while (true)
|
||||
{
|
||||
WalkPlane[] edgePlanes;
|
||||
int edgeCount;
|
||||
bool last;
|
||||
if (viewCount == 0)
|
||||
{
|
||||
edgePlanes = [];
|
||||
edgeCount = 0;
|
||||
last = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
WalkViewPoly poly = activeViews.View.Polys[v];
|
||||
edgePlanes = new WalkPlane[poly.VertexCount];
|
||||
for (int k = 0; k < poly.VertexCount; k++)
|
||||
edgePlanes[k] = activeViews.View.Vertices[poly.VertexIndex + k].Plane;
|
||||
edgeCount = poly.VertexCount;
|
||||
v++;
|
||||
last = v == viewCount;
|
||||
}
|
||||
|
||||
// Seed the west column (grid x = 0) into parity row 0.
|
||||
for (int j = 0; j <= MidWidth; j++)
|
||||
WalkVisibilityMath.FillClipHeights(
|
||||
(0 - ViewerBlockX) * BlockLength,
|
||||
(j - ViewerBlockY) * BlockLength,
|
||||
cyPlane, edgePlanes, intervals[j]);
|
||||
for (int bx = 0; bx < MidWidth; bx++)
|
||||
{
|
||||
int westRow = (bx & 1) * cornerRow;
|
||||
int eastRow = ((bx - 1) & 1) * cornerRow;
|
||||
for (int j = 0; j <= MidWidth; j++)
|
||||
WalkVisibilityMath.FillClipHeights(
|
||||
(bx + 1 - ViewerBlockX) * BlockLength,
|
||||
(j - ViewerBlockY) * BlockLength,
|
||||
cyPlane, edgePlanes, intervals[eastRow + j]);
|
||||
for (int by = 0; by < MidWidth; by++)
|
||||
{
|
||||
WalkLandBlock? block = Blocks[bx * MidWidth + by];
|
||||
if (block is null) continue;
|
||||
WalkBoundingType bt = WalkVisibilityMath.BlockCheck(
|
||||
intervals[westRow + by], intervals[westRow + by + 1],
|
||||
intervals[eastRow + by], intervals[eastRow + by + 1],
|
||||
edgeCount, block.MaxZ, block.MinZ);
|
||||
if (bt != WalkBoundingType.Outside)
|
||||
{
|
||||
block.InView = bt;
|
||||
LandCellCheck(block, bx, by, cyPlane, edgePlanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (last) return;
|
||||
}
|
||||
}
|
||||
|
||||
// landcell_check @0x005050a0: non-8×8 far blocks mark all cells PARTIAL;
|
||||
// ENTIRELY_INSIDE blocks mark all cells 2; else per-cell corner grids at
|
||||
// 24 m pitch, skipping cells already in view (union across views).
|
||||
private void LandCellCheck(
|
||||
WalkLandBlock block, int bx, int by,
|
||||
in WalkPlane cyPlane, WalkPlane[] edgePlanes)
|
||||
{
|
||||
int n = block.SideCellCount;
|
||||
if (n != 8)
|
||||
{
|
||||
for (int i = 0; i < n * n; i++)
|
||||
block.CellInView[i] = WalkBoundingType.PartiallyInside;
|
||||
return;
|
||||
}
|
||||
if (block.InView == WalkBoundingType.EntirelyInside)
|
||||
{
|
||||
for (int i = 0; i < n * n; i++)
|
||||
block.CellInView[i] = WalkBoundingType.EntirelyInside;
|
||||
return;
|
||||
}
|
||||
float x0 = (bx - ViewerBlockX) * BlockLength;
|
||||
float y0 = (by - ViewerBlockY) * BlockLength;
|
||||
int cornerRow = n + 1;
|
||||
var grid = new float[2 * cornerRow][];
|
||||
for (int i = 0; i < grid.Length; i++) grid[i] = new float[32];
|
||||
for (int j = 0; j <= n; j++)
|
||||
WalkVisibilityMath.FillClipHeights(
|
||||
x0, j * CellLength + y0, cyPlane, edgePlanes, grid[j]);
|
||||
for (int cx = 0; cx < n; cx++)
|
||||
{
|
||||
int westRow = (cx & 1) * cornerRow;
|
||||
int eastRow = ((cx - 1) & 1) * cornerRow;
|
||||
for (int j = 0; j <= n; j++)
|
||||
WalkVisibilityMath.FillClipHeights(
|
||||
(cx + 1) * CellLength + x0, j * CellLength + y0,
|
||||
cyPlane, edgePlanes, grid[eastRow + j]);
|
||||
for (int cy = 0; cy < n; cy++)
|
||||
{
|
||||
if (block.CellInView[n * cx + cy] != WalkBoundingType.Outside)
|
||||
continue; // union across views: never downgrade
|
||||
block.CellInView[n * cx + cy] = WalkVisibilityMath.BlockCheck(
|
||||
grid[westRow + cy], grid[westRow + cy + 1],
|
||||
grid[eastRow + cy], grid[eastRow + cy + 1],
|
||||
edgePlanes.Length, block.MaxZ, block.MinZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.1 — the production sibling of the FW1 conformance harness's
|
||||
/// <c>WalkLandscapeDatBuilder</c>
|
||||
/// (tests/AcDream.App.Tests/Rendering/Walk/WalkLandscapeDatBuilder.cs): owns
|
||||
/// retail's viewer-centred block grid (LScape <c>mid_radius</c> = 25 → a
|
||||
/// 51×51 window, recon 2026-08-30) and its LOD ring pyramid
|
||||
/// (<see cref="SideCellCountForRing"/>).
|
||||
///
|
||||
/// Unlike the test builder — which re-scans the whole grid from a
|
||||
/// <c>DatCollection</c> on every call — this class is fed INCREMENTALLY from
|
||||
/// landblock commit/retire (<see cref="PublishLandblock"/>/
|
||||
/// <see cref="RetireLandblock"/>), the same lifecycle
|
||||
/// <c>Wb.BuildingRegistry</c> and <c>CellVisibility</c> already follow. Each
|
||||
/// landblock's z-slab and building placements are computed ONCE, on the
|
||||
/// streaming worker thread, at landblock-build time
|
||||
/// (<c>LandblockBuildFactory</c>/<c>WalkBuildingFactory</c>) — publishing
|
||||
/// here is a pure dictionary write, no DAT or GfxObj work.
|
||||
///
|
||||
/// <see cref="SetViewer"/> recentres the grid on the camera's block, porting
|
||||
/// the test builder's viewer-cell recompute; it goes further by also
|
||||
/// reflowing the 51×51 window when the camera crosses into a different
|
||||
/// landblock (the test builder never needs this — every moving fixture stays
|
||||
/// within its anchor block). Reflow only touches already-published data (a
|
||||
/// dictionary lookup per slot); no DAT access ever happens here.
|
||||
///
|
||||
/// No frame currently calls <see cref="SetViewer"/> or reads
|
||||
/// <see cref="Landscape"/> — FW3.2 wires <c>RetailFrameWalk</c> into the
|
||||
/// render loop. This class exists now so that wiring is additive, and so the
|
||||
/// FW3.1 conformance gate can exercise the SAME assembly code path
|
||||
/// production will drive.
|
||||
/// </summary>
|
||||
public sealed class WalkLandscapeAssembler
|
||||
{
|
||||
/// <summary>Retail LScape <c>mid_radius</c> (recon 2026-08-30) — NOT
|
||||
/// acdream's streaming radius (two-tier N1/N2), which is smaller. Blocks
|
||||
/// outside the streamed window simply stay unpublished (null slots).</summary>
|
||||
public const int MidRadius = 25;
|
||||
|
||||
public const int GridWidth = MidRadius * 2 + 1;
|
||||
|
||||
private sealed class BlockData
|
||||
{
|
||||
public required float MaxZ;
|
||||
public required float MinZ;
|
||||
public required IReadOnlyList<WalkBuildingFactory.Entry> Buildings;
|
||||
}
|
||||
|
||||
private readonly Dictionary<(int X, int Y), BlockData> _blocks = new();
|
||||
private int _viewerBlockX = int.MinValue;
|
||||
private int _viewerBlockY = int.MinValue;
|
||||
|
||||
public WalkLandscape Landscape { get; } = new()
|
||||
{
|
||||
MidWidth = GridWidth,
|
||||
Blocks = new WalkLandBlock?[GridWidth * GridWidth],
|
||||
ViewerBlockX = MidRadius,
|
||||
ViewerBlockY = MidRadius,
|
||||
};
|
||||
|
||||
/// <summary>Landblock commit
|
||||
/// (<c>LandblockRenderPublisher.AdvanceCompleteOne</c>'s
|
||||
/// <c>BuildingRegistryCommitted</c> step): register/replace this
|
||||
/// landblock's z-slab and building placements. If the landblock falls
|
||||
/// within the CURRENT window, the visible slot refreshes immediately.</summary>
|
||||
public void PublishLandblock(
|
||||
uint landblockId, float maxZ, float minZ,
|
||||
IReadOnlyList<WalkBuildingFactory.Entry> buildings)
|
||||
{
|
||||
(int bx, int by) = BlockCoords(landblockId);
|
||||
_blocks[(bx, by)] = new BlockData { MaxZ = maxZ, MinZ = minZ, Buildings = buildings };
|
||||
RefreshSlotIfWindowed(bx, by);
|
||||
}
|
||||
|
||||
/// <summary>Landblock retirement (<c>LandblockRenderPublisher
|
||||
/// .RemoveBuildingRegistry</c>): drop this landblock's data. Safe to call
|
||||
/// on a landblock that never published (no-op).</summary>
|
||||
public void RetireLandblock(uint landblockId)
|
||||
{
|
||||
(int bx, int by) = BlockCoords(landblockId);
|
||||
_blocks.Remove((bx, by));
|
||||
RefreshSlotIfWindowed(bx, by);
|
||||
}
|
||||
|
||||
/// <summary>Recentres the grid on the camera's block and updates the
|
||||
/// sub-block cell offsets (retail's viewer recentre —
|
||||
/// <c>WalkLandscapeDatBuilder.SetViewer</c>'s port). When the camera's
|
||||
/// block hasn't changed since the last call, this only updates
|
||||
/// <see cref="WalkLandscape.ViewerCellX"/>/<see cref="WalkLandscape.ViewerCellY"/>
|
||||
/// — zero allocation, matching the test builder's lightweight path. A
|
||||
/// block crossing additionally reflows the 51×51 window from already-
|
||||
/// published data (a bounded, allocation-free dictionary-lookup pass —
|
||||
/// no DAT/GfxObj work, which already happened at publish time).</summary>
|
||||
public void SetViewer(uint cameraCellId, Vector3 cameraOrigin)
|
||||
{
|
||||
int cameraBlockX = (int)(cameraCellId >> 24);
|
||||
int cameraBlockY = (int)((cameraCellId >> 16) & 0xFF);
|
||||
if (cameraBlockX != _viewerBlockX || cameraBlockY != _viewerBlockY)
|
||||
{
|
||||
_viewerBlockX = cameraBlockX;
|
||||
_viewerBlockY = cameraBlockY;
|
||||
RebuildWindow();
|
||||
}
|
||||
|
||||
uint low = cameraCellId & 0xFFFFu;
|
||||
if (low >= 1 && low <= 0x40)
|
||||
{
|
||||
int cellIndex = (int)low - 1;
|
||||
Landscape.ViewerCellX = cellIndex / 8;
|
||||
Landscape.ViewerCellY = cellIndex % 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interior camera: derive the outside-projected landcell from
|
||||
// the camera origin, matching the test builder's fallback
|
||||
// (Position::get_outside_cell_id via SmartBox::RenderNormalMode's
|
||||
// seen_outside arm).
|
||||
Landscape.ViewerCellX = Math.Clamp((int)MathF.Floor(cameraOrigin.X / 24f), 0, 7);
|
||||
Landscape.ViewerCellY = Math.Clamp((int)MathF.Floor(cameraOrigin.Y / 24f), 0, 7);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary><c>ring <= 1 ? 8 : ring == 2 ? 4 : ring <= 4 ? 2 : 1</c>
|
||||
/// — the live-observed resolution pyramid (recon 2026-08-30): 8×8 in the
|
||||
/// 3×3 core, 4×4 at ring 2, 2×2 at rings 3–4, 1×1 beyond. Buildings only
|
||||
/// attach at full resolution (ring ≤ 1) — the traces show no BLD beyond
|
||||
/// ±1 block.</summary>
|
||||
internal static int SideCellCountForRing(int ring)
|
||||
=> ring <= 1 ? 8 : ring == 2 ? 4 : ring <= 4 ? 2 : 1;
|
||||
|
||||
internal static int RingOf(int gridX, int gridY)
|
||||
=> Math.Max(Math.Abs(gridX - MidRadius), Math.Abs(gridY - MidRadius));
|
||||
|
||||
private void RebuildWindow()
|
||||
{
|
||||
for (int gx = 0; gx < GridWidth; gx++)
|
||||
{
|
||||
for (int gy = 0; gy < GridWidth; gy++)
|
||||
{
|
||||
int bx = _viewerBlockX + gx - MidRadius;
|
||||
int by = _viewerBlockY + gy - MidRadius;
|
||||
Landscape.Blocks[gx * GridWidth + gy] = BuildSlot(bx, by, gx, gy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshSlotIfWindowed(int bx, int by)
|
||||
{
|
||||
if (_viewerBlockX == int.MinValue)
|
||||
return; // SetViewer has never run — no window to refresh yet.
|
||||
int gx = bx - _viewerBlockX + MidRadius;
|
||||
int gy = by - _viewerBlockY + MidRadius;
|
||||
if (gx < 0 || gx >= GridWidth || gy < 0 || gy >= GridWidth)
|
||||
return;
|
||||
Landscape.Blocks[gx * GridWidth + gy] = BuildSlot(bx, by, gx, gy);
|
||||
}
|
||||
|
||||
private WalkLandBlock? BuildSlot(int bx, int by, int gx, int gy)
|
||||
{
|
||||
if (bx < 0 || bx > 0xFF || by < 0 || by > 0xFF)
|
||||
return null;
|
||||
if (!_blocks.TryGetValue((bx, by), out BlockData? data))
|
||||
return null;
|
||||
|
||||
int sideCellCount = SideCellCountForRing(RingOf(gx, gy));
|
||||
var block = new WalkLandBlock
|
||||
{
|
||||
// Additive (Campaign FW3.2b-1): same (bx<<24 | by<<16) encoding
|
||||
// BlockCoords decodes below — see WalkLandBlock.LandblockId.
|
||||
LandblockId = (uint)bx << 24 | (uint)by << 16,
|
||||
SideCellCount = sideCellCount,
|
||||
MaxZ = data.MaxZ,
|
||||
MinZ = data.MinZ,
|
||||
};
|
||||
block.EnsureCellArrays();
|
||||
|
||||
if (sideCellCount == 8)
|
||||
{
|
||||
foreach (WalkBuildingFactory.Entry entry in data.Buildings)
|
||||
{
|
||||
int cellIndex = (int)(entry.Building.PositionCellId & 0xFFFFu) - 1;
|
||||
if (cellIndex >= 0 && cellIndex < 64)
|
||||
block.CellBuildings[cellIndex] = entry.Building;
|
||||
}
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
private static (int X, int Y) BlockCoords(uint landblockId)
|
||||
=> ((int)((landblockId >> 24) & 0xFFu), (int)((landblockId >> 16) & 0xFFu));
|
||||
}
|
||||
|
|
@ -1,462 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW1 — retail <c>PView</c>'s interior flood, transcribed from the
|
||||
/// flood-read appendix (docs/research/2026-08-30-fw-flood-pseudocode-appendix.md)
|
||||
/// plus the raw bodies re-read for AddToCell @0x005a4d90, AdjustDrawList
|
||||
/// @0x005a4e90, AdjustCellPlace @0x005a5010, FixCellList @0x005a5250,
|
||||
/// AdjustCellView @0x005a5770, and ConstructView @0x005a57b0.
|
||||
///
|
||||
/// PORTAL-FLAG CONVENTION (resolved 2026-08-30 — the PDB names mislead):
|
||||
/// <c>inflag</c> (+4) == 1 marks a portal whose polygon FACES the viewer as
|
||||
/// a visible surface (it feeds max_indist); <c>inflag</c> == 0 with
|
||||
/// <c>seen</c> (+0) == 1 marks an OPENING on the viewer's look-through side
|
||||
/// (InitCell's <c>side == portal_side</c> arm — the same side condition
|
||||
/// <c>ConstructView(CBldPortal)</c> REQUIRES for building look-ins).
|
||||
/// ClipPortals' traversal gate <c>seen != 0 && inflag != 1</c>
|
||||
/// therefore selects exactly the armed openings; the entry portal is forced
|
||||
/// {seen=1, inflag=1} so the flood never walks back out of it. Transcribe;
|
||||
/// do not "fix" the names.
|
||||
/// </summary>
|
||||
public sealed class WalkPView
|
||||
{
|
||||
private static int _masterTimestamp;
|
||||
|
||||
private readonly struct TodoEntry(WalkCell cell, float dist)
|
||||
{
|
||||
public readonly WalkCell Cell = cell;
|
||||
public readonly float Dist = dist;
|
||||
}
|
||||
|
||||
public readonly WalkPortalView OutsideView = new();
|
||||
public readonly List<WalkCell> CellDrawList = new();
|
||||
private readonly List<TodoEntry> _todo = new();
|
||||
private readonly WalkPortalView _otherPortalScratch = new(); // retail's static temp_view
|
||||
private Vector2[] _activeViewVerts = new Vector2[32];
|
||||
private int _activeViewVertCount;
|
||||
private readonly WalkScreenPoint[] _projectScratch = new WalkScreenPoint[64];
|
||||
private readonly WalkScreenPoint[] _clipScratch = new WalkScreenPoint[64];
|
||||
|
||||
/// <summary>Retail <c>PView.draw_landscape</c>: exit portals raise
|
||||
/// <see cref="OutsideView"/> only when set.</summary>
|
||||
public bool DrawLandscape = true;
|
||||
|
||||
/// <summary>Retail <c>Render::PortalList</c> as ClipPortals installs it —
|
||||
/// the last processed cell's top view (consumed by the landscape draw).</summary>
|
||||
public WalkPortalView? PortalList { get; private set; }
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// ConstructView (CEnvCell overload) @0x005a57b0 — the flood.
|
||||
// ------------------------------------------------------------------
|
||||
public void ConstructView(WalkCell seed, int throughPortalIndex, IWalkFrameContext ctx)
|
||||
{
|
||||
OutsideView.ResetForPush(); // outside_view.view_count = 0 (counters only)
|
||||
_masterTimestamp++;
|
||||
_todo.Clear();
|
||||
CellDrawList.Clear();
|
||||
InitCell(seed, throughPortalIndex, ctx);
|
||||
InsCellTodoList(seed, 0f);
|
||||
while (_todo.Count > 0)
|
||||
{
|
||||
WalkCell cell = _todo[^1].Cell;
|
||||
_todo.RemoveAt(_todo.Count - 1);
|
||||
CellDrawList.Add(cell);
|
||||
cell.TopView.CellViewDone = true;
|
||||
if (ClipPortals(cell, 0, ctx))
|
||||
AddViewToPortals(cell, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// InitCell @0x005a4b70 (Ghidra-verified raw re-read 2026-08-30).
|
||||
// ------------------------------------------------------------------
|
||||
public bool InitCell(WalkCell cell, int entryPortalIndex, IWalkFrameContext ctx)
|
||||
{
|
||||
WalkPortalView top = cell.TopView;
|
||||
if (top.ViewCount == 0) return false;
|
||||
|
||||
Vector3 viewpoint = ctx.ViewpointIn(cell);
|
||||
top.CellViewDone = false;
|
||||
top.ViewTimestamp = _masterTimestamp;
|
||||
if (top.PortalFlags.Length < cell.Portals.Length)
|
||||
top.PortalFlags = new WalkPortalFlags[cell.Portals.Length];
|
||||
|
||||
float maxDistSquared = 0f;
|
||||
bool anyLookThrough = false; // retail var_4 (uninitialized there; benign — init false)
|
||||
|
||||
for (int i = 0; i < cell.Portals.Length; i++)
|
||||
{
|
||||
ref WalkPortalFlags flags = ref top.PortalFlags[i];
|
||||
WalkPolygon poly = cell.PortalPolygons[cell.Portals[i].PolygonIndex];
|
||||
|
||||
if (i == entryPortalIndex && !flags.InView)
|
||||
{
|
||||
// Entered-through portal: forced facing + armed — never re-traversed.
|
||||
flags.InView = true;
|
||||
flags.Seen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
flags.Seen = false;
|
||||
float d = Vector3.Dot(poly.Plane.Normal, viewpoint) + poly.Plane.D;
|
||||
if (d <= WalkVisibilityMath.Epsilon && d >= -WalkVisibilityMath.Epsilon)
|
||||
{
|
||||
flags.InView = false; // IN_PLANE: neither surface nor opening
|
||||
anyLookThrough = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int side = d > WalkVisibilityMath.Epsilon ? 0 : 1;
|
||||
if (side == cell.Portals[i].PortalSide)
|
||||
{
|
||||
flags.InView = false; // viewer on the look-through side (opening)
|
||||
anyLookThrough = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
flags.InView = true; // portal polygon faces the viewer
|
||||
}
|
||||
}
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.WalkPortalProbeThisFrame)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[walk-portal] init cell={cell.CellId:x8} i={i} "
|
||||
+ $"dest={cell.Portals[i].OtherCellId:x8} d={d:F3} "
|
||||
+ $"pside={cell.Portals[i].PortalSide} "
|
||||
+ $"inview={(flags.InView ? 1 : 0)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.InView)
|
||||
{
|
||||
foreach (Vector3 v in poly.Vertices)
|
||||
{
|
||||
float dx = viewpoint.X - v.X;
|
||||
float dy = viewpoint.Y - v.Y;
|
||||
float dz = viewpoint.Z - v.Z;
|
||||
float d2 = dx * dx + dy * dy + dz * dz;
|
||||
if (d2 > maxDistSquared) maxDistSquared = d2;
|
||||
}
|
||||
}
|
||||
}
|
||||
top.MaxInDistSquared = maxDistSquared;
|
||||
|
||||
// Fixup: arm every opening (retail runs this once per view with a
|
||||
// vestigial set_view per pass; the flag writes are idempotent).
|
||||
if (anyLookThrough && top.ViewCount > 0)
|
||||
{
|
||||
for (int j = 0; j < cell.Portals.Length; j++)
|
||||
{
|
||||
ref WalkPortalFlags flags = ref top.PortalFlags[j];
|
||||
if (!flags.InView && !flags.Seen) flags.Seen = true;
|
||||
}
|
||||
}
|
||||
|
||||
top.UpdateCount = top.ViewCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// InsCellTodoList @0x005a4f50 — descending by dist from index 0;
|
||||
// END = nearest; pop-from-END = nearest-first (the Ghidra-corrected
|
||||
// polarity: the sink stops under the first STRICTLY greater key).
|
||||
// ------------------------------------------------------------------
|
||||
public void InsCellTodoList(WalkCell cell, float dist)
|
||||
{
|
||||
int pos = _todo.Count;
|
||||
while (pos > 0 && !(dist < _todo[pos - 1].Dist))
|
||||
pos--;
|
||||
_todo.Insert(pos, new TodoEntry(cell, dist));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// ClipPortals @0x005a5520.
|
||||
// ------------------------------------------------------------------
|
||||
public bool ClipPortals(WalkCell cell, int startView, IWalkFrameContext ctx)
|
||||
{
|
||||
WalkPortalView top = cell.TopView;
|
||||
PortalList = top; // set BEFORE any early-out (retail)
|
||||
if (cell.Portals.Length <= 0) return false;
|
||||
|
||||
// Pass 1: which portals are live; resolve+cache neighbors.
|
||||
bool anyLive = false;
|
||||
for (int j = 0; j < cell.Portals.Length; j++)
|
||||
{
|
||||
ref WalkPortalFlags flags = ref top.PortalFlags[j];
|
||||
if (!flags.Seen || flags.InView) continue;
|
||||
ref WalkCellPortal portal = ref cell.Portals[j];
|
||||
if (cell.CachedNeighbors[j] is null && portal.OtherCellId != 0xFFFFFFFFu)
|
||||
{
|
||||
cell.CachedNeighbors[j] = ctx.GetVisible(portal.OtherCellId);
|
||||
if (cell.CachedNeighbors[j] is null) continue; // not loaded: silently dead
|
||||
}
|
||||
anyLive = true;
|
||||
}
|
||||
if (!anyLive) return false;
|
||||
|
||||
// Pass 2: clip every live portal against each view in the window.
|
||||
for (int i = startView; i < top.ViewCount; i++)
|
||||
{
|
||||
SetView(top, i);
|
||||
for (int j = 0; j < cell.Portals.Length; j++)
|
||||
{
|
||||
ref WalkPortalFlags flags = ref top.PortalFlags[j];
|
||||
if (!flags.Seen || flags.InView) continue;
|
||||
ref WalkCellPortal portal = ref cell.Portals[j];
|
||||
int n = GetClip(
|
||||
cell, portal.PortalSide,
|
||||
cell.PortalPolygons[portal.PolygonIndex],
|
||||
doClip: true, ctx, _clipScratch);
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.WalkPortalProbeThisFrame)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[walk-portal] clip cell={cell.CellId:x8} view={i} j={j} "
|
||||
+ $"dest={portal.OtherCellId:x8} n={n}");
|
||||
}
|
||||
if (n == 0) continue;
|
||||
|
||||
if (portal.OtherCellId == 0xFFFFFFFFu)
|
||||
{
|
||||
if (DrawLandscape)
|
||||
{
|
||||
if (ctx.ClipLandscape)
|
||||
WalkCopyView.Append(
|
||||
OutsideView, _clipScratch.AsSpan(0, n),
|
||||
ctx.Rays, ctx.WorldViewpoint);
|
||||
else
|
||||
WalkCopyView.AppendFullViewportQuad(
|
||||
OutsideView, ctx.Rays, ctx.WorldViewpoint,
|
||||
ctx.ViewportWidth, ctx.ViewportHeight);
|
||||
}
|
||||
}
|
||||
else if (cell.CachedNeighbors[j] is WalkCell neighbor)
|
||||
{
|
||||
if (!portal.ExactMatch && portal.OtherPortalId >= 0)
|
||||
{
|
||||
n = OtherPortalClip(cell, j, n, ctx);
|
||||
SetView(top, i); // restore after the far-frame excursion
|
||||
if (n == 0) continue;
|
||||
}
|
||||
if (neighbor.NumView != 0)
|
||||
WalkCopyView.Append(
|
||||
neighbor.TopView, _clipScratch.AsSpan(0, n),
|
||||
ctx.Rays, ctx.WorldViewpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// OtherPortalClip @0x005a5400 — the double clip for non-exact_match
|
||||
// portals: snapshot the near-clipped poly as a temp view, then re-clip
|
||||
// against the FAR cell's own portal polygon with INVERTED sidedness
|
||||
// (a literal == 0 test, not an XOR).
|
||||
// ------------------------------------------------------------------
|
||||
private int OtherPortalClip(
|
||||
WalkCell cell, int portalIndex, int count, IWalkFrameContext ctx)
|
||||
{
|
||||
_otherPortalScratch.ResetForPush();
|
||||
if (!WalkCopyView.Append(
|
||||
_otherPortalScratch, _clipScratch.AsSpan(0, count),
|
||||
ctx.Rays, ctx.WorldViewpoint))
|
||||
return 0;
|
||||
ref WalkCellPortal portal = ref cell.Portals[portalIndex];
|
||||
WalkCell far = cell.CachedNeighbors[portalIndex]
|
||||
?? throw new InvalidOperationException(
|
||||
"OtherPortalClip requires a resolved neighbor (ClipPortals pass 1 contract).");
|
||||
ref WalkCellPortal farPortal = ref far.Portals[portal.OtherPortalId];
|
||||
SetView(_otherPortalScratch, 0);
|
||||
return GetClip(
|
||||
far, farPortal.PortalSide == 0 ? 1 : 0,
|
||||
far.PortalPolygons[farPortal.PolygonIndex],
|
||||
doClip: true, ctx, _clipScratch);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// AddViewToPortals @0x005a52d0.
|
||||
// ------------------------------------------------------------------
|
||||
public void AddViewToPortals(WalkCell cell, IWalkFrameContext ctx)
|
||||
{
|
||||
for (int j = 0; j < cell.Portals.Length; j++)
|
||||
{
|
||||
ref WalkCellPortal portal = ref cell.Portals[j];
|
||||
WalkCell? neighbor = cell.CachedNeighbors[j];
|
||||
ref WalkPortalFlags flags = ref cell.TopView.PortalFlags[j];
|
||||
if (neighbor is null || flags.InView || !flags.Seen || neighbor.NumView == 0)
|
||||
continue;
|
||||
WalkPortalView neighborTop = neighbor.TopView;
|
||||
if (neighborTop.ViewCount == 0) continue;
|
||||
|
||||
if (neighborTop.UpdateCount == 0)
|
||||
{
|
||||
// First touch this flood: schedule the neighbor.
|
||||
if (InitCell(neighbor, ToEntryIndex(portal.OtherPortalId), ctx))
|
||||
InsCellTodoList(neighbor, neighborTop.MaxInDistSquared);
|
||||
}
|
||||
else if (neighborTop.UpdateCount != neighborTop.ViewCount)
|
||||
{
|
||||
// Duplicate reach with NEW views since last processed.
|
||||
AddToCell(neighbor, ToEntryIndex(portal.OtherPortalId));
|
||||
if (neighborTop.CellViewDone)
|
||||
FixCellList(neighbor, cell, ctx);
|
||||
neighborTop.UpdateCount = neighborTop.ViewCount; // fresh re-read after recursion
|
||||
}
|
||||
else
|
||||
{
|
||||
continue; // nothing new; NO SetOtherSeen either
|
||||
}
|
||||
|
||||
if (portal.OtherPortalId >= 0) // full-width signed test (−1 sentinel skips)
|
||||
SetOtherSeen(cell, j);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Retail passes other_portal_id as a ZERO-EXTENDED 16-bit read:
|
||||
/// −1 becomes 0xFFFF, InitCell/AddToCell's never-matching sentinel.</summary>
|
||||
private static int ToEntryIndex(int otherPortalId)
|
||||
=> otherPortalId < 0 ? 0xFFFF : otherPortalId;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// AddToCell @0x005a4d90 — the duplicate-reach flag refresh over the
|
||||
// NEW views window only.
|
||||
// ------------------------------------------------------------------
|
||||
public void AddToCell(WalkCell cell, int entryPortalIndex)
|
||||
{
|
||||
WalkPortalView top = cell.TopView;
|
||||
for (int i = top.UpdateCount; i < top.ViewCount; i++)
|
||||
{
|
||||
// retail set_view(top.view, i) here is side-effect-only (vestigial)
|
||||
for (int j = 0; j < cell.Portals.Length; j++)
|
||||
{
|
||||
ref WalkPortalFlags flags = ref top.PortalFlags[j];
|
||||
if (j == entryPortalIndex && !flags.InView) flags.InView = true;
|
||||
if (!flags.InView && !flags.Seen) flags.Seen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// SetOtherSeen @0x005a4e30: fill the neighbor's backlink and arm its
|
||||
// back-portal when that portal faces the neighbor's viewer.
|
||||
// ------------------------------------------------------------------
|
||||
public void SetOtherSeen(WalkCell cell, int portalIndex)
|
||||
{
|
||||
WalkCell? neighbor = cell.CachedNeighbors[portalIndex];
|
||||
if (neighbor is null) return;
|
||||
int backIndex = cell.Portals[portalIndex].OtherPortalId;
|
||||
ref WalkCellPortal backPortal = ref neighbor.Portals[backIndex];
|
||||
if (neighbor.CachedNeighbors[backIndex] is null)
|
||||
neighbor.CachedNeighbors[backIndex] = cell;
|
||||
ref WalkPortalFlags backFlags = ref neighbor.TopView.PortalFlags[backIndex];
|
||||
if (backFlags.InView) backFlags.Seen = true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// FixCellList @0x005a5250 = AdjustCellPlace + AdjustCellView.
|
||||
// ------------------------------------------------------------------
|
||||
public void FixCellList(WalkCell moved, WalkCell reachedThrough, IWalkFrameContext ctx)
|
||||
{
|
||||
AdjustCellPlace(moved, reachedThrough);
|
||||
AdjustCellView(moved, ctx);
|
||||
}
|
||||
|
||||
// AdjustCellPlace @0x005a5010: re-place, then recurse through the
|
||||
// reaching cell's armed facing portals.
|
||||
private void AdjustCellPlace(WalkCell moved, WalkCell reachedThrough)
|
||||
{
|
||||
WalkPortalView top = reachedThrough.TopView;
|
||||
if (!AdjustDrawList(moved, reachedThrough)) return;
|
||||
for (int i = 0; i < reachedThrough.Portals.Length; i++)
|
||||
{
|
||||
ref WalkPortalFlags flags = ref top.PortalFlags[i];
|
||||
if (flags.Seen && flags.InView && reachedThrough.CachedNeighbors[i] is WalkCell next)
|
||||
AdjustCellPlace(reachedThrough, next);
|
||||
}
|
||||
}
|
||||
|
||||
// AdjustDrawList @0x005a4e90: if `moved` appears in the draw list BEFORE
|
||||
// `reachedThrough`, insert `reachedThrough` at `moved`'s slot (shifting
|
||||
// the range up) so the walk-from-the-end draws `reachedThrough` earlier
|
||||
// (farther). Appends `reachedThrough` when absent. Returns true when a
|
||||
// move happened.
|
||||
private bool AdjustDrawList(WalkCell moved, WalkCell reachedThrough)
|
||||
{
|
||||
for (int i = 0; i < CellDrawList.Count; i++)
|
||||
{
|
||||
uint id = CellDrawList[i].CellId;
|
||||
if (id == reachedThrough.CellId) break; // already earlier: nothing to do
|
||||
if (id != moved.CellId) continue;
|
||||
|
||||
int at = i;
|
||||
while (at < CellDrawList.Count && CellDrawList[at].CellId != reachedThrough.CellId)
|
||||
at++;
|
||||
if (at == CellDrawList.Count)
|
||||
CellDrawList.Add(reachedThrough); // conceptual append (grown below by the shift)
|
||||
for (int k = at; k > i; k--)
|
||||
CellDrawList[k] = CellDrawList[k - 1];
|
||||
CellDrawList[i] = reachedThrough;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// AdjustCellView @0x005a5770: incremental re-flood over the new-views
|
||||
// window only (the update_count watermark).
|
||||
private void AdjustCellView(WalkCell cell, IWalkFrameContext ctx)
|
||||
{
|
||||
if (ClipPortals(cell, cell.TopView.UpdateCount, ctx))
|
||||
AddViewToPortals(cell, ctx);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// set_view @0x0054d0e0 (the slice GetClip consumes: the installed
|
||||
// view's screen vertices).
|
||||
// ------------------------------------------------------------------
|
||||
public void SetView(WalkPortalView portalView, int polyIndex)
|
||||
{
|
||||
WalkViewPoly poly = portalView.View.Polys[polyIndex];
|
||||
if (_activeViewVerts.Length < poly.VertexCount)
|
||||
_activeViewVerts = new Vector2[poly.VertexCount];
|
||||
for (int k = 0; k < poly.VertexCount; k++)
|
||||
_activeViewVerts[k] = portalView.View.Vertices[poly.VertexIndex + k].Point;
|
||||
_activeViewVertCount = poly.VertexCount;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// PView::GetClip @0x005a4320: project, order by sidedness, optionally
|
||||
// clip against the installed view. Returns the surviving count (0 on
|
||||
// full rejection — the caller's pre-zero contract).
|
||||
// ------------------------------------------------------------------
|
||||
public int GetClip(
|
||||
WalkCell cell, int side, WalkPolygon polygon, bool doClip,
|
||||
IWalkFrameContext ctx, Span<WalkScreenPoint> output)
|
||||
{
|
||||
int n = polygon.Vertices.Length;
|
||||
Matrix4x4 objectToClip = ctx.ObjectToClip(cell);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
_projectScratch[i] = WalkScreenClip.TransformToScreen(
|
||||
polygon.Vertices[i], objectToClip, ctx.ViewportWidth, ctx.ViewportHeight);
|
||||
}
|
||||
// Sidedness != POSITIVE reverses the winding (IN_PLANE takes the
|
||||
// reversed branch too, though flood callers never pass it).
|
||||
if (side != 0)
|
||||
{
|
||||
for (int i = 0; i < n / 2; i++)
|
||||
(_projectScratch[i], _projectScratch[n - 1 - i])
|
||||
= (_projectScratch[n - 1 - i], _projectScratch[i]);
|
||||
}
|
||||
if (!doClip)
|
||||
{
|
||||
_projectScratch.AsSpan(0, n).CopyTo(output);
|
||||
return n;
|
||||
}
|
||||
return WalkScreenClip.ClipAgainstView(
|
||||
_projectScratch.AsSpan(0, n),
|
||||
_activeViewVerts.AsSpan(0, _activeViewVertCount),
|
||||
output);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.1 — the production <see cref="IWalkFrameContext"/> /
|
||||
/// <see cref="IRetailFrameWalkContext"/> / <see cref="IWalkBuildingFrameContext"/>
|
||||
/// implementation: cells resolve through the committed
|
||||
/// <see cref="CellVisibility"/> registry (<c>LoadedCell.Walk</c>, per the FW
|
||||
/// binding rule that the walk consumes ONLY the committed registry —
|
||||
/// <c>CellVisibility.TryGetCell</c>, never a synthetic/dead-code path);
|
||||
/// buildings resolve through <see cref="WalkBuildingRegistry"/>; the camera
|
||||
/// pose, projection, and viewport are supplied by the caller.
|
||||
///
|
||||
/// Deliberately NOT coupled to any concrete camera type — FW3.2 will supply
|
||||
/// live values from <c>WorldCameraFrame</c>; wiring that in is additive.
|
||||
/// The ray caster is a GENERIC inverse-view-projection unprojection, not the
|
||||
/// capture client's exact <c>Render::xinvscale</c>/<c>tx</c>/<c>vdst</c>
|
||||
/// constants the FW1 conformance harness's <c>WalkTraceReplayContext</c>
|
||||
/// uses — those are FIXTURE PINS specific to the 1024×720 capture client,
|
||||
/// not production values. This is safe because
|
||||
/// <see cref="WalkCopyView.Append"/> only ever CROSS-PRODUCTS these rays to
|
||||
/// build view-edge planes: a uniform scale or additive offset along a ray
|
||||
/// cancels out of every cross product it feeds, so any two points along the
|
||||
/// true eye ray (near/far unprojection) are observably equivalent to
|
||||
/// retail's exact construction for this contract.
|
||||
///
|
||||
/// One instance is a per-frame value (like <c>WalkTraceReplayContext</c>):
|
||||
/// construct fresh each frame with that frame's camera pose.
|
||||
/// </summary>
|
||||
public sealed class WalkProductionFrameContext : IWalkFrameContext, IRetailFrameWalkContext
|
||||
{
|
||||
/// <summary><c>Render::znear</c> @0x0081ec84 / <c>set_vdst</c> @0x0054b240.</summary>
|
||||
public const float ZNear = 0.1f;
|
||||
|
||||
private sealed class InverseViewProjectionRayCaster : IWalkRayCaster
|
||||
{
|
||||
private readonly Matrix4x4 _inverseViewProjection;
|
||||
private readonly float _viewportWidth;
|
||||
private readonly float _viewportHeight;
|
||||
|
||||
public InverseViewProjectionRayCaster(
|
||||
Matrix4x4 viewProjection, float viewportWidth, float viewportHeight)
|
||||
{
|
||||
if (!Matrix4x4.Invert(viewProjection, out _inverseViewProjection))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The walk's view-projection matrix must be invertible.",
|
||||
nameof(viewProjection));
|
||||
}
|
||||
_viewportWidth = viewportWidth;
|
||||
_viewportHeight = viewportHeight;
|
||||
}
|
||||
|
||||
/// <summary>Screen space: origin top-left, +Y down — matching
|
||||
/// <see cref="WalkScreenClip.TransformToScreen"/> and
|
||||
/// <see cref="WalkCopyView.AppendFullViewportQuad"/>'s quad
|
||||
/// winding.</summary>
|
||||
public Vector3 RayThrough(float screenX, float screenY)
|
||||
{
|
||||
float ndcX = screenX / _viewportWidth * 2f - 1f;
|
||||
float ndcY = 1f - screenY / _viewportHeight * 2f;
|
||||
Vector4 near = Vector4.Transform(
|
||||
new Vector4(ndcX, ndcY, 0f, 1f), _inverseViewProjection);
|
||||
Vector4 far = Vector4.Transform(
|
||||
new Vector4(ndcX, ndcY, 1f, 1f), _inverseViewProjection);
|
||||
Vector3 nearWorld = new Vector3(near.X, near.Y, near.Z) / near.W;
|
||||
Vector3 farWorld = new Vector3(far.X, far.Y, far.Z) / far.W;
|
||||
return farWorld - nearWorld;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CellVisibility _cells;
|
||||
private readonly WalkBuildingRegistry _buildings;
|
||||
private readonly Matrix4x4 _viewProjection;
|
||||
private readonly IWalkRayCaster _rays;
|
||||
|
||||
private Vector2[] _activeViewVerts = new Vector2[32];
|
||||
private int _activeViewVertCount;
|
||||
|
||||
public WalkProductionFrameContext(
|
||||
CellVisibility cells,
|
||||
WalkBuildingRegistry buildings,
|
||||
Vector3 worldViewpoint,
|
||||
Vector3 forward,
|
||||
Matrix4x4 viewProjection,
|
||||
float viewportWidth,
|
||||
float viewportHeight)
|
||||
{
|
||||
_cells = cells ?? throw new ArgumentNullException(nameof(cells));
|
||||
_buildings = buildings ?? throw new ArgumentNullException(nameof(buildings));
|
||||
WorldViewpoint = worldViewpoint;
|
||||
_viewProjection = viewProjection;
|
||||
ViewportWidth = viewportWidth;
|
||||
ViewportHeight = viewportHeight;
|
||||
_rays = new InverseViewProjectionRayCaster(viewProjection, viewportWidth, viewportHeight);
|
||||
// The retail CY near plane: N = forward, d = -dot(eye, forward) - znear.
|
||||
CyPlane = new WalkPlane(forward, -Vector3.Dot(worldViewpoint, forward) - ZNear);
|
||||
}
|
||||
|
||||
public Vector3 WorldViewpoint { get; }
|
||||
public float ViewportWidth { get; }
|
||||
public float ViewportHeight { get; }
|
||||
public WalkPlane CyPlane { get; }
|
||||
public IWalkRayCaster Rays => _rays;
|
||||
public IWalkFrameContext CellContext => this;
|
||||
|
||||
public Vector3 ViewpointIn(WalkCell cell)
|
||||
=> Vector3.Transform(WorldViewpoint, cell.InverseWorldTransform);
|
||||
|
||||
public Matrix4x4 ObjectToClip(WalkCell cell) => cell.WorldTransform * _viewProjection;
|
||||
|
||||
/// <summary>Resolves through the committed <see cref="CellVisibility"/>
|
||||
/// registry only — a missing/uncommitted cell returns null (retail's
|
||||
/// portal-skip behavior; the FW binding rule requires any such miss to
|
||||
/// be diagnostically counted under a flag rather than silently swallowed
|
||||
/// once a caller wires one up — this seam does not itself log, matching
|
||||
/// <see cref="IWalkFrameContext.GetVisible"/>'s documented contract).</summary>
|
||||
public WalkCell? GetVisible(uint cellId)
|
||||
=> _cells.TryGetCell(cellId, out LoadedCell? cell) ? cell?.Walk : null;
|
||||
|
||||
public void SetActiveView(WalkPortalView views, int index)
|
||||
{
|
||||
WalkViewPoly poly = views.View.Polys[index];
|
||||
if (_activeViewVerts.Length < poly.VertexCount)
|
||||
_activeViewVerts = new Vector2[poly.VertexCount];
|
||||
for (int k = 0; k < poly.VertexCount; k++)
|
||||
_activeViewVerts[k] = views.View.Vertices[poly.VertexIndex + k].Point;
|
||||
_activeViewVertCount = poly.VertexCount;
|
||||
}
|
||||
|
||||
public Vector3 ViewpointInBuilding(WalkBuilding building)
|
||||
=> Vector3.Transform(WorldViewpoint, GetEntry(building).InverseWorldTransform);
|
||||
|
||||
/// <summary><c>CPhysicsPart::UpdateViewerDistance</c> @0x0050e030: the
|
||||
/// distance to the part's SCALED sort center, not the position origin.</summary>
|
||||
public float ViewerDistanceTo(WalkBuilding building)
|
||||
{
|
||||
WalkBuildingFactory.Entry entry = GetEntry(building);
|
||||
return Vector3.Distance(
|
||||
WorldViewpoint, Vector3.Transform(building.SortCenter, entry.WorldTransform));
|
||||
}
|
||||
|
||||
public int ClipBuildingPolygon(
|
||||
WalkBuilding building, WalkPolygon polygon, int side, Span<WalkScreenPoint> output)
|
||||
{
|
||||
Matrix4x4 objectToClip = GetEntry(building).WorldTransform * _viewProjection;
|
||||
Span<WalkScreenPoint> projected = stackalloc WalkScreenPoint[polygon.Vertices.Length];
|
||||
for (int i = 0; i < polygon.Vertices.Length; i++)
|
||||
{
|
||||
projected[i] = WalkScreenClip.TransformToScreen(
|
||||
polygon.Vertices[i], objectToClip, ViewportWidth, ViewportHeight);
|
||||
}
|
||||
if (side != 0)
|
||||
projected.Reverse();
|
||||
return WalkScreenClip.ClipAgainstView(
|
||||
projected, _activeViewVerts.AsSpan(0, _activeViewVertCount), output);
|
||||
}
|
||||
|
||||
private WalkBuildingFactory.Entry GetEntry(WalkBuilding building)
|
||||
{
|
||||
if (!_buildings.TryGetEntry(building, out var entry))
|
||||
{
|
||||
// Fail loud (the PV3 post-mortem rule): a building the walk is
|
||||
// actively placing MUST be committed in the same registry the
|
||||
// walk was handed — a miss here is a walk/registry desync, never
|
||||
// a silently-skipped building.
|
||||
throw new InvalidOperationException(
|
||||
"WalkProductionFrameContext was asked to place a WalkBuilding " +
|
||||
"that is not committed in its WalkBuildingRegistry.");
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.2b-2: the production <see cref="IWalkFrameWorldData"/> over
|
||||
/// the retained scene (<see cref="RenderSceneQuery"/>) and the FW3.1
|
||||
/// <see cref="WalkBuildingRegistry"/>. Rebuilt facts per frame via
|
||||
/// <see cref="BeginFrame"/>:
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item>Cell statics — <see cref="RenderSceneQuery.CopyCellStaticsTo"/> on
|
||||
/// demand, one arena segment per distinct cell per frame (a cell can be
|
||||
/// visited once by the root flood OR once per admitting look-in portal; the
|
||||
/// per-frame cache keeps the copy single).</item>
|
||||
/// <item>Outdoor statics — ONE <see cref="RenderSceneQuery.CopyIndexTo"/>
|
||||
/// sweep bucketed by landscape cell id
|
||||
/// (<c>(lb & 0xFFFF0000) | (cellX*8 + cellY + 1)</c> from the record's
|
||||
/// world position — the same encoding <see cref="IWalkEventSink.OnLandscapeCellTurn"/>
|
||||
/// computes), EXCLUDING building shells (they draw at their building's own
|
||||
/// shell turn, retail <c>CPhysicsPart::Draw(parts, 0)</c> @0x0059f331, not
|
||||
/// at the cell's <c>DrawObjCell</c> turn).</item>
|
||||
/// <item>Building shells — the same sweep's <c>IsBuildingShell</c> records
|
||||
/// bucketed by <c>Source.BuildingShellAnchorCellId</c>; a
|
||||
/// <see cref="WalkBuilding"/> maps to its anchor via its first
|
||||
/// non-exit portal's destination (the SAME rule
|
||||
/// <c>LandblockLoader</c> used to author the anchor).</item>
|
||||
/// </list>
|
||||
///
|
||||
/// The tuple landblock id handed to the classifier is the frame's player
|
||||
/// landblock — the packed path's own convention for every
|
||||
/// <c>RenderFrameEntityDrawRequest</c>.
|
||||
///
|
||||
/// <para>Campaign FW3.4a: <see cref="GetCellStatics"/>, <see cref="GetOutdoorStatics"/>,
|
||||
/// and <see cref="GetBuildingShellStatics"/> used to materialize their result
|
||||
/// with <c>_cellScratch[..count]</c> / <c>[.. bucket]</c> — a FRESH
|
||||
/// <c>RenderProjectionRecord[]</c> allocation per distinct cell/anchor per
|
||||
/// frame. At a town-density frame (dozens of cells) that was the single
|
||||
/// largest contributor to the FW3.4 perf checkpoint's 14× frame-allocation
|
||||
/// regression (1.9 MB/frame p50). <see cref="_arena"/> replaces it: a
|
||||
/// grow-only buffer, reset to length 0 once per frame in
|
||||
/// <see cref="BeginFrame"/>, that every materialization call
|
||||
/// <see cref="AppendToArena"/>s its records into instead of snapshotting a
|
||||
/// new array — after the arena reaches its steady-state size (a few frames
|
||||
/// of warmup, same shape as <see cref="_sweepScratch"/>/<see cref="_cellScratch"/>'s
|
||||
/// existing grow-on-demand pattern), zero further heap allocation occurs
|
||||
/// here. Every <see cref="WalkFrameStaticRecords.Records"/> segment is
|
||||
/// STRICTLY per-frame scratch — nothing holds one across a frame boundary
|
||||
/// (the driver/populator consume it immediately, matching
|
||||
/// <see cref="_sweepScratch"/>'s existing lifetime contract) — so reusing the
|
||||
/// same backing array's memory next frame is safe.</para>
|
||||
/// </summary>
|
||||
internal sealed class WalkProductionWorldData : IWalkFrameWorldData
|
||||
{
|
||||
private readonly WalkBuildingRegistry _buildings;
|
||||
private RenderSceneQuery _scene;
|
||||
private uint _tupleLandblockId;
|
||||
private int _renderCenterLbX;
|
||||
private int _renderCenterLbY;
|
||||
|
||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _cellCache = new();
|
||||
private readonly Dictionary<uint, List<RenderProjectionRecord>> _outdoorByCell = new();
|
||||
private readonly Dictionary<uint, List<RenderProjectionRecord>> _shellsByAnchor = new();
|
||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _outdoorMaterialized = new();
|
||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _shellMaterialized = new();
|
||||
private RenderProjectionRecord[] _sweepScratch = new RenderProjectionRecord[1024];
|
||||
private RenderProjectionRecord[] _cellScratch = new RenderProjectionRecord[256];
|
||||
|
||||
// Campaign FW3.4a: the per-frame, grow-only materialization arena — see
|
||||
// this type's own doc comment.
|
||||
private RenderProjectionRecord[] _arena = new RenderProjectionRecord[4096];
|
||||
private int _arenaLength;
|
||||
|
||||
internal WalkProductionWorldData(WalkBuildingRegistry buildings)
|
||||
{
|
||||
_buildings = buildings ?? throw new ArgumentNullException(nameof(buildings));
|
||||
}
|
||||
|
||||
/// <summary>Rebuilds the frame's outdoor/shell buckets and clears the
|
||||
/// per-cell cache. Call once per frame before the driver runs.
|
||||
/// <paramref name="renderCenterLbX"/>/<paramref name="renderCenterLbY"/>
|
||||
/// are the streaming recenter origin: record positions are
|
||||
/// RENDER-ORIGIN-RELATIVE (each landblock's entities carry
|
||||
/// <c>(lbX − CenterX)·192</c> offsets), so mapping a position back to
|
||||
/// its TRUE landblock byte needs the center added back — the first
|
||||
/// connected gate of the FW3.2b-2 cutover shipped without this and most
|
||||
/// outdoor scenery landed in garbage buckets no walk turn ever reads.</summary>
|
||||
internal void BeginFrame(
|
||||
RenderSceneQuery scene,
|
||||
uint tupleLandblockId,
|
||||
int renderCenterLbX,
|
||||
int renderCenterLbY)
|
||||
{
|
||||
_scene = scene;
|
||||
_tupleLandblockId = tupleLandblockId;
|
||||
_renderCenterLbX = renderCenterLbX;
|
||||
_renderCenterLbY = renderCenterLbY;
|
||||
_cellCache.Clear();
|
||||
_outdoorMaterialized.Clear();
|
||||
_shellMaterialized.Clear();
|
||||
_arenaLength = 0;
|
||||
foreach (List<RenderProjectionRecord> bucket in _outdoorByCell.Values)
|
||||
bucket.Clear();
|
||||
foreach (List<RenderProjectionRecord> bucket in _shellsByAnchor.Values)
|
||||
bucket.Clear();
|
||||
|
||||
// CopyIndexTo THROWS on an undersized destination (ArchRenderScene
|
||||
// validates up front — the first connected gate run of the FW3.2b-2
|
||||
// cutover crashed on exactly this at Aerlinthe's 5,040 outdoor
|
||||
// statics), so presize from the query's own index counts.
|
||||
int required = _scene.IndexCounts.For(RenderSceneIndex.OutdoorStatic);
|
||||
if (required > _sweepScratch.Length)
|
||||
{
|
||||
_sweepScratch = new RenderProjectionRecord[
|
||||
Math.Max(required, _sweepScratch.Length * 2)];
|
||||
}
|
||||
int count = _scene.CopyIndexTo(RenderSceneIndex.OutdoorStatic, _sweepScratch);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ref readonly RenderProjectionRecord record = ref _sweepScratch[i];
|
||||
if (record.EntityPayload.IsBuildingShell)
|
||||
{
|
||||
uint anchor = record.Source.BuildingShellAnchorCellId;
|
||||
if (!_shellsByAnchor.TryGetValue(anchor, out List<RenderProjectionRecord>? shells))
|
||||
_shellsByAnchor[anchor] = shells = new List<RenderProjectionRecord>();
|
||||
shells.Add(record);
|
||||
continue;
|
||||
}
|
||||
uint cellId = LandscapeCellId(
|
||||
record.Transform.Position, _renderCenterLbX, _renderCenterLbY);
|
||||
if (!_outdoorByCell.TryGetValue(cellId, out List<RenderProjectionRecord>? bucket))
|
||||
_outdoorByCell[cellId] = bucket = new List<RenderProjectionRecord>();
|
||||
bucket.Add(record);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The landscape cell owning a RENDER-ORIGIN-RELATIVE position
|
||||
/// — retail's 24 m cell grid inside the 192 m landblock, producing the
|
||||
/// same TRUE <c>(lb & 0xFFFF0000) | (cellX*8 + cellY + 1)</c> encoding
|
||||
/// the walk's landscape turn emits: the relative block index
|
||||
/// (<c>floor(p/192)</c>) plus the streaming center recovers the true
|
||||
/// landblock byte, because entity positions carry
|
||||
/// <c>(lbX − CenterX)·192</c> world offsets
|
||||
/// (<c>LandblockBuildFactory</c>'s <c>worldOffset</c>).</summary>
|
||||
internal static uint LandscapeCellId(
|
||||
Vector3 relativePosition, int renderCenterLbX, int renderCenterLbY)
|
||||
{
|
||||
int relBlockX = (int)MathF.Floor(relativePosition.X / 192f);
|
||||
int relBlockY = (int)MathF.Floor(relativePosition.Y / 192f);
|
||||
float localX = relativePosition.X - relBlockX * 192f;
|
||||
float localY = relativePosition.Y - relBlockY * 192f;
|
||||
int cellX = Math.Clamp((int)(localX / 24f), 0, 7);
|
||||
int cellY = Math.Clamp((int)(localY / 24f), 0, 7);
|
||||
uint landblock =
|
||||
((uint)(byte)(renderCenterLbX + relBlockX) << 24)
|
||||
| ((uint)(byte)(renderCenterLbY + relBlockY) << 16);
|
||||
return landblock | (uint)(cellX * 8 + cellY + 1);
|
||||
}
|
||||
|
||||
public WalkFrameStaticRecords GetCellStatics(uint cellId)
|
||||
{
|
||||
if (_cellCache.TryGetValue(cellId, out WalkFrameStaticRecords cached))
|
||||
return cached;
|
||||
// Same up-front-validation contract as CopyIndexTo: presize from the
|
||||
// query's own count rather than probing with an undersized span.
|
||||
int required = _scene.GetCellStaticCount(cellId);
|
||||
if (required > _cellScratch.Length)
|
||||
{
|
||||
_cellScratch = new RenderProjectionRecord[
|
||||
Math.Max(required, _cellScratch.Length * 2)];
|
||||
}
|
||||
int count = _scene.CopyCellStaticsTo(cellId, _cellScratch);
|
||||
WalkFrameStaticRecords records = count == 0
|
||||
? WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId }
|
||||
: new WalkFrameStaticRecords(
|
||||
AppendToArena(_cellScratch.AsSpan(0, count)), _tupleLandblockId);
|
||||
_cellCache[cellId] = records;
|
||||
return records;
|
||||
}
|
||||
|
||||
public WalkFrameStaticRecords GetOutdoorStatics(uint cellId)
|
||||
{
|
||||
if (_outdoorMaterialized.TryGetValue(cellId, out WalkFrameStaticRecords cached))
|
||||
return cached;
|
||||
WalkFrameStaticRecords records =
|
||||
_outdoorByCell.TryGetValue(cellId, out List<RenderProjectionRecord>? bucket)
|
||||
&& bucket.Count > 0
|
||||
? new WalkFrameStaticRecords(
|
||||
AppendToArena(CollectionsMarshal.AsSpan(bucket)), _tupleLandblockId)
|
||||
: WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId };
|
||||
_outdoorMaterialized[cellId] = records;
|
||||
return records;
|
||||
}
|
||||
|
||||
public WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building)
|
||||
{
|
||||
uint anchor = AnchorCellId(building);
|
||||
if (anchor == 0)
|
||||
return WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId };
|
||||
if (_shellMaterialized.TryGetValue(anchor, out WalkFrameStaticRecords cached))
|
||||
return cached;
|
||||
WalkFrameStaticRecords records =
|
||||
_shellsByAnchor.TryGetValue(anchor, out List<RenderProjectionRecord>? shells)
|
||||
&& shells.Count > 0
|
||||
? new WalkFrameStaticRecords(
|
||||
AppendToArena(CollectionsMarshal.AsSpan(shells)), _tupleLandblockId)
|
||||
: WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId };
|
||||
_shellMaterialized[anchor] = records;
|
||||
return records;
|
||||
}
|
||||
|
||||
/// <summary>Copies <paramref name="source"/> into <see cref="_arena"/> at
|
||||
/// its current length, growing the arena first if needed (doubling, or
|
||||
/// exactly enough for an unusually large sweep — the same growth shape
|
||||
/// <see cref="_sweepScratch"/>/<see cref="_cellScratch"/> already use),
|
||||
/// and returns the segment the copy landed in. A prior frame's growth can
|
||||
/// leave an earlier-returned segment pointing at a retired backing array
|
||||
/// — harmless, since that array's content stays valid and nothing reads
|
||||
/// a segment across a frame boundary (see this type's own doc
|
||||
/// comment).</summary>
|
||||
private ArraySegment<RenderProjectionRecord> AppendToArena(
|
||||
ReadOnlySpan<RenderProjectionRecord> source)
|
||||
{
|
||||
if (source.Length == 0)
|
||||
return ArraySegment<RenderProjectionRecord>.Empty;
|
||||
|
||||
int required = _arenaLength + source.Length;
|
||||
if (required > _arena.Length)
|
||||
{
|
||||
var grown = new RenderProjectionRecord[Math.Max(required, _arena.Length * 2)];
|
||||
Array.Copy(_arena, grown, _arenaLength);
|
||||
_arena = grown;
|
||||
}
|
||||
|
||||
source.CopyTo(_arena.AsSpan(_arenaLength, source.Length));
|
||||
var segment = new ArraySegment<RenderProjectionRecord>(_arena, _arenaLength, source.Length);
|
||||
_arenaLength += source.Length;
|
||||
return segment;
|
||||
}
|
||||
|
||||
/// <summary>The building's authored shell anchor: its first non-exit
|
||||
/// portal's destination cell — the SAME rule <c>LandblockLoader</c> used
|
||||
/// when it stamped <c>BuildingShellAnchorCellId</c> on the shell entity.</summary>
|
||||
internal static uint AnchorCellId(WalkBuilding building)
|
||||
{
|
||||
foreach (ref readonly WalkBldPortal portal in building.Portals.AsSpan())
|
||||
{
|
||||
if (portal.OtherCellId != 0xFFFFFFFFu)
|
||||
return portal.OtherCellId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Matrix4x4 GetBuildingWorldTransform(WalkBuilding building)
|
||||
{
|
||||
if (!_buildings.TryGetEntry(building, out WalkBuildingFactory.Entry? entry))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"walk building 0x{building.PositionCellId:X8} has no committed registry entry");
|
||||
}
|
||||
return entry.WorldTransform;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>Vec2Dscreen</c>: homogeneous viewport coordinates as produced
|
||||
/// by <c>PrimD3DRender::xformStart</c> @0x0059b990 — X/Y are viewport-scaled
|
||||
/// but NOT perspective-divided (divide by W to get pixels), Z is raw clip z,
|
||||
/// W is raw clip w. <c>copy_view</c> performs the divide;
|
||||
/// <c>polyClipFinish</c> clips pre-divide homogeneously.
|
||||
/// </summary>
|
||||
public struct WalkScreenPoint
|
||||
{
|
||||
public float X, Y, Z, W;
|
||||
|
||||
public WalkScreenPoint(float x, float y, float z, float w)
|
||||
{
|
||||
X = x; Y = y; Z = z; W = w;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW1 — the screen-space projection/clip chain, ported from the
|
||||
/// flood-read appendix (docs/research/2026-08-30-fw-flood-pseudocode-appendix.md,
|
||||
/// report 3; Ghidra-arbitrated — BN's literal rendering inverts the edge
|
||||
/// inside test and the w-clip plane).
|
||||
/// </summary>
|
||||
public static class WalkScreenClip
|
||||
{
|
||||
/// <summary>The w-clip plane constant <c>cdstW</c> (= retail F_EPSILON).</summary>
|
||||
public const float MinW = WalkVisibilityMath.Epsilon;
|
||||
|
||||
/// <summary>
|
||||
/// <c>PrimD3DRender::xformStart</c> @0x0059b990 (toScreen path): object
|
||||
/// space → homogeneous viewport coordinates. x=(bw/2)(x_clip+w),
|
||||
/// y=(bh/2)(w−y_clip) — y flipped, origin top-left — z/w raw clip.
|
||||
/// <paramref name="objectToClip"/> is the concatenated object→clip
|
||||
/// matrix (row-vector convention, v * M).
|
||||
/// </summary>
|
||||
public static WalkScreenPoint TransformToScreen(
|
||||
Vector3 point, in Matrix4x4 objectToClip, float viewportWidth, float viewportHeight)
|
||||
{
|
||||
Vector4 clip = Vector4.Transform(new Vector4(point, 1f), objectToClip);
|
||||
return new WalkScreenPoint(
|
||||
clip.X * viewportWidth * 0.5f + clip.W * viewportWidth * 0.5f,
|
||||
clip.W * viewportHeight * 0.5f - clip.Y * viewportHeight * 0.5f,
|
||||
clip.Z,
|
||||
clip.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>ACRender::polyClipFinish</c> @0x006b6d00: Sutherland-Hodgman clip
|
||||
/// of a homogeneous screen polygon against the active view — first the
|
||||
/// w ≥ <see cref="MinW"/> plane (only when some w is below it), then
|
||||
/// every view edge, iterated LAST-to-FIRST as vertex pairs
|
||||
/// (v[0], v[n−1]), (v[n−1], v[n−2]), …, (v[1], v[0]). Edge INSIDE is
|
||||
/// side ≤ 0 with the homogeneous 2D cross
|
||||
/// side(p) = (p.x − a.x·p.w)·ey − (p.y − a.y·p.w)·ex. Each pass scans
|
||||
/// its input in REVERSE; the output keeps the ORIGINAL winding (retail's
|
||||
/// pass-parity bookkeeping collapses to reversing per pass and
|
||||
/// un-reversing at the end — this port appends reversed per pass and
|
||||
/// restores at the end, observably identical). Returns the surviving
|
||||
/// count, or 0 the moment any stage drops below 3 vertices — in which
|
||||
/// case <paramref name="output"/> content is unspecified (retail never
|
||||
/// writes the out count on that path; callers pre-zero it).
|
||||
/// </summary>
|
||||
public static int ClipAgainstView(
|
||||
ReadOnlySpan<WalkScreenPoint> input,
|
||||
ReadOnlySpan<Vector2> viewEdgeVertices,
|
||||
Span<WalkScreenPoint> output)
|
||||
{
|
||||
// Working buffers sized for retail's ≤32-vertex contract plus clip growth.
|
||||
Span<WalkScreenPoint> bufferA = stackalloc WalkScreenPoint[64];
|
||||
Span<WalkScreenPoint> bufferB = stackalloc WalkScreenPoint[64];
|
||||
Span<WalkScreenPoint> current = bufferA;
|
||||
int count = input.Length;
|
||||
input.CopyTo(current);
|
||||
// Track how many reversing passes ran so the final copy can restore
|
||||
// the original winding exactly as retail's parity dance does.
|
||||
int reversals = 0;
|
||||
|
||||
// Pass 0: the w-plane, only when some vertex is below cdstW.
|
||||
bool anyBelow = false;
|
||||
for (int i = 0; i < count; i++)
|
||||
if (current[i].W < MinW) { anyBelow = true; break; }
|
||||
if (anyBelow)
|
||||
{
|
||||
count = ClipPassW(current[..count], bufferB);
|
||||
if (count < 3) return 0;
|
||||
Span<WalkScreenPoint> swap = current;
|
||||
current = bufferB;
|
||||
bufferB = swap;
|
||||
reversals++;
|
||||
}
|
||||
|
||||
// Edge passes: pairs (a, b) = (v[0], v[n-1]), (v[n-1], v[n-2]) … (v[1], v[0]).
|
||||
int n = viewEdgeVertices.Length;
|
||||
for (int e = n - 1; e >= 0; e--)
|
||||
{
|
||||
Vector2 a = viewEdgeVertices[e == n - 1 ? 0 : e + 1];
|
||||
Vector2 b = viewEdgeVertices[e];
|
||||
count = ClipPassEdge(current[..count], a, b, bufferB);
|
||||
if (count < 3) return 0;
|
||||
Span<WalkScreenPoint> swap = current;
|
||||
current = bufferB;
|
||||
bufferB = swap;
|
||||
reversals++;
|
||||
}
|
||||
|
||||
// Restore original winding: each pass reversed the order once.
|
||||
if ((reversals & 1) != 0)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
output[i] = current[count - 1 - i];
|
||||
}
|
||||
else
|
||||
{
|
||||
current[..count].CopyTo(output);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static int ClipPassW(ReadOnlySpan<WalkScreenPoint> pts, Span<WalkScreenPoint> outPts)
|
||||
{
|
||||
int outCount = 0;
|
||||
// Reverse traversal starting from the wrap pair (pts[0], pts[n-1]).
|
||||
WalkScreenPoint prev = pts[0];
|
||||
float sPrev = prev.W - MinW;
|
||||
bool inPrev = sPrev >= 0f;
|
||||
for (int i = pts.Length - 1; i >= 0; i--)
|
||||
{
|
||||
WalkScreenPoint cur = pts[i];
|
||||
float s = cur.W - MinW;
|
||||
bool inCur = s >= 0f;
|
||||
if (inPrev != inCur)
|
||||
outPts[outCount++] = Lerp(prev, cur, sPrev / (sPrev - s));
|
||||
if (inCur)
|
||||
outPts[outCount++] = cur;
|
||||
prev = cur; sPrev = s; inPrev = inCur;
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
private static int ClipPassEdge(
|
||||
ReadOnlySpan<WalkScreenPoint> pts, Vector2 a, Vector2 b, Span<WalkScreenPoint> outPts)
|
||||
{
|
||||
float ex = b.X - a.X;
|
||||
float ey = b.Y - a.Y;
|
||||
float Side(in WalkScreenPoint p) => (p.X - a.X * p.W) * ey - (p.Y - a.Y * p.W) * ex;
|
||||
|
||||
int outCount = 0;
|
||||
WalkScreenPoint prev = pts[0];
|
||||
float s0 = Side(prev);
|
||||
float sPrev = s0;
|
||||
bool inPrev = s0 <= 0f; // INSIDE = side <= 0 (Ghidra-verified)
|
||||
for (int i = pts.Length - 1; i >= 0; i--)
|
||||
{
|
||||
WalkScreenPoint cur = pts[i];
|
||||
float s = i != 0 ? Side(cur) : s0; // final pair reuses point 0's side
|
||||
bool inCur = s <= 0f;
|
||||
if (inPrev != inCur)
|
||||
outPts[outCount++] = Lerp(prev, cur, sPrev / (sPrev - s));
|
||||
if (inCur)
|
||||
outPts[outCount++] = cur;
|
||||
prev = cur; sPrev = s; inPrev = inCur;
|
||||
}
|
||||
return outCount;
|
||||
}
|
||||
|
||||
private static WalkScreenPoint Lerp(in WalkScreenPoint p, in WalkScreenPoint q, float t)
|
||||
=> new(
|
||||
p.X + (q.X - p.X) * t,
|
||||
p.Y + (q.Y - p.Y) * t,
|
||||
p.Z + (q.Z - p.Z) * t,
|
||||
p.W + (q.W - p.W) * t);
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2a: the walk→draw population layer. Turns one
|
||||
/// cell's already-classified static content into <see cref="OrderedDrawCommand"/>
|
||||
/// appends (opaque, walk order), per-instance deferred-alpha submissions
|
||||
/// (translucent), and selection-scene publications (picking) — WITHOUT any
|
||||
/// production frame wiring. <c>WorldSceneRenderer</c> does not call this
|
||||
/// class yet; FW3.2b roots it into the real frame.
|
||||
///
|
||||
/// <para><b>Why per-entity append instead of the classic material grouping</b>:
|
||||
/// under <see cref="WorldDepthContract"/>'s depth-compare Less, opaque draw
|
||||
/// order is pixel-relevant ONLY for coplanar surfaces (a rug on a floor, a
|
||||
/// shell over terrain) — and retail resolves those by first-drawn-wins in
|
||||
/// ITS walk/cell-content order, not by any texture/material grouping. So
|
||||
/// this populator appends one <see cref="OrderedDrawCommand"/> per (entity,
|
||||
/// opaque batch) in the SAME order the caller's <c>records</c> span presents
|
||||
/// them — never material-grouped, never re-sorted. Translucent batches go to
|
||||
/// the SAME <see cref="RetailAlphaQueue"/> the classic/packed paths already
|
||||
/// use (global far→near re-sort still applies; walk-order submission only
|
||||
/// improves retail's submission-order tie-break fidelity for coincident
|
||||
/// distances — <c>WbDrawDispatcher.DeferTransparentGroups</c>'s own doc
|
||||
/// comment cites the same <c>CShadowPart::insertion_sort</c> stability this
|
||||
/// preserves).</para>
|
||||
///
|
||||
/// <para>Reads no retained scene state itself: every method takes the target
|
||||
/// <see cref="OrderedDrawStream"/> and a caller-supplied span of already
|
||||
/// -queried <see cref="RenderProjectionRecord"/>s (from
|
||||
/// <c>RenderSceneQuery.CopyCellStaticsTo</c> / <c>CopyIndexTo</c>). Owns only
|
||||
/// two small per-call scratch lists — the classify seam's per-entity output
|
||||
/// — reused across records to keep this hot path allocation-free after
|
||||
/// warmup.</para>
|
||||
/// </summary>
|
||||
internal sealed class WalkStaticStreamPopulator
|
||||
{
|
||||
private readonly WbDrawDispatcher _dispatcher;
|
||||
private readonly List<WbDrawDispatcher.WalkClassifiedBatch> _batchScratch = new();
|
||||
private readonly List<WbDrawDispatcher.WalkClassifiedSelectionPart> _selectionScratch = new();
|
||||
|
||||
internal WalkStaticStreamPopulator(WbDrawDispatcher dispatcher)
|
||||
{
|
||||
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates <paramref name="stream"/> from one indoor cell's static
|
||||
/// content (<c>RenderProjectionClass.IndoorCellStatic</c> —
|
||||
/// <c>RenderSceneQuery.CopyCellStaticsTo</c>). <paramref name="stage"/>
|
||||
/// is caller-selected per the walk turn this cell's content belongs to
|
||||
/// — <see cref="WalkDrawStage.CellStatic"/> for an ordinary
|
||||
/// <c>PView::DrawCells</c> flood, <see cref="WalkDrawStage.LookInStatic"/>
|
||||
/// for a <c>ConstructView(CBldPortal)</c> look-in, or
|
||||
/// <see cref="WalkDrawStage.BuildingShell"/> for a building's own
|
||||
/// exterior shell content.
|
||||
/// </summary>
|
||||
internal void PopulateCell(
|
||||
OrderedDrawStream stream,
|
||||
WalkDrawStage stage,
|
||||
uint cellId,
|
||||
ReadOnlySpan<RenderProjectionRecord> records,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
ClassifyAndAppend(
|
||||
stream, stage, cellId, in records[i], tupleLandblockId,
|
||||
cameraWorldPosition, viewProjection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The landscape entry point: outdoor static content
|
||||
/// (<c>RenderProjectionClass.OutdoorStatic</c> —
|
||||
/// <c>RenderSceneQuery.CopyIndexTo(RenderSceneIndex.OutdoorStatic, ...)</c>)
|
||||
/// at <see cref="WalkDrawStage.OutdoorStatic"/>, the terrain-adjacent
|
||||
/// stage <c>LScape::draw</c> visits a landblock's static objects at
|
||||
/// alongside its ground mesh (see that stage value's own doc comment).
|
||||
/// <paramref name="cellId"/> is the OUTDOOR landblock id — outdoor
|
||||
/// statics have no EnvCell of their own, so this is walk-order
|
||||
/// provenance only, not a clip-slot key.
|
||||
/// </summary>
|
||||
internal void PopulateOutdoorStatics(
|
||||
OrderedDrawStream stream,
|
||||
uint cellId,
|
||||
ReadOnlySpan<RenderProjectionRecord> records,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection) =>
|
||||
PopulateCell(
|
||||
stream, WalkDrawStage.OutdoorStatic, cellId, records,
|
||||
tupleLandblockId, cameraWorldPosition, viewProjection);
|
||||
|
||||
private void ClassifyAndAppend(
|
||||
OrderedDrawStream stream,
|
||||
WalkDrawStage stage,
|
||||
uint cellId,
|
||||
in RenderProjectionRecord record,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection)
|
||||
{
|
||||
_batchScratch.Clear();
|
||||
_selectionScratch.Clear();
|
||||
_dispatcher.ClassifyEntityForWalk(
|
||||
in record, tupleLandblockId, _batchScratch, _selectionScratch);
|
||||
|
||||
for (int i = 0; i < _batchScratch.Count; i++)
|
||||
{
|
||||
WbDrawDispatcher.WalkClassifiedBatch batch = _batchScratch[i];
|
||||
if (batch.IsOpaque)
|
||||
{
|
||||
stream.Append(new OrderedDrawCommand(
|
||||
batch.Key, batch.Transform, stage, cellId, batch.ClipSlot,
|
||||
batch.Lights, batch.IndoorFlag, batch.Alpha,
|
||||
batch.SelectionLighting, batch.DetailCategory));
|
||||
}
|
||||
else
|
||||
{
|
||||
_dispatcher.SubmitWalkAlphaInstance(
|
||||
in batch, cameraWorldPosition, viewProjection);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _selectionScratch.Count; i++)
|
||||
{
|
||||
WbDrawDispatcher.WalkClassifiedSelectionPart part = _selectionScratch[i];
|
||||
_dispatcher.PublishWalkSelectionPart(in part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,285 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>Retail <c>view_vertex</c> (stride 0x18): a screen point plus the
|
||||
/// world-space plane of the edge that STARTS at it (edge k = verts k → k+1).</summary>
|
||||
public struct WalkViewVertex
|
||||
{
|
||||
public Vector2 Point;
|
||||
public WalkPlane Plane;
|
||||
}
|
||||
|
||||
/// <summary>Retail <c>view_poly</c>: one view polygon's slice of the shared
|
||||
/// vertex pool plus its screen bounds.</summary>
|
||||
public readonly record struct WalkViewPoly(
|
||||
int VertexCount, int VertexIndex, float XMin, float XMax, float YMin, float YMax);
|
||||
|
||||
/// <summary>Retail <c>view_type</c>: the poly list + shared vertex pool one
|
||||
/// <c>portal_view_type</c> accumulates its views into.</summary>
|
||||
public sealed class WalkViewSet
|
||||
{
|
||||
public readonly List<WalkViewPoly> Polys = new();
|
||||
public readonly List<WalkViewVertex> Vertices = new();
|
||||
public int VertexCountTotal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>portal_view_type</c> (0x48 bytes): one view-recursion slot on a
|
||||
/// cell (or the PView's <c>outside_view</c>). Retail recycles slots and
|
||||
/// resets exactly view_count/update_count/view_timestamp on push
|
||||
/// (<c>CEnvCell::curr_view_push</c> @0x005a5090); this port models the same
|
||||
/// counters over list storage — when <see cref="ViewCount"/> is 0 the next
|
||||
/// append clears the pools, matching retail's vertex-pool base reset.
|
||||
/// </summary>
|
||||
public sealed class WalkPortalView
|
||||
{
|
||||
/// <summary>Per-portal <c>portal_info</c> flags (seen, inflag), sized by
|
||||
/// <c>PView::InitCell</c> for the owning cell's portal count.</summary>
|
||||
public WalkPortalFlags[] PortalFlags = [];
|
||||
|
||||
public readonly WalkViewSet View = new();
|
||||
|
||||
/// <summary>Max SQUARED cell-local distance to any in-view portal vertex
|
||||
/// (<c>PView::InitCell</c>); the flood's todo-list distance key.</summary>
|
||||
public float MaxInDistSquared;
|
||||
|
||||
public int ViewCount;
|
||||
public bool CellViewDone;
|
||||
public int ViewTimestamp;
|
||||
public int UpdateCount;
|
||||
|
||||
/// <summary><c>curr_view_push</c>'s per-push counter reset.</summary>
|
||||
public void ResetForPush()
|
||||
{
|
||||
ViewCount = 0;
|
||||
UpdateCount = 0;
|
||||
ViewTimestamp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public struct WalkPortalFlags
|
||||
{
|
||||
public bool Seen;
|
||||
public bool InView;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unprojects a screen point to a world-space eye ray direction —
|
||||
/// retail <c>PrimD3DRender::ScreenToViewTransform</c> @0x0059aa40 (the live
|
||||
/// <c>newmethod==1</c> path of <c>Render::copy_view</c>'s plane builder).
|
||||
/// The exact matrix wiring lives with the camera module; the walk depends
|
||||
/// only on this contract.
|
||||
/// </summary>
|
||||
public interface IWalkRayCaster
|
||||
{
|
||||
Vector3 RayThrough(float screenX, float screenY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW1 — <c>Render::copy_view</c> @0x0054dfc0, ported from the
|
||||
/// flood-read appendix report 3 (Ghidra-arbitrated). Appends ONE view
|
||||
/// polygon to a <see cref="WalkPortalView"/>: perspective-divides the
|
||||
/// homogeneous screen points IN PLACE, prunes ~1 px duplicates and
|
||||
/// collinear points (three closing wrap checks included), rejects fewer
|
||||
/// than 3 survivors (returns false, dest untouched), caps at 31, stores the
|
||||
/// point list plus a closing duplicate, computes bounds, and builds
|
||||
/// per-edge WORLD planes N = normalize(cross(ray[k+1], ray[k])) — NEXT ×
|
||||
/// CURRENT — with d = −dot(N, viewpoint).
|
||||
/// </summary>
|
||||
public static class WalkCopyView
|
||||
{
|
||||
public const int MaxVertices = 31; // retail cap 0x1f
|
||||
public const float DedupThreshold = 1f; // strict > 1 px
|
||||
|
||||
/// <summary>The null-source path: the full-viewport root quad
|
||||
/// (0,H)(W,H)(W,0)(0,0) — used by <c>Render::set_default_view</c> and
|
||||
/// <c>PView::DrawInside</c>'s root view (the source count is ignored).</summary>
|
||||
public static bool AppendFullViewportQuad(
|
||||
WalkPortalView dest, IWalkRayCaster rays, Vector3 viewpoint,
|
||||
float viewportWidth, float viewportHeight)
|
||||
{
|
||||
Span<WalkScreenPoint> quad =
|
||||
[
|
||||
new(0f, viewportHeight, 0f, 1f),
|
||||
new(viewportWidth, viewportHeight, 0f, 1f),
|
||||
new(viewportWidth, 0f, 0f, 1f),
|
||||
new(0f, 0f, 0f, 1f),
|
||||
];
|
||||
return Append(dest, quad, rays, viewpoint);
|
||||
}
|
||||
|
||||
/// <summary>The point-source path. <paramref name="points"/> is mutated
|
||||
/// (in-place perspective divide) exactly as retail mutates the shared
|
||||
/// clip buffer; the buffer is consumed per portal, so the mutation never
|
||||
/// leaks across calls.</summary>
|
||||
public static bool Append(
|
||||
WalkPortalView dest, Span<WalkScreenPoint> points,
|
||||
IWalkRayCaster rays, Vector3 viewpoint)
|
||||
{
|
||||
int npts = points.Length;
|
||||
if (npts == 0) return false;
|
||||
|
||||
// ---- survivor marking (keep[] / last / stl / second bookkeeping) ----
|
||||
Span<bool> keep = stackalloc bool[npts];
|
||||
keep[0] = true;
|
||||
int n = 1;
|
||||
int last = 0;
|
||||
int secondToLast = 0; // retail 'stl': index of the second-to-last kept corner
|
||||
int second = 0; // retail local_220: index of the 2nd kept point
|
||||
|
||||
for (int i = 0; i < npts; i++)
|
||||
{
|
||||
ref WalkScreenPoint p = ref points[i];
|
||||
if (p.W != 1f)
|
||||
{
|
||||
p.X /= p.W;
|
||||
p.Y /= p.W;
|
||||
p.W = 1f;
|
||||
}
|
||||
if (i == 0) continue;
|
||||
|
||||
bool distinct =
|
||||
MathF.Abs(points[i].X - points[last].X) > DedupThreshold
|
||||
|| MathF.Abs(points[i].Y - points[last].Y) > DedupThreshold;
|
||||
keep[i] = distinct;
|
||||
if (!distinct) continue;
|
||||
|
||||
if (n == 1)
|
||||
{
|
||||
n++;
|
||||
second = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
WalkScreenPoint pp = points[secondToLast];
|
||||
WalkScreenPoint prev = points[last];
|
||||
WalkScreenPoint cur = points[i];
|
||||
float span = MathF.Max(MathF.Abs(pp.X - cur.X), MathF.Abs(pp.Y - cur.Y));
|
||||
float cross = (pp.X - prev.X) * (prev.Y - cur.Y)
|
||||
- (pp.Y - prev.Y) * (prev.X - cur.X);
|
||||
if (MathF.Abs(cross) >= span)
|
||||
{
|
||||
n++;
|
||||
secondToLast = last;
|
||||
}
|
||||
else
|
||||
{
|
||||
// prev was collinear: un-keep it; count unchanged (prev out, cur in).
|
||||
keep[last] = false;
|
||||
if (second == last) second = i;
|
||||
}
|
||||
}
|
||||
last = i;
|
||||
}
|
||||
|
||||
// ---- closing wrap checks against point 0 ----
|
||||
WalkScreenPoint first = points[0];
|
||||
bool lastDistinct =
|
||||
MathF.Abs(first.X - points[last].X) > DedupThreshold
|
||||
|| MathF.Abs(first.Y - points[last].Y) > DedupThreshold;
|
||||
keep[last] = lastDistinct;
|
||||
if (!lastDistinct)
|
||||
{
|
||||
n--;
|
||||
last = secondToLast;
|
||||
}
|
||||
else
|
||||
{
|
||||
float span = MathF.Max(
|
||||
MathF.Abs(points[secondToLast].X - first.X),
|
||||
MathF.Abs(points[secondToLast].Y - first.Y));
|
||||
float cross = (points[secondToLast].X - points[last].X) * (points[last].Y - first.Y)
|
||||
- (points[last].X - first.X) * (points[secondToLast].Y - points[last].Y);
|
||||
if (MathF.Abs(cross) < span)
|
||||
{
|
||||
keep[last] = false;
|
||||
n--;
|
||||
last = secondToLast;
|
||||
}
|
||||
}
|
||||
secondToLast = last;
|
||||
if (second > 0)
|
||||
{
|
||||
// Is point 0 itself collinear between the last corner and the second?
|
||||
float span = MathF.Max(
|
||||
MathF.Abs(points[secondToLast].X - points[second].X),
|
||||
MathF.Abs(points[secondToLast].Y - points[second].Y));
|
||||
float cross = (first.Y - points[second].Y) * (points[secondToLast].X - first.X)
|
||||
- (first.X - points[second].X) * (points[secondToLast].Y - first.Y);
|
||||
if (MathF.Abs(cross) < span)
|
||||
{
|
||||
n--;
|
||||
keep[0] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (n < 3) return false; // REJECT: dest completely untouched
|
||||
if (n > MaxVertices) n = MaxVertices; // cap 31 (corrupt overflow path unreachable ≤32 in)
|
||||
|
||||
// ---- append into the pool (view_count==0 resets the pool base) ----
|
||||
WalkViewSet view = dest.View;
|
||||
if (dest.ViewCount == 0)
|
||||
{
|
||||
view.Polys.Clear();
|
||||
view.Vertices.Clear();
|
||||
view.VertexCountTotal = 0;
|
||||
}
|
||||
int vbase = view.VertexCountTotal;
|
||||
view.VertexCountTotal = vbase + n + 1;
|
||||
|
||||
int written = 0;
|
||||
for (int i = 0; i < npts && written < n; i++)
|
||||
{
|
||||
if (!keep[i]) continue;
|
||||
view.Vertices.Add(new WalkViewVertex
|
||||
{
|
||||
// Retail applies a REAL fabs on copy (harmless post-clip; preserved).
|
||||
Point = new Vector2(MathF.Abs(points[i].X), MathF.Abs(points[i].Y)),
|
||||
});
|
||||
written++;
|
||||
}
|
||||
// Closing duplicate vertex (its plane slot is never consumed).
|
||||
view.Vertices.Add(new WalkViewVertex { Point = view.Vertices[vbase].Point });
|
||||
|
||||
// ---- bounds over v[0..n-1] ----
|
||||
float xmin, xmax, ymin, ymax;
|
||||
Vector2 seed = view.Vertices[vbase + n - 1].Point;
|
||||
xmin = xmax = seed.X;
|
||||
ymin = ymax = seed.Y;
|
||||
for (int k = n - 2; k >= 0; k--)
|
||||
{
|
||||
Vector2 pt = view.Vertices[vbase + k].Point;
|
||||
if (pt.X < xmin) xmin = pt.X; else if (pt.X > xmax) xmax = pt.X;
|
||||
if (pt.Y < ymin) ymin = pt.Y; else if (pt.Y > ymax) ymax = pt.Y;
|
||||
}
|
||||
view.Polys.Add(new WalkViewPoly(n, vbase, xmin, xmax, ymin, ymax));
|
||||
|
||||
// ---- per-edge world planes from unprojected rays ----
|
||||
Span<Vector3> ray = stackalloc Vector3[n + 1];
|
||||
for (int k = 0; k < n; k++)
|
||||
{
|
||||
Vector2 pt = view.Vertices[vbase + k].Point;
|
||||
ray[k] = rays.RayThrough(pt.X, pt.Y);
|
||||
}
|
||||
ray[n] = ray[0];
|
||||
for (int k = n - 1; k >= 0; k--)
|
||||
{
|
||||
Vector3 normal = Vector3.Cross(ray[k + 1], ray[k]); // NEXT × CURRENT
|
||||
if (MathF.Abs(normal.X) >= WalkVisibilityMath.Epsilon
|
||||
|| MathF.Abs(normal.Y) >= WalkVisibilityMath.Epsilon
|
||||
|| MathF.Abs(normal.Z) >= WalkVisibilityMath.Epsilon)
|
||||
{
|
||||
normal *= 1f / MathF.Sqrt(
|
||||
normal.X * normal.X + normal.Y * normal.Y + normal.Z * normal.Z);
|
||||
}
|
||||
// else: degenerate edge left tiny/unnormalized (retail behavior).
|
||||
WalkViewVertex v = view.Vertices[vbase + k];
|
||||
v.Plane = new WalkPlane(normal, -Vector3.Dot(normal, viewpoint));
|
||||
view.Vertices[vbase + k] = v;
|
||||
}
|
||||
|
||||
dest.ViewCount += 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,211 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW1 — retail's CPU visibility primitives, ported from the named
|
||||
/// 2013 decomp (extractions:
|
||||
/// docs/research/2026-08-30-fw-flood-pseudocode-appendix.md, report 4; all
|
||||
/// x87-ambiguous branches Ghidra-arbitrated). Two families:
|
||||
///
|
||||
/// 1. The landscape interval test — a grid corner's vertical column is
|
||||
/// classified against each active clip plane as a signed height
|
||||
/// (<c>Render::get_pt_limit</c> @0x0054b840), and a landblock's z-slab
|
||||
/// is tested against its four corner columns plane by plane
|
||||
/// (<c>corner_plane_check</c> @0x0054b930, <c>block_plane_check</c>
|
||||
/// @0x0054d060, <c>block_check</c> @0x0054dc50).
|
||||
/// 2. The sphere-vs-view test (<c>Render::viewconeCheck</c> @0x0054c250).
|
||||
///
|
||||
/// Both consume the same installed state: the CY near plane (built by
|
||||
/// <c>Render::update_viewpoint</c>) plus the active view's edge planes
|
||||
/// (installed by <c>Render::set_view</c>).
|
||||
/// </summary>
|
||||
public static class WalkVisibilityMath
|
||||
{
|
||||
/// <summary>Retail F_EPSILON (raw float 0x3951B717).</summary>
|
||||
public const float Epsilon = 0.000199999995f;
|
||||
|
||||
/// <summary>get_pt_limit sky cap: a clip height at or above this means
|
||||
/// the column holds nothing (up-normal arm) / everything (down-normal
|
||||
/// arm).</summary>
|
||||
public const float SkyHeight = 1000f;
|
||||
|
||||
/// <summary>Sentinel: the column is wholly inside the plane's positive
|
||||
/// half-space.</summary>
|
||||
public const float InsideColumn = 0f;
|
||||
|
||||
/// <summary>Sentinel: the column is wholly outside (static init 1000+1).</summary>
|
||||
public const float OutsideColumn = 1001f;
|
||||
|
||||
/// <summary>
|
||||
/// <c>Render::get_pt_limit</c> @0x0054b840: classify the vertical column
|
||||
/// at (x, y) against one plane. Returns <see cref="InsideColumn"/> /
|
||||
/// <see cref="OutsideColumn"/>, a positive h (inside only BELOW h —
|
||||
/// down-pointing normal), or a negative −h (inside only ABOVE h —
|
||||
/// up-pointing normal). Vertical planes (|N.z| ≤ ε) collapse to
|
||||
/// all-in/all-out via the plane side of (x, y, 0).
|
||||
/// </summary>
|
||||
public static float GetPointLimit(float x, float y, in WalkPlane plane)
|
||||
{
|
||||
Vector3 n = plane.Normal;
|
||||
if (n.Z > Epsilon)
|
||||
{
|
||||
float h = -((x * n.X + y * n.Y + plane.D) / n.Z);
|
||||
if (h >= SkyHeight) return OutsideColumn;
|
||||
return h > 0f ? -h : InsideColumn;
|
||||
}
|
||||
if (n.Z < -Epsilon)
|
||||
{
|
||||
float h = -((x * n.X + y * n.Y + plane.D) / n.Z);
|
||||
if (h <= 0f) return OutsideColumn;
|
||||
return h >= SkyHeight ? InsideColumn : h;
|
||||
}
|
||||
// Vertical plane: no z dependence; test point (x, y, 0), retail
|
||||
// Plane::which_side semantics (NEGATIVE = strictly below -epsilon).
|
||||
float d = x * n.X + y * n.Y + plane.D;
|
||||
return d < -Epsilon ? OutsideColumn : InsideColumn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Render::get_clip_height</c> @0x0054cff0: fill one corner's
|
||||
/// interval vector — bounds[0] vs the CY plane, bounds[1..edgeCount] vs
|
||||
/// each active edge plane (the loop is INCLUSIVE of edgeCount entries:
|
||||
/// edgeCount + 1 floats are written).
|
||||
/// </summary>
|
||||
public static void FillClipHeights(
|
||||
float x, float y, in WalkPlane cyPlane, ReadOnlySpan<WalkPlane> edgePlanes,
|
||||
Span<float> bounds)
|
||||
{
|
||||
bounds[0] = GetPointLimit(x, y, cyPlane);
|
||||
for (int i = 0; i < edgePlanes.Length; i++)
|
||||
bounds[i + 1] = GetPointLimit(x, y, edgePlanes[i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Render::corner_plane_check</c> @0x0054b930: one corner's encoding
|
||||
/// vs the block z-slab [minZ, maxZ]. Boundary semantics are retail's:
|
||||
/// touching the clip height on the OUT side is Outside; on the IN side
|
||||
/// is EntirelyInside.
|
||||
/// </summary>
|
||||
public static WalkBoundingType CornerPlaneCheck(float bound, float minZ, float maxZ)
|
||||
{
|
||||
if (bound == OutsideColumn) return WalkBoundingType.Outside;
|
||||
if (bound != InsideColumn)
|
||||
{
|
||||
if (bound <= 0f)
|
||||
{
|
||||
// inside is z >= h, h = -bound
|
||||
float h = -bound;
|
||||
if (h > minZ)
|
||||
{
|
||||
if (maxZ <= h) return WalkBoundingType.Outside;
|
||||
return WalkBoundingType.PartiallyInside;
|
||||
}
|
||||
}
|
||||
else if (bound < maxZ)
|
||||
{
|
||||
// inside is z <= h, h = bound; the block top pokes above h
|
||||
if (bound <= minZ) return WalkBoundingType.Outside;
|
||||
return WalkBoundingType.PartiallyInside;
|
||||
}
|
||||
}
|
||||
return WalkBoundingType.EntirelyInside;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Render::block_plane_check</c> @0x0054d060: four corners vs one
|
||||
/// plane. Outside/EntirelyInside require unanimity; anything mixed is
|
||||
/// PartiallyInside.
|
||||
/// </summary>
|
||||
public static WalkBoundingType BlockPlaneCheck(
|
||||
float b1, float b2, float b3, float b4, float minZ, float maxZ)
|
||||
{
|
||||
WalkBoundingType c1 = CornerPlaneCheck(b1, minZ, maxZ);
|
||||
WalkBoundingType c2 = CornerPlaneCheck(b2, minZ, maxZ);
|
||||
WalkBoundingType c3 = CornerPlaneCheck(b3, minZ, maxZ);
|
||||
WalkBoundingType c4 = CornerPlaneCheck(b4, minZ, maxZ);
|
||||
if (c1 == WalkBoundingType.Outside)
|
||||
{
|
||||
if (c2 == WalkBoundingType.Outside
|
||||
&& c3 == WalkBoundingType.Outside
|
||||
&& c4 == WalkBoundingType.Outside)
|
||||
{
|
||||
return WalkBoundingType.Outside;
|
||||
}
|
||||
}
|
||||
else if (c1 == WalkBoundingType.EntirelyInside
|
||||
&& c2 == WalkBoundingType.EntirelyInside
|
||||
&& c3 == WalkBoundingType.EntirelyInside
|
||||
&& c4 == WalkBoundingType.EntirelyInside)
|
||||
{
|
||||
return WalkBoundingType.EntirelyInside;
|
||||
}
|
||||
return WalkBoundingType.PartiallyInside;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Render::block_check</c> @0x0054dc50: the block-vs-view test over
|
||||
/// four corner interval vectors. Retail's call site passes
|
||||
/// (max_zval, min_zval) and an internal double positional swap hands
|
||||
/// corner_plane_check (min, max) — this port takes (maxZ, minZ) to
|
||||
/// mirror the caller's argument order and performs the same swap, so
|
||||
/// call sites read like the decomp. Any plane with all four corners
|
||||
/// outside culls; any partial plane demotes stickily; EntirelyInside
|
||||
/// requires unanimity on every plane. planeCount = the active view's
|
||||
/// edge count (bounds[0] is the CY plane, tested first with early-out).
|
||||
/// </summary>
|
||||
public static WalkBoundingType BlockCheck(
|
||||
ReadOnlySpan<float> corner00, ReadOnlySpan<float> corner01,
|
||||
ReadOnlySpan<float> corner10, ReadOnlySpan<float> corner11,
|
||||
int planeCount, float maxZ, float minZ)
|
||||
{
|
||||
WalkBoundingType result = BlockPlaneCheck(
|
||||
corner00[0], corner01[0], corner10[0], corner11[0], minZ, maxZ);
|
||||
if (result == WalkBoundingType.Outside) return WalkBoundingType.Outside;
|
||||
for (int k = 1; k <= planeCount; k++)
|
||||
{
|
||||
WalkBoundingType r = BlockPlaneCheck(
|
||||
corner00[k], corner01[k], corner10[k], corner11[k], minZ, maxZ);
|
||||
if (r == WalkBoundingType.Outside) return WalkBoundingType.Outside;
|
||||
if (r == WalkBoundingType.PartiallyInside)
|
||||
result = WalkBoundingType.PartiallyInside;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Render::viewconeCheck</c> @0x0054c250 (plane-test half): a sphere
|
||||
/// (center already in viewer-block space, radius already scaled) vs the
|
||||
/// CY plane plus the active view's edge planes. Cull is STRICT
|
||||
/// (d < −r); the partial flag is INCLUSIVE (d ≤ r) — a sphere exactly
|
||||
/// tangent from inside counts Partial, not EntirelyInside. The retail
|
||||
/// body's side effects (publishing local_object_center/radius) belong
|
||||
/// to the walk context, not this math.
|
||||
/// </summary>
|
||||
public static WalkBoundingType ViewconeCheck(
|
||||
Vector3 center, float radius, in WalkPlane cyPlane,
|
||||
ReadOnlySpan<WalkPlane> edgePlanes)
|
||||
{
|
||||
float d = Vector3.Dot(cyPlane.Normal, center) + cyPlane.D;
|
||||
if (d < -radius) return WalkBoundingType.Outside;
|
||||
bool partial = d <= radius;
|
||||
foreach (ref readonly WalkPlane plane in edgePlanes)
|
||||
{
|
||||
d = Vector3.Dot(plane.Normal, center) + plane.D;
|
||||
if (d < -radius) return WalkBoundingType.Outside;
|
||||
if (d <= radius) partial = true;
|
||||
}
|
||||
return partial ? WalkBoundingType.PartiallyInside : WalkBoundingType.EntirelyInside;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Retail <c>Plane</c>: dot(N, p) + d, positive side = inside.</summary>
|
||||
public readonly record struct WalkPlane(Vector3 Normal, float D);
|
||||
|
||||
/// <summary>Retail <c>BoundingType</c>, values used raw by the walk.</summary>
|
||||
public enum WalkBoundingType
|
||||
{
|
||||
Outside = 0,
|
||||
PartiallyInside = 1,
|
||||
EntirelyInside = 2,
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>A cell-local portal polygon with its plane (retail
|
||||
/// <c>CPolygon</c>: vertices + <c>plane</c> @+0x20).</summary>
|
||||
public sealed class WalkPolygon
|
||||
{
|
||||
public Vector3[] Vertices = [];
|
||||
public WalkPlane Plane;
|
||||
}
|
||||
|
||||
/// <summary>Retail <c>CCellPortal</c> (stride 0x18). The exit-to-landscape
|
||||
/// sentinel is <c>OtherCellId == 0xFFFFFFFF</c>; <c>OtherPortalId == -1</c>
|
||||
/// means "no reciprocal".</summary>
|
||||
public struct WalkCellPortal
|
||||
{
|
||||
public uint OtherCellId;
|
||||
public int PolygonIndex;
|
||||
public int PortalSide;
|
||||
public int OtherPortalId;
|
||||
public bool ExactMatch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The flood's cell model (retail <c>CEnvCell</c> as the walk sees it).
|
||||
/// Retail stores the walk state ON the cell (num_view / portal_view stack /
|
||||
/// cached neighbor pointers); this port keeps the same placement so the
|
||||
/// transcription stays line-by-line — production adapters construct these
|
||||
/// from the committed cell registry (FW3 wiring).
|
||||
/// </summary>
|
||||
public sealed class WalkCell
|
||||
{
|
||||
public uint CellId;
|
||||
public WalkCellPortal[] Portals = [];
|
||||
public WalkPolygon[] PortalPolygons = [];
|
||||
public uint[] StabList = [];
|
||||
|
||||
/// <summary>Cell-local → landblock-local (retail <c>CEnvCell.pos</c>;
|
||||
/// the frame the flood's plane tests and projections run in).</summary>
|
||||
public Matrix4x4 WorldTransform = Matrix4x4.Identity;
|
||||
public Matrix4x4 InverseWorldTransform = Matrix4x4.Identity;
|
||||
|
||||
// ---- walk state (retail: fields on CEnvCell) ----
|
||||
public int NumView;
|
||||
public readonly List<WalkPortalView> PortalViews = new();
|
||||
public WalkCell?[] CachedNeighbors = [];
|
||||
|
||||
public WalkPortalView TopView => PortalViews[NumView - 1];
|
||||
|
||||
/// <summary><c>CEnvCell::curr_view_push</c> @0x005a5090: push one
|
||||
/// view-recursion level (lazy slot, exactly three counters reset).</summary>
|
||||
public void PushView()
|
||||
{
|
||||
while (PortalViews.Count <= NumView)
|
||||
PortalViews.Add(new WalkPortalView());
|
||||
PortalViews[NumView].ResetForPush();
|
||||
NumView++;
|
||||
if (CachedNeighbors.Length < Portals.Length)
|
||||
CachedNeighbors = new WalkCell?[Portals.Length];
|
||||
}
|
||||
|
||||
public void PopView() => NumView--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The per-frame context retail keeps in globals (<c>Render::FrameCurrent</c>
|
||||
/// after <c>positionPush(3, cell.pos)</c>, the projection state, the visible
|
||||
/// cell registry, and the <c>cliplandscape</c> toggle — .data default 1).
|
||||
/// </summary>
|
||||
public interface IWalkFrameContext
|
||||
{
|
||||
/// <summary>The eye position in the cell's local frame (retail: the
|
||||
/// pushed frame's <c>viewer.viewpoint</c>).</summary>
|
||||
Vector3 ViewpointIn(WalkCell cell);
|
||||
|
||||
/// <summary>Object(cell-local)→clip matrix for projecting the cell's
|
||||
/// portal polygons (retail: the pushed frame composed with
|
||||
/// WorldToView·ViewToClip).</summary>
|
||||
Matrix4x4 ObjectToClip(WalkCell cell);
|
||||
|
||||
/// <summary><c>CEnvCell::GetVisible</c>: the committed/visible cell
|
||||
/// registry. Returning null skips the portal silently (retail behavior —
|
||||
/// but implementations should count the miss for the fail-loud rule).</summary>
|
||||
WalkCell? GetVisible(uint cellId);
|
||||
|
||||
IWalkRayCaster Rays { get; }
|
||||
Vector3 WorldViewpoint { get; }
|
||||
float ViewportWidth { get; }
|
||||
float ViewportHeight { get; }
|
||||
|
||||
/// <summary>Retail global <c>cliplandscape</c> (.data @0x00820f4c = 1).</summary>
|
||||
bool ClipLandscape => true;
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
using AcDream.Core.Rendering.Wb;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
|
|
@ -35,48 +34,22 @@ public sealed class EnvCellLandblockBuild
|
|||
public EnvCellLandblockBuild(
|
||||
uint landblockId,
|
||||
IEnumerable<LoadedCell> visibilityCells,
|
||||
IEnumerable<EnvCellShellPlacement> shells,
|
||||
IEnumerable<WalkBuildingFactory.Entry>? walkBuildings = null,
|
||||
float walkMaxZ = 0f,
|
||||
float walkMinZ = 0f)
|
||||
IEnumerable<EnvCellShellPlacement> shells)
|
||||
{
|
||||
LandblockId = landblockId;
|
||||
VisibilityCells = visibilityCells.ToImmutableArray();
|
||||
Shells = shells.ToImmutableArray();
|
||||
WalkBuildings = (walkBuildings ?? Enumerable.Empty<WalkBuildingFactory.Entry>()).ToImmutableArray();
|
||||
WalkMaxZ = walkMaxZ;
|
||||
WalkMinZ = walkMinZ;
|
||||
|
||||
uint expectedPrefix = landblockId & 0xFFFF0000u;
|
||||
if (VisibilityCells.Any(cell => (cell.CellId & 0xFFFF0000u) != expectedPrefix))
|
||||
throw new ArgumentException("A visibility cell belongs to a different landblock.", nameof(visibilityCells));
|
||||
if (Shells.Any(shell => (shell.CellId & 0xFFFF0000u) != expectedPrefix))
|
||||
throw new ArgumentException("A render shell belongs to a different landblock.", nameof(shells));
|
||||
if (WalkBuildings.Any(entry => (entry.Building.PositionCellId & 0xFFFF0000u) != expectedPrefix))
|
||||
throw new ArgumentException("A walk building belongs to a different landblock.", nameof(walkBuildings));
|
||||
}
|
||||
|
||||
public uint LandblockId { get; }
|
||||
public ImmutableArray<LoadedCell> VisibilityCells { get; }
|
||||
public ImmutableArray<EnvCellShellPlacement> Shells { get; }
|
||||
|
||||
/// <summary>Campaign FW3.1: this landblock's walk building placements
|
||||
/// (<c>WalkBuildingFactory.Build</c>, computed worker-side alongside
|
||||
/// <see cref="VisibilityCells"/>). Committed atomically with the rest of
|
||||
/// this transaction — see <c>WalkBuildingRegistry.Publish</c> at
|
||||
/// <c>LandblockRenderPublisher.AdvanceCompleteOne</c>.</summary>
|
||||
public ImmutableArray<WalkBuildingFactory.Entry> WalkBuildings { get; }
|
||||
|
||||
/// <summary>Campaign FW3.1: this landblock's retail z-slab
|
||||
/// (<c>heightTable[maxByte] + 200</c>) — the walk landscape's per-block
|
||||
/// visibility bound (<c>WalkLandscapeAssembler.PublishLandblock</c>).
|
||||
/// Computed once, worker-side, from the SAME heightmap bytes + height
|
||||
/// table the terrain mesh build already reads — no extra DAT access.</summary>
|
||||
public float WalkMaxZ { get; }
|
||||
|
||||
/// <summary>Campaign FW3.1: this landblock's retail z-slab
|
||||
/// (<c>heightTable[minByte] − 1</c>). See <see cref="WalkMaxZ"/>.</summary>
|
||||
public float WalkMinZ { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -89,9 +62,6 @@ public sealed class EnvCellLandblockBuildBuilder
|
|||
private readonly uint _landblockId;
|
||||
private readonly List<LoadedCell> _visibilityCells = new();
|
||||
private readonly List<EnvCellShellPlacement> _shells = new();
|
||||
private readonly List<WalkBuildingFactory.Entry> _walkBuildings = new();
|
||||
private float _walkMaxZ;
|
||||
private float _walkMinZ;
|
||||
private bool _built;
|
||||
|
||||
public EnvCellLandblockBuildBuilder(uint landblockId)
|
||||
|
|
@ -99,31 +69,6 @@ public sealed class EnvCellLandblockBuildBuilder
|
|||
_landblockId = landblockId;
|
||||
}
|
||||
|
||||
/// <summary>Campaign FW3.1: registers this landblock's walk building
|
||||
/// placements (<c>WalkBuildingFactory.Build</c>'s output). Additive to
|
||||
/// the existing cell/shell accumulation — called once per landblock from
|
||||
/// <c>LandblockBuildFactory.BuildInteriorEntitiesForStreaming</c>, where
|
||||
/// <c>LandBlockInfo</c> is already in hand.</summary>
|
||||
public void AddWalkBuildings(IEnumerable<WalkBuildingFactory.Entry> entries)
|
||||
{
|
||||
if (_built)
|
||||
throw new InvalidOperationException("This landblock cell build is already complete.");
|
||||
_walkBuildings.AddRange(entries);
|
||||
}
|
||||
|
||||
/// <summary>Campaign FW3.1: registers this landblock's retail z-slab
|
||||
/// (<see cref="EnvCellLandblockBuild.WalkMaxZ"/>/<see cref="EnvCellLandblockBuild.WalkMinZ"/>).
|
||||
/// Independent of interior-cell/building presence — every near-tier
|
||||
/// landblock has outdoor terrain heights, so
|
||||
/// <c>LandblockBuildFactory.BuildLocked</c> calls this unconditionally.</summary>
|
||||
public void SetWalkZSlab(float maxZ, float minZ)
|
||||
{
|
||||
if (_built)
|
||||
throw new InvalidOperationException("This landblock cell build is already complete.");
|
||||
_walkMaxZ = maxZ;
|
||||
_walkMinZ = minZ;
|
||||
}
|
||||
|
||||
public void AddCell(
|
||||
uint envCellId,
|
||||
EnvCell envCell,
|
||||
|
|
@ -171,8 +116,7 @@ public sealed class EnvCellLandblockBuildBuilder
|
|||
if (_built)
|
||||
throw new InvalidOperationException("This landblock cell build is already complete.");
|
||||
_built = true;
|
||||
return new EnvCellLandblockBuild(
|
||||
_landblockId, _visibilityCells, _shells, _walkBuildings, _walkMaxZ, _walkMinZ);
|
||||
return new EnvCellLandblockBuild(_landblockId, _visibilityCells, _shells);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -346,18 +290,6 @@ public sealed class EnvCellLandblockBuildBuilder
|
|||
PortalPolygons = portalPolygons,
|
||||
VisibleCells = visibleCells,
|
||||
SeenOutside = envCell.Flags.HasFlag(EnvCellFlags.SeenOutside),
|
||||
// Campaign FW3.1: the walk's model of this cell, built from the
|
||||
// SAME EnvCell/CellStruct/transform this LoadedCell was just
|
||||
// built from — WalkCellFactory.FromParsed does its own portal
|
||||
// pass (independent of the clipPlanes/portalPolygons above; the
|
||||
// two consumers decode the same dat fields for different needs)
|
||||
// so it stays a direct, auditable port of the FW1 test adapter
|
||||
// rather than reshaping LoadedCell's own list layout around it.
|
||||
// cellTransform/inverse are the dat EnvCell origin verbatim —
|
||||
// since FW3.3 retired ShellDrawLiftZ, render and physics share
|
||||
// this one transform (retail draws at the origin; the walk's
|
||||
// retail draw order owns the coplanar tie-breaks).
|
||||
Walk = WalkCellFactory.FromParsed(envCellId, envCell, cellStruct, cellTransform, inverse),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,12 +88,10 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
/// shared: <c>mesh_modern</c>, the 32-byte world-mesh vertex, triangle lists,
|
||||
/// back-face culling with clockwise front faces.
|
||||
///
|
||||
/// <para>Depth compare is <see cref="AcDream.App.Rendering.WorldDepthContract.WorldCompare"/>
|
||||
/// (<c>Less</c>), not the contract's <c>LessOrEqual</c> default — see that
|
||||
/// type for the full citation. The world frame runs under <c>GL_LESS</c>
|
||||
/// and this renderer never called <c>glDepthFunc</c>, so it inherited it;
|
||||
/// baking <c>LessOrEqual</c> would change which of two coplanar retail
|
||||
/// surfaces wins.</para>
|
||||
/// <para>Depth compare is <c>Less</c>, not the contract's <c>LessOrEqual</c>
|
||||
/// default. The world frame runs under <c>GL_LESS</c> and this renderer never
|
||||
/// called <c>glDepthFunc</c>, so it inherited it; baking <c>LessOrEqual</c>
|
||||
/// would change which of two coplanar retail surfaces wins.</para>
|
||||
/// </summary>
|
||||
private static IGpuPipeline CreateShellPipeline(
|
||||
IGpuDevice device,
|
||||
|
|
@ -102,7 +100,7 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
bool depthWrite,
|
||||
int sampleCount,
|
||||
string shaderName = "mesh_modern",
|
||||
GpuCompareOp depthCompare = AcDream.App.Rendering.WorldDepthContract.WorldCompare) =>
|
||||
GpuCompareOp depthCompare = GpuCompareOp.Less) =>
|
||||
device.CreatePipeline(new GpuPipelineDescription
|
||||
{
|
||||
Name = name,
|
||||
|
|
|
|||
|
|
@ -1,590 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
using AcDream.Core.Lighting;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.Rendering;
|
||||
using DatReaderWriter.Enums;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW2: <see cref="OrderedDrawStream"/>'s submitter.
|
||||
///
|
||||
/// <para>Walk-order submission through the SAME RHI machinery
|
||||
/// <c>WbDrawDispatcher.Rhi.cs</c> already owns — the ring-section writers,
|
||||
/// <see cref="MeshPipelineSet"/>, and <see cref="DrawIndirectRangeRhi"/> — is
|
||||
/// why this is a partial of <see cref="WbDrawDispatcher"/> rather than a
|
||||
/// standalone class. Two shapes are reused directly:
|
||||
/// <see cref="PrepareDeferredAlphaDraws"/>'s per-instance-first emission
|
||||
/// (command <c>i</c> owns exactly one instance, <c>BaseInstance = i</c>, so
|
||||
/// walk order — never material bucketing — survives into the indirect array)
|
||||
/// and <see cref="PrepareRhiAlphaSections"/>'s "write every section once"
|
||||
/// shape (<c>_ordered*</c> fields here, not the persisted <c>_alpha*</c>
|
||||
/// fields: those belong to <see cref="RetailAlphaQueue"/>, which can still be
|
||||
/// mid-flight in the same frame, and overwriting them would corrupt that
|
||||
/// replay).</para>
|
||||
///
|
||||
/// <para>Campaign FW stage FW3.4a (2026-08-30): the FW2 submitter was ONE
|
||||
/// method, <c>SubmitOrderedStream</c>, that wrote every per-instance section
|
||||
/// AND drew every merge run in one call — fine for FW2/FW3.2's proof, but the
|
||||
/// FW3.4 perf checkpoint measured ~40 of these per frame at a town (one per
|
||||
/// walk segment — every cell shell, every building's alpha barrier, every
|
||||
/// punch fan flushes the accumulated stream so far), each one re-writing all
|
||||
/// nine per-instance ring sections and rebinding everything, for that
|
||||
/// segment's handful of instances. <see cref="PrepareOrderedStream"/> +
|
||||
/// <see cref="DrawOrderedRange"/> replace it: the WHOLE frame's stream is
|
||||
/// written and bound ONCE, and each walk segment becomes a cheap
|
||||
/// <see cref="DrawOrderedRange"/> call over the ALREADY-uploaded payload —
|
||||
/// the same split <see cref="PrepareRhiAlphaSections"/>/
|
||||
/// <see cref="DrawPreparedAlphaBatchRhi"/> already prove for the alpha path.
|
||||
/// <c>SubmitOrderedStream</c> itself is deleted; every FW2 caller/test now
|
||||
/// calls the pair (prepare once, draw the whole stream as one range, or as
|
||||
/// several — see <c>OrderPreservingSubmitterTests</c>).</para>
|
||||
///
|
||||
/// <para>Scope: this stage proves walk-order submission through the existing
|
||||
/// RHI on static content (plan §FW2). It does NOT wire the retail
|
||||
/// building-detail overlay replay (<c>DrawBuildingDetailRangeRhi</c>'s second
|
||||
/// pass through <c>RetailDetail</c>/<c>RetailDetailTransparent</c>) — a
|
||||
/// detail-category command still forces a solo merge run (mirroring the
|
||||
/// deferred-alpha detail break), but this submitter issues only the
|
||||
/// base-pipeline draw for it. The overlay replay is production wiring,
|
||||
/// deferred to whichever stage cuts the walk over for real content.</para>
|
||||
/// </summary>
|
||||
public sealed unsafe partial class WbDrawDispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// One walk-order merge run: a maximal, in-order span of commands that
|
||||
/// share a <see cref="WalkDrawStage"/>, a resolved pipeline, and a cull
|
||||
/// mode, built by <see cref="BuildOrderedMergeRuns"/>.
|
||||
/// </summary>
|
||||
internal readonly record struct OrderedMergeRun(int FirstCommand, int CommandCount);
|
||||
|
||||
/// <summary>
|
||||
/// The four pipeline buckets a translucency kind resolves to, independent
|
||||
/// of any live <see cref="MeshPipelineSet"/> instance. <see cref="AlphaToCoverage"/>
|
||||
/// selects between <c>Opaque</c> and <c>OpaqueAlphaToCoverage</c> uniformly
|
||||
/// for the whole submission — it never varies per command — so merge-run
|
||||
/// legality only needs to know WHICH bucket a command falls in, not which
|
||||
/// concrete <see cref="IGpuPipeline"/> that resolves to. That is what lets
|
||||
/// <see cref="BuildOrderedMergeRuns"/> stay pure CPU logic, testable
|
||||
/// without a live GPU device — the same separation
|
||||
/// <see cref="BuildIndirectArrays"/> already draws between layout and RHI
|
||||
/// glue.
|
||||
/// </summary>
|
||||
private enum PipelineBucket
|
||||
{
|
||||
Opaque,
|
||||
AlphaBlend,
|
||||
AlphaAdditive,
|
||||
AlphaInverse,
|
||||
}
|
||||
|
||||
private static PipelineBucket BucketFor(TranslucencyKind kind)
|
||||
{
|
||||
if (IsOpaque(kind))
|
||||
return PipelineBucket.Opaque;
|
||||
|
||||
return kind switch
|
||||
{
|
||||
TranslucencyKind.Additive => PipelineBucket.AlphaAdditive,
|
||||
TranslucencyKind.InvAlpha => PipelineBucket.AlphaInverse,
|
||||
_ => PipelineBucket.AlphaBlend,
|
||||
};
|
||||
}
|
||||
|
||||
private IGpuPipeline PipelineForBucket(MeshPipelineSet pipelines, PipelineBucket bucket) =>
|
||||
bucket switch
|
||||
{
|
||||
PipelineBucket.Opaque => AlphaToCoverage ? pipelines.OpaqueAlphaToCoverage : pipelines.Opaque,
|
||||
PipelineBucket.AlphaAdditive => pipelines.AlphaAdditive,
|
||||
PipelineBucket.AlphaInverse => pipelines.AlphaInverse,
|
||||
_ => pipelines.AlphaBlend,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Builds the maximal in-order merge runs for <paramref name="stream"/>.
|
||||
/// Pure CPU: no GPU device, no live pipeline, no encoder — every legality
|
||||
/// decision is a comparison over the stream's own parallel arrays, which
|
||||
/// is what lets this be unit-tested directly.
|
||||
///
|
||||
/// <para>A run extends from command <c>i</c> to <c>j</c> while every
|
||||
/// command in <c>[i, j)</c> shares the same <see cref="WalkDrawStage"/>,
|
||||
/// the same <see cref="PipelineBucket"/>, the same <see cref="CullMode"/>,
|
||||
/// and none carries a nonzero <c>DetailCategory</c> — a detail-category
|
||||
/// command always emits alone, mirroring
|
||||
/// <c>DrawPreparedAlphaBatchRhi</c>'s <c>hasDetail</c> break. Never
|
||||
/// reorders or drops anything: every command in <paramref name="stream"/>
|
||||
/// belongs to exactly one returned run, in stream order.</para>
|
||||
///
|
||||
/// <para><paramref name="forcedBreaksAscending"/> (FW3.4a addition,
|
||||
/// default none): extra command indices, sorted ascending, at which a run
|
||||
/// must end even when the state comparison above would otherwise extend
|
||||
/// it. <see cref="WalkFrameDriver"/>'s Replay phase draws the frame's ONE
|
||||
/// prepared stream as several <see cref="DrawOrderedRange"/> calls — one
|
||||
/// per walk segment, each separated by a leaf GPU call (a cell shell, a
|
||||
/// punch fan, an alpha barrier) that MUST execute between them — and nothing
|
||||
/// about <see cref="WalkDrawStage"/>/bucket/cull/detail forbids two
|
||||
/// DIFFERENT segments from sharing all four (two consecutive indoor cells
|
||||
/// drawn Opaque/CounterClockwise, the overwhelmingly common case). Without
|
||||
/// this parameter, a whole-stream merge pass would happily fuse such
|
||||
/// segments into one run spanning the leaf call that must run BETWEEN
|
||||
/// them, silently reordering GPU commands relative to retail's walk — the
|
||||
/// one invariant this campaign may never trade away. Passing each
|
||||
/// segment's start index here makes <see cref="DrawOrderedRange"/>'s
|
||||
/// "a range boundary always coincides with a run boundary" assumption
|
||||
/// true BY CONSTRUCTION instead of by hope; that method's own assert
|
||||
/// stays as insurance against a future bug in how boundaries are
|
||||
/// supplied. Every FW2 call site keeps passing none, so existing
|
||||
/// single-segment behavior (and its tests) is unchanged.</para>
|
||||
///
|
||||
/// <para>Fails loud before building any run: <see cref="WalkDrawStage.PortalPunch"/>
|
||||
/// has no submission path (see that value's own documentation), so a
|
||||
/// stream carrying one throws immediately rather than silently degrading
|
||||
/// to some other stage's handling.</para>
|
||||
/// </summary>
|
||||
internal static List<OrderedMergeRun> BuildOrderedMergeRuns(
|
||||
OrderedDrawStream stream, IReadOnlyList<int>? forcedBreaksAscending = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
int count = stream.Count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (stream.Stages[i] == WalkDrawStage.PortalPunch)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"OrderedDrawStream command {i} carries WalkDrawStage.PortalPunch, "
|
||||
+ "which has no FW2 submission path — punch geometry emission lands "
|
||||
+ "in FW3 with the world wiring "
|
||||
+ "(docs/plans/2026-08-30-campaign-fw-frame-walk.md §FW2/§FW3). The "
|
||||
+ "stage exists now purely so stage-separation gates can exercise the "
|
||||
+ "boundary before the real emission path exists.");
|
||||
}
|
||||
}
|
||||
|
||||
IReadOnlyList<int> breaks = forcedBreaksAscending ?? Array.Empty<int>();
|
||||
int breakCursor = 0;
|
||||
|
||||
var runs = new List<OrderedMergeRun>();
|
||||
int cursor = 0;
|
||||
while (cursor < count)
|
||||
{
|
||||
// A break AT OR BEFORE cursor already ended the previous run (or
|
||||
// predates the stream entirely) — only a break STRICTLY AFTER
|
||||
// cursor can stop the one starting here.
|
||||
while (breakCursor < breaks.Count && breaks[breakCursor] <= cursor)
|
||||
breakCursor++;
|
||||
|
||||
WalkDrawStage stage = stream.Stages[cursor];
|
||||
PipelineBucket bucket = BucketFor(stream.Keys[cursor].Translucency);
|
||||
CullMode cull = stream.Keys[cursor].CullMode;
|
||||
bool detail = stream.DetailCategories[cursor] != 0;
|
||||
|
||||
int end = cursor + 1;
|
||||
if (!detail)
|
||||
{
|
||||
while (end < count
|
||||
&& !(breakCursor < breaks.Count && breaks[breakCursor] == end)
|
||||
&& stream.Stages[end] == stage
|
||||
&& stream.DetailCategories[end] == 0
|
||||
&& BucketFor(stream.Keys[end].Translucency) == bucket
|
||||
&& stream.Keys[end].CullMode == cull)
|
||||
{
|
||||
end++;
|
||||
}
|
||||
}
|
||||
|
||||
runs.Add(new OrderedMergeRun(cursor, end - cursor));
|
||||
cursor = end;
|
||||
}
|
||||
|
||||
return runs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The campaign's "assert it" rule (plan §FW2: "a merge across a state or
|
||||
/// stage boundary is forbidden by construction"). <see cref="BuildOrderedMergeRuns"/>
|
||||
/// only ever EXTENDS a run while stage/bucket/cull/detail all match, so
|
||||
/// this should never fire — it exists so a future edit to that method's
|
||||
/// loop condition fails a test immediately instead of silently drawing
|
||||
/// the wrong material state for part of a run.
|
||||
/// </summary>
|
||||
private static void ValidateMergeRun(OrderedDrawStream stream, OrderedMergeRun run)
|
||||
{
|
||||
int firstCommand = run.FirstCommand;
|
||||
WalkDrawStage stage = stream.Stages[firstCommand];
|
||||
PipelineBucket bucket = BucketFor(stream.Keys[firstCommand].Translucency);
|
||||
CullMode cull = stream.Keys[firstCommand].CullMode;
|
||||
bool detail = stream.DetailCategories[firstCommand] != 0;
|
||||
int end = firstCommand + run.CommandCount;
|
||||
|
||||
if (detail && run.CommandCount != 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Merge run [{firstCommand}, {end}) carries a nonzero DetailCategory but "
|
||||
+ $"contains {run.CommandCount} commands — a detail-category command must "
|
||||
+ "emit alone.");
|
||||
}
|
||||
|
||||
for (int i = firstCommand + 1; i < end; i++)
|
||||
{
|
||||
if (stream.Stages[i] != stage)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Merge run [{firstCommand}, {end}) crosses a WalkDrawStage boundary "
|
||||
+ $"at command {i} ({stream.Stages[i]} != {stage}) — a merge across a "
|
||||
+ "stage boundary is forbidden by construction (Campaign FW §FW2).");
|
||||
}
|
||||
if (BucketFor(stream.Keys[i].Translucency) != bucket)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Merge run [{firstCommand}, {end}) crosses a pipeline boundary at "
|
||||
+ $"command {i} — a merge across a material-state boundary is "
|
||||
+ "forbidden by construction (Campaign FW §FW2).");
|
||||
}
|
||||
if (stream.Keys[i].CullMode != cull)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Merge run [{firstCommand}, {end}) crosses a cull-mode boundary at "
|
||||
+ $"command {i} — a merge across a material-state boundary is "
|
||||
+ "forbidden by construction (Campaign FW §FW2).");
|
||||
}
|
||||
if (stream.DetailCategories[i] != 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Merge run [{firstCommand}, {end}) contains a detail-category "
|
||||
+ $"command at {i} outside a solo run — a detail-category command "
|
||||
+ "must emit alone (Campaign FW §FW2).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.2b-2: the production frame/encoder pair for
|
||||
/// <see cref="WalkFrameDriver"/>'s own <see cref="DrawOrderedRange"/>
|
||||
/// calls (contrast this stage's diagnostic-target callers, which supply
|
||||
/// their own frame/encoder). Reads the SAME world-pass scope
|
||||
/// <see cref="SubmitRhi"/> already requires (<see cref="RequireRhiFrame"/> /
|
||||
/// <c>_scope.RequireEncoder()</c>) — fails loud rather than handing the
|
||||
/// driver a null pair when the world phase is not bracketing.
|
||||
/// </summary>
|
||||
internal (IGpuFrame Frame, IGpuPassEncoder Encoder) RequireWalkSubmission() =>
|
||||
(RequireRhiFrame(), _scope!.RequireEncoder());
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.2b-2: the live colour-attachment size, for
|
||||
/// <see cref="WalkProductionFrameContext"/>'s viewport (the walk's ray
|
||||
/// caster needs the REAL viewport, not the FW0/FW1 capture-client
|
||||
/// fixture constants — see that class's own doc comment). Null outside
|
||||
/// the world phase (no scope published yet); the caller falls back to
|
||||
/// the fixture constants with a comment in that case rather than
|
||||
/// failing loud, since a missing scope here is a startup-ordering
|
||||
/// timing question, not a misconfiguration.
|
||||
/// </summary>
|
||||
internal (int Width, int Height)? WalkAttachmentExtent =>
|
||||
_scope is null ? null : (_scope.AttachmentWidth, _scope.AttachmentHeight);
|
||||
|
||||
// ── Campaign FW3.4a: the prepared-once, drawn-in-ranges pair ───────────
|
||||
//
|
||||
// Persisted state a PrepareOrderedStream call fills and every later
|
||||
// DrawOrderedRange call in the SAME frame reads. Deliberately its own set
|
||||
// — never _alpha* — for the same reason SubmitOrderedStream's per-frame
|
||||
// locals were never _alpha* (see this file's type doc comment):
|
||||
// RetailAlphaQueue can still be mid-flight when a walk segment flushes,
|
||||
// and sharing storage would corrupt whichever path writes second.
|
||||
|
||||
private OrderedDrawStream? _orderedStream;
|
||||
private List<OrderedMergeRun> _orderedRuns = new();
|
||||
private int _orderedPreparedCount;
|
||||
// Caller-supplied, exactly like SubmitOrderedStream's own frame/encoder
|
||||
// parameters were (see this file's type doc comment: the walk submitter
|
||||
// draws into whatever pass its caller has open, never pulled from
|
||||
// _frames/_scope) — DrawOrderedRange's bind-once step needs the SAME
|
||||
// frame Prepare wrote sections into, for WorldFrameSectionBinding's
|
||||
// clip-region/scene-lighting binds; RequireRhiFrame() is the wrong tool
|
||||
// here since it demands the dispatcher's OWN BeginFrame/_dynamicFrameStarted
|
||||
// bookkeeping, which the walk path never participates in.
|
||||
private IGpuFrame? _orderedFrame;
|
||||
private Matrix4x4 _orderedViewProjection;
|
||||
private uint _orderedTransformBaseInstance;
|
||||
private RhiSection _orderedInstances;
|
||||
private RhiSection _orderedBatches;
|
||||
private RhiSection _orderedClipSlots;
|
||||
private RhiSection _orderedGlobalLights;
|
||||
private RhiSection _orderedLightSets;
|
||||
private RhiSection _orderedIndoor;
|
||||
private RhiSection _orderedAlpha;
|
||||
private RhiSection _orderedSelectionLighting;
|
||||
private RhiSection _orderedDetailCategory;
|
||||
private RhiSection _orderedCommands;
|
||||
|
||||
/// <summary>
|
||||
/// Writes <paramref name="stream"/>'s ENTIRE walk-order payload into the
|
||||
/// frame ring exactly ONCE — the per-instance-first emission
|
||||
/// (<see cref="PrepareDeferredAlphaDraws"/>'s shape) followed by one
|
||||
/// section write per per-instance array (<see cref="PrepareRhiAlphaSections"/>'s
|
||||
/// shape, into the <c>_ordered*</c> fields above). No draw happens here;
|
||||
/// <see cref="DrawOrderedRange"/> issues the actual
|
||||
/// <see cref="DrawIndirectRangeRhi"/> calls against this prepared payload,
|
||||
/// as many times as the caller needs (<see cref="WalkFrameDriver"/> calls
|
||||
/// it once per walk segment, interleaved with the leaf GPU calls that
|
||||
/// must run between segments).
|
||||
///
|
||||
/// <para><paramref name="forcedBreaksAscending"/> forwards to
|
||||
/// <see cref="BuildOrderedMergeRuns"/> — see that parameter's own doc
|
||||
/// comment. Pass the walk segment boundaries here so a later
|
||||
/// <see cref="DrawOrderedRange"/> call's range always aligns with a merge
|
||||
/// run by construction.</para>
|
||||
///
|
||||
/// <para>A no-op (leaves <see cref="_orderedPreparedCount"/> at 0) when
|
||||
/// the stream is empty or the mesh source is not yet ready — mirrors
|
||||
/// <see cref="PrepareDeferredAlphaDraws"/>'s own early-outs. Fails loud
|
||||
/// BEFORE any GPU work if the stream carries an unsupported stage (see
|
||||
/// <see cref="BuildOrderedMergeRuns"/>).</para>
|
||||
/// </summary>
|
||||
internal void PrepareOrderedStream(
|
||||
IGpuFrame frame,
|
||||
OrderedDrawStream stream,
|
||||
in Matrix4x4 viewProjection,
|
||||
IReadOnlyList<int>? forcedBreaksAscending = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(frame);
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
|
||||
_orderedStream = stream;
|
||||
_orderedFrame = frame;
|
||||
_orderedPreparedCount = 0;
|
||||
|
||||
// Fail loud before any GPU work: a PortalPunch command has no
|
||||
// submission path.
|
||||
_orderedRuns = BuildOrderedMergeRuns(stream, forcedBreaksAscending);
|
||||
|
||||
int count = stream.Count;
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
GlobalMeshBuffer? global = _meshAdapter.MeshManager?.GlobalBuffer;
|
||||
if (global is null || !MeshSourceReady())
|
||||
return;
|
||||
|
||||
// Per-instance-first emission — see PrepareDeferredAlphaDraws, into
|
||||
// the SAME shared per-instance scratch arrays that method writes
|
||||
// (safe: this is a single-threaded render frame, and the writer here
|
||||
// runs to completion — including the section uploads below — before
|
||||
// any other per-instance producer touches the scratch again). Cull
|
||||
// modes are the one exception: this stage keeps its own
|
||||
// _orderedDrawCullModes scratch (see DrawIndirectRangeRhi's doc
|
||||
// comment) precisely so a walk-ordered draw can freely interleave
|
||||
// with a mid-flight RetailAlphaQueue scope without corrupting — or
|
||||
// being corrupted by — the alpha path's _drawCullModes.
|
||||
EnsureDeferredAlphaCapacity(count);
|
||||
EnsureOrderedCullModeCapacity(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
GroupKey key = stream.Keys[i];
|
||||
WriteMatrix(_instanceData, i * 16, stream.Transforms[i]);
|
||||
_clipSlotData[i] = stream.ClipSlots[i];
|
||||
_indoorData[i] = stream.IndoorFlags[i];
|
||||
_detailCategoryData[i] = stream.DetailCategories[i];
|
||||
_alphaData[i] = stream.Alphas[i];
|
||||
_selectionLightingData[i] = stream.SelectionLighting[i];
|
||||
stream.Lights[i].CopyTo(_lightSetData, i * LightManager.MaxLightsPerObject);
|
||||
|
||||
_batchData[i] = new BatchData
|
||||
{
|
||||
TextureIndex = key.TextureSlot.Index,
|
||||
TextureLayer = key.TextureLayer,
|
||||
Flags = 1u | key.FoliageFlags,
|
||||
};
|
||||
_indirectCommands[i] = new DrawElementsIndirectCommand
|
||||
{
|
||||
Count = (uint)key.IndexCount,
|
||||
InstanceCount = 1,
|
||||
FirstIndex = key.FirstIndex,
|
||||
BaseVertex = key.BaseVertex,
|
||||
BaseInstance = (uint)i,
|
||||
};
|
||||
_orderedDrawCullModes[i] = key.CullMode;
|
||||
}
|
||||
|
||||
// Write every section ONCE — the PrepareRhiAlphaSections shape, into
|
||||
// _ordered* rather than _alpha* (see this file's type doc comment).
|
||||
_orderedViewProjection = viewProjection;
|
||||
_orderedInstances = WriteWorldTransformSection(
|
||||
frame, _instanceData.AsSpan(0, count * 16), out uint transformBaseInstance);
|
||||
_orderedTransformBaseInstance = transformBaseInstance;
|
||||
_orderedBatches = WriteRingSection<BatchData>(frame, _batchData.AsSpan(0, count));
|
||||
_orderedClipSlots = WriteRingSection<uint>(frame, _clipSlotData.AsSpan(0, count));
|
||||
int lightCount = GlobalLightPacker.Pack(_pointSnapshot, ref _globalLightData);
|
||||
int uploadCount = lightCount > 0 ? lightCount : 1;
|
||||
_orderedGlobalLights = WriteRingSection<float>(
|
||||
frame,
|
||||
_globalLightData.AsSpan(0, uploadCount * GlobalLightPacker.FloatsPerLight));
|
||||
_orderedLightSets = WriteRingSection<int>(
|
||||
frame, _lightSetData.AsSpan(0, count * LightManager.MaxLightsPerObject));
|
||||
_orderedIndoor = WriteRingSection<uint>(frame, _indoorData.AsSpan(0, count));
|
||||
_orderedAlpha = WriteRingSection<float>(frame, _alphaData.AsSpan(0, count));
|
||||
_orderedSelectionLighting = WriteRingSection<Vector2>(
|
||||
frame, _selectionLightingData.AsSpan(0, count));
|
||||
_orderedDetailCategory = WriteRingSection<uint>(frame, _detailCategoryData.AsSpan(0, count));
|
||||
GpuRingAllocation commandsAllocation = WriteIndirectCommands(
|
||||
frame, _indirectCommands.AsSpan(0, count), transformBaseInstance);
|
||||
_orderedCommands = new RhiSection(
|
||||
commandsAllocation.Buffer,
|
||||
commandsAllocation.OffsetBytes,
|
||||
checked((uint)(count * DrawCommandStride)));
|
||||
|
||||
_orderedPreparedCount = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws commands <c>[firstCommand, firstCommand + commandCount)</c> of
|
||||
/// the payload the most recent <see cref="PrepareOrderedStream"/> call
|
||||
/// uploaded. Every call re-binds the pipeline, push constants, and the
|
||||
/// per-instance sections — leaf draws and alpha flushes between ranges
|
||||
/// rebind the same set-0 slots to THEIR buffers, so a latched skip draws
|
||||
/// against foreign sections (the dense-Arwic device-lost). The FW3.4a
|
||||
/// win is the once-per-frame ring WRITES in <see cref="PrepareOrderedStream"/>
|
||||
/// (what used to be ~40 full <c>SubmitOrderedStream</c> uploads per
|
||||
/// frame at a town becomes one bind plus ~40 cheap
|
||||
/// <see cref="DrawIndirectRangeRhi"/> calls). Section binds survive
|
||||
/// pipeline switches (every mesh pipeline shares one layout — the same
|
||||
/// reasoning <c>SubmitRhi</c>/the old <c>SubmitOrderedStream</c> already
|
||||
/// relied on), so binding once per frame rather than once per pipeline
|
||||
/// switch is safe.
|
||||
///
|
||||
/// <para>Fail-loud range check mirrors <see cref="DrawPreparedAlphaBatchRhi"/>'s:
|
||||
/// a range outside <c>[0, _orderedPreparedCount]</c> throws
|
||||
/// <see cref="ArgumentOutOfRangeException"/> rather than silently
|
||||
/// clamping or drawing garbage — including when nothing was ever
|
||||
/// prepared this frame (a caller drawing without preparing is a real
|
||||
/// bug, not a valid empty draw). A zero-length range is a legal no-op
|
||||
/// (mirrors an empty walk segment).</para>
|
||||
///
|
||||
/// <para>Walks the runs <see cref="PrepareOrderedStream"/> built that
|
||||
/// intersect this range. By construction (the boundaries the caller fed
|
||||
/// <see cref="PrepareOrderedStream"/> as <c>forcedBreaksAscending</c>) a
|
||||
/// run never starts before the range and never ends after it — this is
|
||||
/// asserted, not assumed: a run that straddles the range edge throws
|
||||
/// rather than being silently sliced, per plan §FW3.4a.</para>
|
||||
/// </summary>
|
||||
internal void DrawOrderedRange(IGpuPassEncoder encoder, int firstCommand, int commandCount)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(encoder);
|
||||
if (firstCommand < 0
|
||||
|| commandCount < 0
|
||||
|| firstCommand > _orderedPreparedCount - commandCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(firstCommand),
|
||||
"The ordered draw range exceeds the payload the most recent "
|
||||
+ "PrepareOrderedStream call uploaded.");
|
||||
}
|
||||
if (commandCount == 0)
|
||||
return;
|
||||
if (_orderedCommands.Buffer is null || _orderedStream is null)
|
||||
return;
|
||||
|
||||
GlobalMeshBuffer? global = _meshAdapter.MeshManager?.GlobalBuffer;
|
||||
if (global is null)
|
||||
return;
|
||||
|
||||
MeshPipelineSet pipelines = PipelinesFor(encoder);
|
||||
var pushConstants = new GpuPushConstants
|
||||
{
|
||||
ViewProjection = _orderedViewProjection,
|
||||
DrawIdOffset = 0,
|
||||
LightingMode = 0,
|
||||
RenderPass = 0,
|
||||
LightDebug = RenderingDiagnostics.LightDebugMode,
|
||||
TextureIndexA = 0,
|
||||
TextureIndexB = _orderedTransformBaseInstance,
|
||||
ParamA = 0f,
|
||||
ParamB = 0f,
|
||||
};
|
||||
|
||||
{
|
||||
IGpuFrame frame = _orderedFrame
|
||||
?? throw new InvalidOperationException(
|
||||
"DrawOrderedRange has no frame to bind clip-region/"
|
||||
+ "scene-lighting sections against — PrepareOrderedStream must run first.");
|
||||
|
||||
// Bind the SECTIONS on EVERY range call — never latch them
|
||||
// across calls. Between ordered ranges the walk's leaf draws run
|
||||
// (terrain, cell shells, sky, punch fans) and RetailAlphaQueue
|
||||
// flushes rebind the SAME set-0 storage bindings to THEIR
|
||||
// sections; a latched skip here draws the next range against the
|
||||
// alpha path's buffers — out-of-bounds instance reads and a
|
||||
// VK_ERROR_DEVICE_LOST at dense Arwic (the FW3.4a re-measure
|
||||
// crash). The expensive part — the ring WRITES — already happens
|
||||
// once per frame in PrepareOrderedStream; these are descriptor
|
||||
// binds only, the same per-batch rebinding the proven
|
||||
// DrawPreparedAlphaBatchRhi does for the same reason.
|
||||
BindPipelineWithMesh(encoder, pipelines.Opaque, global);
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
BindSection(encoder, GpuBindingModel.StorageInstances, _orderedInstances);
|
||||
BindSection(encoder, GpuBindingModel.StorageBatches, _orderedBatches);
|
||||
BindSection(encoder, GpuBindingModel.StorageClipSlots, _orderedClipSlots);
|
||||
BindSection(encoder, GpuBindingModel.StorageGlobalLights, _orderedGlobalLights);
|
||||
BindSection(encoder, GpuBindingModel.StorageInstanceLightSets, _orderedLightSets);
|
||||
BindSection(encoder, GpuBindingModel.StorageInstanceIndoor, _orderedIndoor);
|
||||
BindSection(encoder, GpuBindingModel.StorageInstanceAlpha, _orderedAlpha);
|
||||
BindSection(
|
||||
encoder, GpuBindingModel.StorageInstanceSelectionLighting, _orderedSelectionLighting);
|
||||
BindSection(
|
||||
encoder, GpuBindingModel.StorageInstanceDetailCategory, _orderedDetailCategory);
|
||||
AcDream.App.Rendering.WorldFrameSectionBinding.BindClipRegions(
|
||||
encoder, _scope!.Sections, frame);
|
||||
AcDream.App.Rendering.WorldFrameSectionBinding.BindSceneLighting(
|
||||
encoder, _scope!.Sections, frame);
|
||||
}
|
||||
|
||||
IGpuBuffer commandBuffer = _orderedCommands.Buffer!;
|
||||
uint commandBase = _orderedCommands.OffsetBytes;
|
||||
int rangeEnd = firstCommand + commandCount;
|
||||
|
||||
foreach (OrderedMergeRun run in _orderedRuns)
|
||||
{
|
||||
int runEnd = run.FirstCommand + run.CommandCount;
|
||||
if (runEnd <= firstCommand)
|
||||
continue;
|
||||
if (run.FirstCommand >= rangeEnd)
|
||||
break;
|
||||
|
||||
if (run.FirstCommand < firstCommand || runEnd > rangeEnd)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"DrawOrderedRange [{firstCommand}, {rangeEnd}) straddles merge run "
|
||||
+ $"[{run.FirstCommand}, {runEnd}) — a range boundary must coincide with "
|
||||
+ "a run boundary by construction (PrepareOrderedStream's "
|
||||
+ "forcedBreaksAscending should have forced a break here; Campaign FW "
|
||||
+ "§FW3.4a).");
|
||||
}
|
||||
|
||||
ValidateMergeRun(_orderedStream, run);
|
||||
|
||||
PipelineBucket bucket = BucketFor(_orderedStream.Keys[run.FirstCommand].Translucency);
|
||||
IGpuPipeline pipeline = PipelineForBucket(pipelines, bucket);
|
||||
pushConstants.RenderPass = bucket == PipelineBucket.Opaque ? 0 : 1;
|
||||
|
||||
BindPipelineWithMesh(encoder, pipeline, global);
|
||||
DrawIndirectRangeRhi(
|
||||
encoder, ref pushConstants, commandBuffer, commandBase,
|
||||
run.FirstCommand, run.CommandCount, _orderedDrawCullModes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grows <see cref="_orderedDrawCullModes"/> to at least
|
||||
/// <paramref name="count"/> — the same growth shape
|
||||
/// <c>EnsureDeferredAlphaCapacity</c> uses for <see cref="_drawCullModes"/>,
|
||||
/// kept as its own method because this scratch array is not part of that
|
||||
/// method's shared per-instance group (see this file's type doc comment).
|
||||
/// </summary>
|
||||
private void EnsureOrderedCullModeCapacity(int count)
|
||||
{
|
||||
if (_orderedDrawCullModes.Length < count)
|
||||
_orderedDrawCullModes = new CullMode[count + 64];
|
||||
}
|
||||
}
|
||||
|
|
@ -635,22 +635,56 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
batchIndex < renderData.Batches.Count;
|
||||
batchIndex++)
|
||||
{
|
||||
// Campaign FW stage FW3.2a: mirrors the classic ClassifyBatches
|
||||
// gate/promotion/resolve/foliage-classify sequence exactly — see
|
||||
// the one shared core (WbDrawDispatcher.WalkClassify.cs's
|
||||
// TryClassifyBatch) also used by ClassifyBatches and the walk
|
||||
// classifier, so the classic and packed classifiers cannot drift
|
||||
// (Campaign VM VM6). `survives=false` still applies
|
||||
// compositePending exactly as before this extraction.
|
||||
bool survives = TryClassifyBatch(
|
||||
renderData, batchIndex, in entity, meshRef, paletteIdentity,
|
||||
opacity, entityHasCutoutSubset,
|
||||
out GroupKey key, out bool compositePending);
|
||||
if (compositePending)
|
||||
reusableAcrossFrames = false;
|
||||
if (!survives)
|
||||
ObjectRenderBatch batch =
|
||||
renderData.Batches[batchIndex];
|
||||
|
||||
// #426: mirrors the classic ClassifyBatches gate exactly — see
|
||||
// RetailUntexturedSubsetPolicy for the retail citation. ONE
|
||||
// shared predicate so the classic and packed classifiers cannot
|
||||
// drift (Campaign VM VM6).
|
||||
if (!RetailUntexturedSubsetPolicy.Draws(entity.IsBuildingShell, batch.Key.IsSolid))
|
||||
continue;
|
||||
|
||||
TranslucencyKind translucency = batch.Translucency;
|
||||
if (opacity < 1f && IsOpaque(translucency))
|
||||
translucency = TranslucencyKind.AlphaBlend;
|
||||
|
||||
ResolvedTexture texture = ResolveTexture(
|
||||
in entity,
|
||||
meshRef,
|
||||
batch,
|
||||
paletteIdentity,
|
||||
out bool compositePending);
|
||||
if (compositePending)
|
||||
reusableAcrossFrames = false;
|
||||
if (!texture.Slot.IsAssigned)
|
||||
continue;
|
||||
|
||||
// Campaign VM VM6 review fix round 2 (F1 BLOCKER): the packed
|
||||
// production classifier never computed FoliageFlags, so the
|
||||
// production BatchData.flags word was always 0 for every
|
||||
// scenery entity — the world geometry never swayed even though
|
||||
// the independently-classified shadow caster did. Classify
|
||||
// BEFORE constructing the key, from the RAW (pre-#188-
|
||||
// promotion) batch.Translucency, exactly as the classic
|
||||
// ClassifyBatches does — see that method's own comment for why
|
||||
// raw translucency is used for classification but the (possibly
|
||||
// promoted) local `translucency` is still what the key/group
|
||||
// partitions draws by.
|
||||
uint foliageFlags = FoliageWindClassification.Classify(
|
||||
entity.LocalEntityId,
|
||||
FoliageWindExclusions.Contains(meshRef.GfxObjId),
|
||||
batch.Translucency,
|
||||
entityHasCutoutSubset);
|
||||
var key = new GroupKey(
|
||||
batch.FirstIndex,
|
||||
(int)batch.BaseVertex,
|
||||
batch.IndexCount,
|
||||
texture.Slot,
|
||||
texture.Layer,
|
||||
translucency,
|
||||
FoliageFlags: foliageFlags,
|
||||
CullMode: batch.CullMode);
|
||||
var classified = new PackedClassifiedBatch(
|
||||
key,
|
||||
restPose,
|
||||
|
|
|
|||
|
|
@ -315,12 +315,11 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
/// where <c>ApplyCullMode</c> sets them, because core Vulkan 1.3 makes those
|
||||
/// dynamic and blend and alpha-to-coverage not.
|
||||
///
|
||||
/// <para>Depth compare is <see cref="AcDream.App.Rendering.WorldDepthContract.WorldCompare"/>
|
||||
/// (<c>Less</c>), not the contract's <c>LessOrEqual</c> default — see that
|
||||
/// type for the full citation. The short version: the world frame runs
|
||||
/// under <c>GL_LESS</c> and this renderer never called <c>glDepthFunc</c>,
|
||||
/// so it inherited it. Baking <c>LessOrEqual</c> would change which of two
|
||||
/// coplanar retail surfaces wins.</para>
|
||||
/// <para>Depth compare is <c>Less</c>, not the contract's
|
||||
/// <c>LessOrEqual</c> default: the world frame runs under <c>GL_LESS</c> and
|
||||
/// this renderer never called <c>glDepthFunc</c>, so it inherited it. Baking
|
||||
/// <c>LessOrEqual</c> would change which of two coplanar retail surfaces
|
||||
/// wins.</para>
|
||||
/// </summary>
|
||||
private static IGpuPipeline CreateMeshPipeline(
|
||||
IGpuDevice device,
|
||||
|
|
@ -331,7 +330,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
int sampleCount,
|
||||
string shaderName = "mesh_modern",
|
||||
GpuShaderSet? shaders = null,
|
||||
GpuCompareOp depthCompare = AcDream.App.Rendering.WorldDepthContract.WorldCompare,
|
||||
GpuCompareOp depthCompare = GpuCompareOp.Less,
|
||||
bool usesRenderPackShaderAbi = false) =>
|
||||
device.CreatePipeline(new GpuPipelineDescription
|
||||
{
|
||||
|
|
@ -796,40 +795,23 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
_ => pipelines.AlphaBlend,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Reads cull modes from <paramref name="cullModes"/> when the caller
|
||||
/// supplies one, or from the shared <see cref="_drawCullModes"/> scratch
|
||||
/// otherwise (every pre-FW3.2a call site). Campaign FW stage FW3.2a:
|
||||
/// <see cref="SubmitOrderedStream"/> passes its OWN scratch
|
||||
/// (<see cref="_orderedDrawCullModes"/>) so a walk-ordered submission can
|
||||
/// interleave with a mid-flight <see cref="RetailAlphaQueue"/> scope —
|
||||
/// <c>_drawCullModes</c> is written fresh by
|
||||
/// <c>PrepareDeferredAlphaDraws</c> at every alpha flush and read right
|
||||
/// back by this method for that draw; an ordered submission sharing the
|
||||
/// same array between those two steps could silently draw the alpha
|
||||
/// content's faces with the ordered content's cull mode, or vice
|
||||
/// versa (the FW2 caveat this stage retires — see
|
||||
/// <c>WbDrawDispatcher.OrderedStream.cs</c>).
|
||||
/// </summary>
|
||||
private void DrawIndirectRangeRhi(
|
||||
IGpuPassEncoder encoder,
|
||||
ref GpuPushConstants pushConstants,
|
||||
IGpuBuffer commandBuffer,
|
||||
uint commandBaseOffsetBytes,
|
||||
int startCommand,
|
||||
int commandCount,
|
||||
CullMode[]? cullModes = null)
|
||||
int commandCount)
|
||||
{
|
||||
CullMode[] modes = cullModes ?? _drawCullModes;
|
||||
int end = startCommand + commandCount;
|
||||
int command = startCommand;
|
||||
while (command < end)
|
||||
{
|
||||
CullMode cullMode = modes[command];
|
||||
CullMode cullMode = _drawCullModes[command];
|
||||
ApplyCullModeRhi(encoder, cullMode);
|
||||
|
||||
int runCount = 1;
|
||||
while (command + runCount < end && modes[command + runCount] == cullMode)
|
||||
while (command + runCount < end && _drawCullModes[command + runCount] == cullMode)
|
||||
runCount++;
|
||||
|
||||
// Each multi-draw-indirect call restarts gl_DrawID at 0, so a run
|
||||
|
|
|
|||
|
|
@ -1,355 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Selection;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter.Enums;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2a: the walk-order population layer. This partial
|
||||
/// holds two things.
|
||||
///
|
||||
/// <para><b>The shared per-batch classify core</b> (<see cref="TryClassifyBatch"/>):
|
||||
/// extracted from <c>ClassifyBatches</c> (this file's sibling
|
||||
/// <c>WbDrawDispatcher.cs</c>) and <c>ClassifyPackedBatches</c>
|
||||
/// (<c>WbDrawDispatcher.PackedOracle.cs</c>), which carried byte-identical
|
||||
/// per-batch logic (the #426 untextured-subset gate, the #188 opacity
|
||||
/// promotion, texture resolution, Campaign VM foliage classification, the
|
||||
/// <see cref="GroupKey"/> construction) with only their SURROUNDING
|
||||
/// bookkeeping differing (classic appends to an <see cref="InstanceGroup"/>
|
||||
/// via caller-scoped instance fields; packed appends via explicit
|
||||
/// parameters). Both call sites now call this one method per batch and keep
|
||||
/// their own append logic — the classic and packed paths are unchanged in
|
||||
/// every observable way (same hermetic + <c>Lane=InstalledDat</c> suites
|
||||
/// stay the referee); this stage's OWN new walk classifier
|
||||
/// (<see cref="ClassifyEntityForWalk"/>) is the third caller.</para>
|
||||
///
|
||||
/// <para><b>The per-entity walk classify seam</b>
|
||||
/// (<see cref="ClassifyEntityForWalk"/>): given ONE <see cref="RenderProjectionRecord"/>
|
||||
/// — the same shape <c>ClassifyPackedEntity</c> consumes, but read straight
|
||||
/// off the record's own <see cref="RenderEntityPayload"/> via
|
||||
/// <c>RenderInstanceCandidate.FromProjection</c> rather than the packed
|
||||
/// route's frame-arena mesh-part flattening — yields one
|
||||
/// <see cref="WalkClassifiedBatch"/> per surviving (part, batch) pair WITHOUT
|
||||
/// touching any <see cref="InstanceGroup"/>, plus the per-part selection
|
||||
/// data <c>AddPackedSelectionPart</c> would have published, for
|
||||
/// <c>WalkStaticStreamPopulator</c> to publish itself (deliverable
|
||||
/// 1 leaves the publish decision — timing, stage/cell provenance — to the
|
||||
/// caller; see <see cref="PublishWalkSelectionPart"/>, the thin internal seam
|
||||
/// that keeps <c>_selectionSink</c> encapsulated).</para>
|
||||
///
|
||||
/// <para>Scope: this stage classifies static content only (no production
|
||||
/// frame wiring — see plan §FW3.2a). Per-part translucency-fade
|
||||
/// (<c>TranslucencyFadeManager</c>/<c>EntityOpacity</c>) is NOT threaded
|
||||
/// through: that mechanic is <c>TransparentPartHook</c>, a retail LIVE-entity
|
||||
/// behavior keyed by ServerGuid, not something world statics undergo — every
|
||||
/// classified batch here carries <c>Alpha = 1f</c>. Likewise the async
|
||||
/// mesh-miss self-heal request (<c>_missRequested</c>/<c>EnsureLoaded</c>,
|
||||
/// frame-scoped state cleared by <c>BeginEntityDispatch</c>) is not fired
|
||||
/// here — this seam has no production frame to be scoped to yet; a missing
|
||||
/// mesh is simply skipped, matching every other unwired FW2/FW3.2a path.</para>
|
||||
/// </summary>
|
||||
public sealed partial class WbDrawDispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// One walk-classified (entity, part, batch) draw candidate — exactly
|
||||
/// <c>OrderedDrawCommand</c>'s per-instance field set (minus
|
||||
/// the walk-provenance <c>Stage</c>/<c>CellId</c> fields, which the
|
||||
/// populator stamps on since the classifier has no notion of either)
|
||||
/// plus two fields an opaque command has no use for but a translucent
|
||||
/// one needs to reach the alpha queue: <see cref="IsOpaque"/> (so the
|
||||
/// populator can route without re-deriving it from
|
||||
/// <see cref="GroupKey.Translucency"/>) and <see cref="LocalSortCenter"/>
|
||||
/// (the authored GfxObj sort center <c>RetailAlphaOrdering.ComputeViewerDistance</c>
|
||||
/// transforms through <see cref="Transform"/> — the same value
|
||||
/// <c>InstanceGroup.LocalSortCenters</c> carries per instance today).
|
||||
/// </summary>
|
||||
internal readonly record struct WalkClassifiedBatch(
|
||||
GroupKey Key,
|
||||
Matrix4x4 Transform,
|
||||
uint ClipSlot,
|
||||
InstanceLightSet Lights,
|
||||
uint IndoorFlag,
|
||||
float Alpha,
|
||||
Vector2 SelectionLighting,
|
||||
uint DetailCategory,
|
||||
bool IsOpaque,
|
||||
Vector3 LocalSortCenter);
|
||||
|
||||
/// <summary>
|
||||
/// One retail-picking part surfaced by <see cref="ClassifyEntityForWalk"/>
|
||||
/// — the exact argument tuple <c>AddPackedSelectionPart</c>'s
|
||||
/// <c>publishSelection: true</c> branch passes to
|
||||
/// <c>IRetailSelectionRenderSink.AddVisiblePart</c>. The classify seam
|
||||
/// surfaces this; <see cref="PublishWalkSelectionPart"/> is the caller's
|
||||
/// publish call.
|
||||
/// </summary>
|
||||
internal readonly record struct WalkClassifiedSelectionPart(
|
||||
uint ServerGuid,
|
||||
uint LocalEntityId,
|
||||
int PartIndex,
|
||||
uint GfxObjId,
|
||||
Matrix4x4 LocalToWorld);
|
||||
|
||||
/// <summary>
|
||||
/// The shared per-batch classify core. Given one already-resolved
|
||||
/// <paramref name="renderData"/> and the batch at <paramref name="batchIndex"/>,
|
||||
/// applies — in this exact order, matching both <c>ClassifyBatches</c> and
|
||||
/// <c>ClassifyPackedBatches</c> before this extraction — the #426
|
||||
/// untextured-subset-on-a-shell-only gate, the #188 mid-fade-forces-
|
||||
/// AlphaBlend promotion, texture resolution, and Campaign VM foliage
|
||||
/// classification, then builds the surviving batch's <see cref="GroupKey"/>.
|
||||
///
|
||||
/// <para>Returns false when the batch does not survive — either the
|
||||
/// untextured-subset gate rejected it (in which case
|
||||
/// <paramref name="compositePending"/> is always false: <c>ResolveTexture</c>
|
||||
/// is never called) or its resolved texture slot is unassigned (in which
|
||||
/// case <paramref name="compositePending"/> still reflects whatever
|
||||
/// <c>ResolveTexture</c> reported — a caller must apply it to its own
|
||||
/// reusability/readiness tracking regardless of the false return, exactly
|
||||
/// as both existing callers did before this extraction).</para>
|
||||
/// </summary>
|
||||
private bool TryClassifyBatch(
|
||||
ObjectRenderData renderData,
|
||||
int batchIndex,
|
||||
in RenderInstanceCandidate entity,
|
||||
MeshRef meshRef,
|
||||
PaletteCompositeIdentity paletteIdentity,
|
||||
float opacityMultiplier,
|
||||
bool entityHasCutoutSubset,
|
||||
out GroupKey key,
|
||||
out bool compositePending)
|
||||
{
|
||||
key = default;
|
||||
compositePending = false;
|
||||
ObjectRenderBatch batch = renderData.Batches[batchIndex];
|
||||
|
||||
// #426 — see RetailUntexturedSubsetPolicy for the retail citation.
|
||||
// ONE shared predicate for every classifier so they cannot drift.
|
||||
if (!RetailUntexturedSubsetPolicy.Draws(entity.IsBuildingShell, batch.Key.IsSolid))
|
||||
return false;
|
||||
|
||||
TranslucencyKind translucency = batch.Translucency;
|
||||
|
||||
// #188: a mid-fade instance whose surface is otherwise Opaque/ClipMap
|
||||
// must route through the alpha-blend pass so mesh_modern.frag's
|
||||
// (blend-enabled) shader actually composites the reduced alpha — the
|
||||
// no-blend opaque pass would ignore it.
|
||||
if (opacityMultiplier < 1.0f && IsOpaque(translucency))
|
||||
translucency = TranslucencyKind.AlphaBlend;
|
||||
|
||||
ResolvedTexture texture = ResolveTexture(
|
||||
in entity, meshRef, batch, paletteIdentity, out compositePending);
|
||||
if (!texture.Slot.IsAssigned)
|
||||
return false;
|
||||
|
||||
// Classify from the RAW (pre-#188-promotion) batch.Translucency — a
|
||||
// mid-fade trunk is still a trunk, it just landed in the alpha-blend
|
||||
// group instead of opaque (Campaign VM VM6 review fix round).
|
||||
uint foliageFlags = FoliageWindClassification.Classify(
|
||||
entity.LocalEntityId,
|
||||
FoliageWindExclusions.Contains(meshRef.GfxObjId),
|
||||
batch.Translucency,
|
||||
entityHasCutoutSubset);
|
||||
key = new GroupKey(
|
||||
batch.FirstIndex, (int)batch.BaseVertex,
|
||||
batch.IndexCount, texture.Slot, texture.Layer, translucency,
|
||||
FoliageFlags: foliageFlags,
|
||||
CullMode: batch.CullMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Classifies one static entity for the walk populator: resolves its clip
|
||||
/// slot / light set / selection lighting exactly as
|
||||
/// <c>ClassifyPackedEntity</c> does (via the shared
|
||||
/// <see cref="ResolveSlotForFrame"/> / <c>ResolvePackedLightSet</c>
|
||||
/// helpers), walks its Setup parts or single mesh (mirroring
|
||||
/// <c>ClassifyPackedEntity</c>'s own shape), and appends one
|
||||
/// <see cref="WalkClassifiedBatch"/> per surviving batch to
|
||||
/// <paramref name="batches"/> plus one <see cref="WalkClassifiedSelectionPart"/>
|
||||
/// per part to <paramref name="selectionParts"/> — WITHOUT touching
|
||||
/// <c>_groups</c>/<c>_packedGroups</c> or publishing selection itself (see
|
||||
/// this file's type doc comment).
|
||||
///
|
||||
/// <para>A culled entity (<see cref="ResolveSlotForFrame"/> returns
|
||||
/// <c>Culled: true</c> — not visible through the active clip route)
|
||||
/// contributes nothing, matching every other classifier's cull gate.</para>
|
||||
/// </summary>
|
||||
internal void ClassifyEntityForWalk(
|
||||
in RenderProjectionRecord projection,
|
||||
uint tupleLandblockId,
|
||||
List<WalkClassifiedBatch> batches,
|
||||
List<WalkClassifiedSelectionPart> selectionParts)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(batches);
|
||||
ArgumentNullException.ThrowIfNull(selectionParts);
|
||||
|
||||
RenderInstanceCandidate entity =
|
||||
RenderInstanceCandidate.FromProjection(in projection, tupleLandblockId);
|
||||
|
||||
(uint slot, bool culled) = ResolveSlotForFrame(
|
||||
_clipRoutingActive, entity.ServerGuid, entity.ParentCell,
|
||||
_cellIdToSlot, _outdoorSlot, _outdoorVisible);
|
||||
if (culled)
|
||||
return;
|
||||
|
||||
ResolvePackedLightSet(in entity, out InstanceLightSet lights, out bool indoor);
|
||||
Vector2 selectionLighting =
|
||||
_selectionLighting?.TryGetLighting(
|
||||
entity.ServerGuid, entity.LocalEntityId, out RetailSelectionLighting lighting) == true
|
||||
? new Vector2(lighting.Luminosity, lighting.Diffuse)
|
||||
: new Vector2(0f, 1f);
|
||||
uint detailCategory = entity.IsBuildingShell ? 1u : 0u;
|
||||
|
||||
PaletteCompositeIdentity paletteIdentity = default;
|
||||
if (entity.PaletteOverride is not null)
|
||||
paletteIdentity = TextureCache.GetPaletteIdentity(entity.PaletteOverride);
|
||||
|
||||
IReadOnlyList<MeshRef>? meshRefs = projection.EntityPayload.MeshRefs;
|
||||
if (meshRefs is null)
|
||||
return;
|
||||
|
||||
for (int partIndex = 0; partIndex < meshRefs.Count; partIndex++)
|
||||
{
|
||||
MeshRef meshRef = meshRefs[partIndex];
|
||||
ObjectRenderData? renderData = _meshAdapter.TryGetRenderData(meshRef.GfxObjId);
|
||||
if (renderData is null)
|
||||
continue;
|
||||
|
||||
if (renderData.IsSetup && renderData.SetupParts.Count > 0)
|
||||
{
|
||||
// Same entity-scoped OR the classic/packed classifiers compute
|
||||
// — a Setup composite's parts are separate GfxObjs with their
|
||||
// own independently cached HasCutoutSubset (Campaign VM VM6
|
||||
// review fix round A4/F1).
|
||||
bool entityHasCutoutSubset = FoliageWindClassification.ComputeEntityHasCutoutSubset(
|
||||
renderData.SetupParts,
|
||||
_meshAdapter,
|
||||
static (adapter, part) => adapter.TryGetRenderData(part.GfxObjId)
|
||||
is { HasCutoutSubset: true });
|
||||
|
||||
for (int setupPartIndex = 0; setupPartIndex < renderData.SetupParts.Count; setupPartIndex++)
|
||||
{
|
||||
(ulong gfxObjId, Matrix4x4 partTransform) = renderData.SetupParts[setupPartIndex];
|
||||
ObjectRenderData? partData = _meshAdapter.TryGetRenderData(gfxObjId);
|
||||
if (partData is null)
|
||||
continue;
|
||||
|
||||
Matrix4x4 restPose = partTransform * meshRef.PartTransform;
|
||||
Matrix4x4 model = restPose * entity.RootWorld;
|
||||
int selectionPartIndex = unchecked((partIndex << 16) | (setupPartIndex & 0xFFFF));
|
||||
|
||||
EmitClassifiedBatches(
|
||||
partData, model, in entity, meshRef, paletteIdentity,
|
||||
entityHasCutoutSubset, slot, lights, indoor, selectionLighting,
|
||||
detailCategory, batches);
|
||||
selectionParts.Add(new WalkClassifiedSelectionPart(
|
||||
entity.ServerGuid, entity.LocalEntityId, selectionPartIndex,
|
||||
(uint)gfxObjId, model));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Matrix4x4 model = meshRef.PartTransform * entity.RootWorld;
|
||||
EmitClassifiedBatches(
|
||||
renderData, model, in entity, meshRef, paletteIdentity,
|
||||
entityHasCutoutSubsetOverride: null, slot, lights, indoor,
|
||||
selectionLighting, detailCategory, batches);
|
||||
selectionParts.Add(new WalkClassifiedSelectionPart(
|
||||
entity.ServerGuid, entity.LocalEntityId, partIndex,
|
||||
(uint)meshRef.GfxObjId, model));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walks one resolved mesh's batches through <see cref="TryClassifyBatch"/>
|
||||
/// and appends every surviving one to <paramref name="sink"/> at
|
||||
/// <c>Alpha = 1f</c> (see this file's type doc comment for why statics
|
||||
/// never carry a per-part opacity multiplier here).
|
||||
/// </summary>
|
||||
private void EmitClassifiedBatches(
|
||||
ObjectRenderData renderData,
|
||||
Matrix4x4 model,
|
||||
in RenderInstanceCandidate entity,
|
||||
MeshRef meshRef,
|
||||
PaletteCompositeIdentity paletteIdentity,
|
||||
bool? entityHasCutoutSubsetOverride,
|
||||
uint slot,
|
||||
InstanceLightSet lights,
|
||||
bool indoor,
|
||||
Vector2 selectionLighting,
|
||||
uint detailCategory,
|
||||
List<WalkClassifiedBatch> sink)
|
||||
{
|
||||
bool entityHasCutoutSubset = entityHasCutoutSubsetOverride ?? renderData.HasCutoutSubset;
|
||||
for (int batchIdx = 0; batchIdx < renderData.Batches.Count; batchIdx++)
|
||||
{
|
||||
bool survives = TryClassifyBatch(
|
||||
renderData, batchIdx, in entity, meshRef, paletteIdentity,
|
||||
opacityMultiplier: 1.0f, entityHasCutoutSubset,
|
||||
out GroupKey key, out _);
|
||||
if (!survives)
|
||||
continue;
|
||||
|
||||
sink.Add(new WalkClassifiedBatch(
|
||||
key, model, slot, lights, indoor ? 1u : 0u, Alpha: 1f,
|
||||
selectionLighting, detailCategory, IsOpaque: IsOpaque(key.Translucency),
|
||||
LocalSortCenter: renderData.SortCenter));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The thin internal publish seam <c>WalkStaticStreamPopulator</c>
|
||||
/// calls for every <see cref="WalkClassifiedSelectionPart"/>
|
||||
/// <see cref="ClassifyEntityForWalk"/> surfaced — same call shape as
|
||||
/// <c>AddPackedSelectionPart</c>'s <c>publishSelection: true</c> branch.
|
||||
/// Keeps <c>_selectionSink</c> encapsulated: the populator lives outside
|
||||
/// the dispatcher and must not reach the field directly.
|
||||
/// </summary>
|
||||
internal void PublishWalkSelectionPart(in WalkClassifiedSelectionPart part) =>
|
||||
_selectionSink?.AddVisiblePart(
|
||||
part.ServerGuid, part.LocalEntityId, part.PartIndex, part.GfxObjId, part.LocalToWorld);
|
||||
|
||||
/// <summary>
|
||||
/// The walk populator's per-instance sibling of <c>DeferTransparentGroups</c>
|
||||
/// (see that method for the retail citations this mirrors): submits ONE
|
||||
/// translucent <see cref="WalkClassifiedBatch"/> into the same
|
||||
/// <c>_deferredAlpha</c>/<see cref="RetailAlphaQueue"/> machinery the
|
||||
/// classic material-grouped path uses, so scenery, particles, and walk
|
||||
/// content share retail's one stable far-to-near stream. Same
|
||||
/// view-projection consistency check, same
|
||||
/// <see cref="RetailAlphaOrdering.ComputeViewerDistance"/> call, same
|
||||
/// <c>queue.Submit</c> contract — the walk path denormalizes to one
|
||||
/// instance per call instead of flattening a material group.
|
||||
/// </summary>
|
||||
internal void SubmitWalkAlphaInstance(
|
||||
in WalkClassifiedBatch batch,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection)
|
||||
{
|
||||
RetailAlphaQueue queue = _alphaQueue
|
||||
?? throw new InvalidOperationException(
|
||||
"SubmitWalkAlphaInstance requires an active RetailAlphaQueue.");
|
||||
|
||||
if (_deferredAlpha.Count == 0)
|
||||
_deferredAlphaViewProjection = viewProjection;
|
||||
else if (_deferredAlphaViewProjection != viewProjection)
|
||||
throw new InvalidOperationException(
|
||||
"One retail alpha scope cannot combine different view-projection matrices.");
|
||||
|
||||
float viewerDistance = RetailAlphaOrdering.ComputeViewerDistance(
|
||||
batch.LocalSortCenter, batch.Transform, cameraWorldPosition);
|
||||
if (!float.IsFinite(viewerDistance) || viewerDistance <= 0f)
|
||||
viewerDistance = 0f;
|
||||
|
||||
int token = _deferredAlpha.Count;
|
||||
_deferredAlpha.Add(new DeferredAlphaInstance(
|
||||
batch.Key, batch.Transform, batch.ClipSlot, batch.Lights,
|
||||
batch.IndoorFlag, batch.DetailCategory, batch.Alpha, batch.SelectionLighting));
|
||||
queue.Submit(_alphaSource, token, viewerDistance);
|
||||
}
|
||||
}
|
||||
|
|
@ -598,12 +598,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
private BatchData[] _batchData = new BatchData[256];
|
||||
private DrawElementsIndirectCommand[] _indirectCommands = new DrawElementsIndirectCommand[256];
|
||||
private CullMode[] _drawCullModes = new CullMode[256];
|
||||
|
||||
// Campaign FW stage FW3.2a: SubmitOrderedStream's OWN cull-mode scratch,
|
||||
// separate from _drawCullModes above. See DrawIndirectRangeRhi's doc
|
||||
// comment for why sharing the array made an ordered submission unsafe to
|
||||
// interleave with a mid-flight RetailAlphaQueue scope.
|
||||
private CullMode[] _orderedDrawCullModes = new CullMode[256];
|
||||
private BatchDataPublic[] _batchPublicScratch = new BatchDataPublic[256];
|
||||
private readonly List<IndirectGroupInput> _groupInputScratch = new(256);
|
||||
private readonly List<GroupKey> _retiredGroupKeys = new();
|
||||
|
|
@ -3318,25 +3312,62 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
bool allTexturesReady = true;
|
||||
for (int batchIdx = 0; batchIdx < renderData.Batches.Count; batchIdx++)
|
||||
{
|
||||
// Campaign FW stage FW3.2a: the untextured-subset gate, the #188
|
||||
// opacity promotion, texture resolution, and Campaign VM foliage
|
||||
// classification now live in the one shared core
|
||||
// (WbDrawDispatcher.WalkClassify.cs) also used by
|
||||
// ClassifyPackedBatches and the walk classifier — see
|
||||
// TryClassifyBatch's doc comment. `survives=false` still applies
|
||||
// compositePending exactly as before this extraction (a batch
|
||||
// that fails the untextured-subset gate never touched
|
||||
// ResolveTexture, so compositePending is always false there; a
|
||||
// batch with an unresolved texture slot still reports it).
|
||||
bool survives = TryClassifyBatch(
|
||||
renderData, batchIdx, in entity, meshRef, paletteIdentity,
|
||||
opacityMultiplier, entityHasCutoutSubset,
|
||||
out GroupKey key, out bool compositePending);
|
||||
var batch = renderData.Batches[batchIdx];
|
||||
|
||||
// #426: retail's D3DPolyRender::DrawMesh skips an UNTEXTURED
|
||||
// (solid-colour) subset only on a BUILDING SHELL
|
||||
// (RenderDeviceD3D::DrawBuilding sets ObjBuildingOrBuildingPart);
|
||||
// ordinary statics/scenery/creatures/items draw it same as any
|
||||
// textured subset. ONE shared predicate with ClassifyPackedBatches
|
||||
// and AddDirectionalShadowBatches — see RetailUntexturedSubsetPolicy.
|
||||
if (!RetailUntexturedSubsetPolicy.Draws(entity.IsBuildingShell, batch.Key.IsSolid))
|
||||
continue;
|
||||
|
||||
TranslucencyKind translucency = batch.Translucency;
|
||||
|
||||
// #188: a mid-fade instance whose surface is otherwise Opaque/ClipMap
|
||||
// must route through the alpha-blend pass so mesh_modern.frag's
|
||||
// (blend-enabled) shader actually composites the reduced alpha —
|
||||
// the no-blend opaque pass would ignore it.
|
||||
if (opacityMultiplier < 1.0f && IsOpaque(translucency))
|
||||
translucency = TranslucencyKind.AlphaBlend;
|
||||
|
||||
ResolvedTexture texture = ResolveTexture(
|
||||
in entity,
|
||||
meshRef,
|
||||
batch,
|
||||
paletteIdentity,
|
||||
out bool compositePending);
|
||||
if (compositePending)
|
||||
allTexturesReady = false;
|
||||
if (!survives)
|
||||
continue;
|
||||
GpuTextureSlot texSlot = key.TextureSlot;
|
||||
// Campaign V slice V4t: an unassigned slot is the "no texture yet"
|
||||
// case a zero handle used to signal. It is a real sentinel
|
||||
// (GpuTextureSlot.Unassigned == ACDREAM_TEXTURE_NONE), not the
|
||||
// default value, so nothing here can silently resolve to slot 0.
|
||||
if (!texture.Slot.IsAssigned) continue;
|
||||
GpuTextureSlot texSlot = texture.Slot;
|
||||
uint texLayer = texture.Layer;
|
||||
|
||||
// Campaign VM VM6 review fix round: classify BEFORE constructing
|
||||
// the key and fold the result INTO the key (rather than
|
||||
// stamping it onto whatever group the key already resolves to).
|
||||
// Classification is from the RAW (pre-#188-promotion)
|
||||
// batch.Translucency — a mid-fade trunk is still a trunk, it
|
||||
// just landed in the alpha-blend group instead of opaque. This
|
||||
// is what keeps a scenery instance and a non-scenery instance
|
||||
// of the identical mesh subset in two SEPARATE groups instead of
|
||||
// coalescing into one group whose classification depends on
|
||||
// whichever entity classified it last.
|
||||
uint foliageFlags = FoliageWindClassification.Classify(
|
||||
entity.LocalEntityId,
|
||||
FoliageWindExclusions.Contains(meshRef.GfxObjId),
|
||||
batch.Translucency,
|
||||
entityHasCutoutSubset);
|
||||
var key = new GroupKey(
|
||||
batch.FirstIndex, (int)batch.BaseVertex,
|
||||
batch.IndexCount, texSlot, texLayer, translucency,
|
||||
FoliageFlags: foliageFlags,
|
||||
CullMode: batch.CullMode);
|
||||
|
||||
InstanceGroup grp = GetOrCreateInstanceGroup(key);
|
||||
grp.Matrices.Add(model);
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
using AcDream.App.Rendering.Gpu;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW2: the one depth-compare operator every world-space
|
||||
/// pipeline uses, named instead of repeated as a literal at each of the
|
||||
/// world's own pipeline-creation sites.
|
||||
///
|
||||
/// <para><b>Why <see cref="GpuCompareOp.Less"/>, not
|
||||
/// <see cref="GpuPipelineDescription"/>'s/<c>GpuDepthState</c>'s own
|
||||
/// <c>LessOrEqual</c> convention default:</b> the GL-era world frame ran under
|
||||
/// <c>GL_LESS</c> and never called <c>glDepthFunc</c> to change it, so every
|
||||
/// world-space renderer inherited <c>GL_LESS</c> by omission rather than by
|
||||
/// design. <c>LessOrEqual</c> would flip which of two exactly-coplanar retail
|
||||
/// surfaces wins the depth test — visible wherever terrain meets a road or a
|
||||
/// building footing, or wherever two retail-authored polygons share a plane.
|
||||
/// FW2's decomp verification (plan §FW2, read 2026-08-30) confirms retail's
|
||||
/// own world raster state is <c>D3DCMP_LESS</c>: the .data default
|
||||
/// <c>Render::zfuncVal</c> @0x00820e1c is <c>0x2</c>, and
|
||||
/// <c>RenderDeviceD3D::SetDepthBufferMode</c> @0x005a2d10 writes that enum
|
||||
/// value DIRECTLY as <c>D3DRS_ZFUNC</c> (render state 0x17) — the enum IS
|
||||
/// <c>D3DCMPFUNC</c>, so <c>0x2 = D3DCMP_LESS</c>. The surface-state applier
|
||||
/// @0x0059c80a–0x0059c866 applies it to all world geometry with Z-write
|
||||
/// toggled by blend state (on for opaque, off for blended), exactly this
|
||||
/// pipeline set's per-variant <c>depthWrite</c>. The <c>DEPTHTEST_LESSEQUAL</c>
|
||||
/// sites in the decomp are SKY-local (<c>GameSky::Draw</c> @0x00506ff0, drawn
|
||||
/// at 4× zfar) — never world state. So <c>Less</c> is not merely "what the
|
||||
/// port happened to inherit" — it is retail's actual world depth-compare
|
||||
/// operator, now named as a citable contract instead of a bare literal
|
||||
/// repeated at each call site.</para>
|
||||
///
|
||||
/// <para>Scope: WORLD-SPACE geometry only (terrain, EnvCell shells, entity
|
||||
/// meshes, particles, portal punches, the directional shadow caster/receiver
|
||||
/// pair). The two <c>RetailDetail</c> pipelines
|
||||
/// (<see cref="RetailDetailTextureContract"/>'s <c>Equal</c>/<c>LessOrEqual</c>
|
||||
/// pair, used by the building-detail overlay replay) are a documented
|
||||
/// exception with their own citation and are untouched by this contract.</para>
|
||||
/// </summary>
|
||||
internal static class WorldDepthContract
|
||||
{
|
||||
/// <summary>Retail's world-space depth-compare operator. See the type
|
||||
/// doc comment for the full citation; every world pipeline site should
|
||||
/// reference this constant rather than spelling <c>GpuCompareOp.Less</c>
|
||||
/// again.</summary>
|
||||
public const GpuCompareOp WorldCompare = GpuCompareOp.Less;
|
||||
}
|
||||
|
|
@ -178,15 +178,6 @@ public sealed class LandblockBuildFactory
|
|||
worldOffset,
|
||||
_heightTable));
|
||||
var envCellBuild = new AcDream.App.Rendering.Wb.EnvCellLandblockBuildBuilder(landblockId);
|
||||
// Campaign FW3.1: the walk landscape's per-block z-slab
|
||||
// (CLandBlock::calc_lighting-adjacent unpack @0x0052f1d0). Every
|
||||
// near-tier landblock has outdoor terrain heights regardless of
|
||||
// whether it has interior cells or buildings, so this is
|
||||
// unconditional — unlike WalkBuildings (added below, inside
|
||||
// BuildInteriorEntitiesForStreaming, where LandBlockInfo is already
|
||||
// in hand).
|
||||
(float walkMaxZ, float walkMinZ) = ComputeWalkZSlab(baseLoaded.Heightmap.Height);
|
||||
envCellBuild.SetWalkZSlab(walkMaxZ, walkMinZ);
|
||||
merged.AddRange(BuildInteriorEntitiesForStreaming(
|
||||
landblockId,
|
||||
lbX,
|
||||
|
|
@ -457,17 +448,6 @@ public sealed class LandblockBuildFactory
|
|||
(lbY - origin.CenterY) * 192f,
|
||||
0f);
|
||||
|
||||
// Campaign FW3.1: the walk's building placements (portals + drawing
|
||||
// BSP + degrade ladder + sort center), read from the SAME
|
||||
// LandBlockInfo.Buildings this method already fetched above — no
|
||||
// extra DAT read. This closes the gap where Wb.BuildingLoader
|
||||
// .AddBuilding (update thread, no DAT access) keeps only
|
||||
// ModelId/Frame/portal-flags/stab-lists for its own BFS seeding and
|
||||
// drops everything the frame walk needs.
|
||||
envCellBuild.AddWalkBuildings(
|
||||
AcDream.App.Rendering.Walk.WalkBuildingFactory.Build(
|
||||
_dats, landblockId, lbInfo.Buildings, lbOffset));
|
||||
|
||||
// Per-landblock id namespace — see AcDream.Core.World.InteriorEntityIdAllocator
|
||||
// for the full bit layout + history. Distinct from scenery (0x80000000+) and
|
||||
// landblock stabs (0xC0000000+, ids from LandblockLoader).
|
||||
|
|
@ -532,20 +512,21 @@ public sealed class LandblockBuildFactory
|
|||
// Static objects inside the cell continue to flow through the dispatcher
|
||||
// as WorldEntity records below — they have real GfxObj MeshRefs that work
|
||||
// fine; EnvCellRenderer receives only the completed shell transaction.
|
||||
// Transforms — needed by the portal-visibility cell AND the
|
||||
// Transforms — needed by the portal-visibility cell (unlifted) AND the
|
||||
// render/physics path. Computed for EVERY cell with a valid cellStruct,
|
||||
// not just drawable ones. Campaign FW3.3 (2026-08-30): the +0.02 m
|
||||
// ShellDrawLiftZ render lift is RETIRED (register row AP-32 deleted) —
|
||||
// retail draws cell geometry at the dat EnvCell origin verbatim, and the
|
||||
// coplanar tie-breaks the lift stood in for are owned by the walk's
|
||||
// retail draw order under depth Less. Render and physics now share the
|
||||
// one verbatim transform.
|
||||
// not just drawable ones. Keep the small render lift out of physics; retail
|
||||
// BSP contact planes use the EnvCell origin verbatim. The lift constant is
|
||||
// shared with every draw-space consumer of portal polygons (OutsideView
|
||||
// gate, seal/punch fans) — PortalVisibilityBuilder.ShellDrawLiftZ (#130).
|
||||
var physicsCellOrigin = envCell.Position.Origin + lbOffset;
|
||||
var cellOrigin = physicsCellOrigin;
|
||||
var cellOrigin = physicsCellOrigin + new System.Numerics.Vector3(
|
||||
0f, 0f, AcDream.App.Rendering.PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var cellTransform =
|
||||
System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) *
|
||||
System.Numerics.Matrix4x4.CreateTranslation(cellOrigin);
|
||||
var physicsCellTransform = cellTransform;
|
||||
var physicsCellTransform =
|
||||
System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) *
|
||||
System.Numerics.Matrix4x4.CreateTranslation(physicsCellOrigin);
|
||||
|
||||
// PORTAL VISIBILITY: register EVERY cell with a valid cellStruct, regardless
|
||||
// of whether CellMesh.Build produced drawable sub-meshes. A portals-only
|
||||
|
|
@ -668,24 +649,6 @@ public sealed class LandblockBuildFactory
|
|||
}
|
||||
|
||||
|
||||
/// <summary>Campaign FW3.1: retail's per-block z-slab
|
||||
/// (<c>WalkLandscapeDatBuilder</c>'s port target —
|
||||
/// <c>CLandBlock::calc_lighting</c>-adjacent unpack @0x0052f1d0):
|
||||
/// <c>max_zval = heightTable[maxByte] + 200</c>,
|
||||
/// <c>min_zval = heightTable[minByte] - 1</c>, over the landblock's own
|
||||
/// 81-byte heightmap. Uses <see cref="_heightTable"/> — already a
|
||||
/// constructor field, so this needs no additional DAT read.</summary>
|
||||
private (float MaxZ, float MinZ) ComputeWalkZSlab(byte[] heights)
|
||||
{
|
||||
byte maxByte = 0, minByte = 255;
|
||||
foreach (byte h in heights)
|
||||
{
|
||||
if (h > maxByte) maxByte = h;
|
||||
if (h < minByte) minByte = h;
|
||||
}
|
||||
return (_heightTable[maxByte] + 200f, _heightTable[minByte] - 1f);
|
||||
}
|
||||
|
||||
private static float SampleTerrainZ(DatReaderWriter.DBObjs.LandBlock block, float[] heightTable, float localX, float localY)
|
||||
{
|
||||
uint landblockX = (block.Id >> 24) & 0xFFu;
|
||||
|
|
|
|||
|
|
@ -76,11 +76,6 @@ public sealed class LandblockPresentationPipeline
|
|||
private readonly Action<LandblockBuild, LandblockMeshData>?
|
||||
_publishBeforeSpatialCommit;
|
||||
private readonly LandblockRenderPublisher? _renderPublisher;
|
||||
|
||||
/// <summary>Campaign FW3.2b-2: the render publisher, exposed so the
|
||||
/// frame composition can hand its walk world-data registries
|
||||
/// (<c>WalkBuildings</c>/<c>WalkLandscape</c>) to the PView renderer.</summary>
|
||||
internal LandblockRenderPublisher? RenderPublisher => _renderPublisher;
|
||||
private readonly LandblockPhysicsPublisher? _physicsPublisher;
|
||||
private readonly LandblockStaticPresentationPublisher? _staticPublisher;
|
||||
private readonly GpuWorldState _state;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Diagnostics;
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
using AcDream.Core.Terrain;
|
||||
|
||||
namespace AcDream.App.Streaming;
|
||||
|
|
@ -103,17 +102,6 @@ public sealed class LandblockRenderPublisher
|
|||
private readonly Action<EnvCellLandblockBuild>? _prepareEnvCells;
|
||||
private readonly Action<uint>? _removeEnvCells;
|
||||
private readonly Dictionary<uint, BuildingRegistry> _buildingRegistries = new();
|
||||
// Campaign FW3.1: the walk's production world-data siblings of
|
||||
// _buildingRegistries. Owned here (not injected) exactly like
|
||||
// _buildingRegistries — no caller constructs a LandblockRenderPublisher
|
||||
// with pre-existing walk state. Committed/retired at the same points as
|
||||
// the building registry (see AdvanceCompleteOne / RemoveBuildingRegistry
|
||||
// below) because both come from the same landblock's BuildingInfo array
|
||||
// at the same commit. No frame reads these yet (FW3.2 wires the walk
|
||||
// into the render loop) — publication happens now so FW3.1's
|
||||
// conformance gate exercises the production commit path.
|
||||
private readonly WalkBuildingRegistry _walkBuildingRegistry = new();
|
||||
private readonly WalkLandscapeAssembler _walkLandscape = new();
|
||||
|
||||
private long _beginCount;
|
||||
private long _completeCount;
|
||||
|
|
@ -160,15 +148,6 @@ public sealed class LandblockRenderPublisher
|
|||
public IReadOnlyCollection<BuildingRegistry> BuildingRegistries =>
|
||||
_buildingRegistries.Values;
|
||||
|
||||
/// <summary>Campaign FW3.1: the walk's production building placements,
|
||||
/// keyed by landblock, committed alongside <see cref="BuildingRegistries"/>.</summary>
|
||||
public WalkBuildingRegistry WalkBuildings => _walkBuildingRegistry;
|
||||
|
||||
/// <summary>Campaign FW3.1: the walk's production viewer-centred
|
||||
/// landscape grid, fed from the same landblock commits as
|
||||
/// <see cref="WalkBuildings"/>.</summary>
|
||||
public WalkLandscapeAssembler WalkLandscape => _walkLandscape;
|
||||
|
||||
public LandblockRenderPublisherDiagnostics Diagnostics => new(
|
||||
_beginCount,
|
||||
_completeCount,
|
||||
|
|
@ -360,21 +339,6 @@ public sealed class LandblockRenderPublisher
|
|||
_buildingRegistries[registryKey] =
|
||||
completedBuildings.Registry;
|
||||
}
|
||||
// Campaign FW3.1: publish this landblock's walk world data in
|
||||
// the same step — build.EnvCells carries WalkBuildings/WalkMaxZ
|
||||
// /WalkMinZ (computed worker-side by LandblockBuildFactory) for
|
||||
// every near-tier build, even one with zero interior cells
|
||||
// (the z-slab is unconditional; WalkBuildings is naturally empty
|
||||
// when LandBlockInfo has none).
|
||||
if (build.EnvCells is { } walkEnvCells)
|
||||
{
|
||||
_walkBuildingRegistry.Publish(landblockId, walkEnvCells.WalkBuildings);
|
||||
_walkLandscape.PublishLandblock(
|
||||
landblockId,
|
||||
walkEnvCells.WalkMaxZ,
|
||||
walkEnvCells.WalkMinZ,
|
||||
walkEnvCells.WalkBuildings);
|
||||
}
|
||||
publication.BuildingRegistryCommitted = true;
|
||||
}
|
||||
else if (publication.EnvCellPublication is { } envCellPublication
|
||||
|
|
@ -433,14 +397,6 @@ public sealed class LandblockRenderPublisher
|
|||
public void RemoveBuildingRegistry(uint landblockId)
|
||||
{
|
||||
_buildingRegistries.Remove(landblockId & 0xFFFF0000u);
|
||||
// Campaign FW3.1: retire the walk's siblings at the SAME retirement
|
||||
// stage — they were committed together above, so they retire
|
||||
// together (no new LandblockRetirementStage; folding into the
|
||||
// existing BuildingRegistry stage keeps the retirement ticket state
|
||||
// machine unchanged, which is the minimal/additive choice for a
|
||||
// slice that does no frame wiring yet).
|
||||
_walkBuildingRegistry.Retire(landblockId);
|
||||
_walkLandscape.RetireLandblock(landblockId);
|
||||
_buildingRegistryRemovalCount++;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -160,43 +160,6 @@ public static class RenderingDiagnostics
|
|||
public static bool ProbeClipRouteEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CLIPROUTE") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW3.2b-2 flip apparatus (2026-08-30): when true and the walk
|
||||
/// registries are wired, RetailPViewRenderer runs the PRODUCTION retail
|
||||
/// frame walk per frame in SHADOW (no draws change) and emits one
|
||||
/// [walk-shadow] line per frame whose cell/building sets diverge from the
|
||||
/// old visibility path — the I5 dual-shadow pattern applied to the FW3
|
||||
/// static cutover. Throwaway apparatus — dies with the flip commit.
|
||||
/// </summary>
|
||||
public static bool ProbeWalkShadowEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_WALK_SHADOW") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// FW3 visual-gate apparatus (2026-08-30, throwaway — dies with the
|
||||
/// stairwell-transition fix): when true, RetailPViewRenderer prints one
|
||||
/// <c>[walk-root]</c> line per frame for the 8 frames after every
|
||||
/// interior/outdoor ROOT flip — viewer cell, resolution, slice count,
|
||||
/// and the walk's visited cell list — pinning which frames draw grass
|
||||
/// over floor openings during a doorway crossing and why.
|
||||
/// </summary>
|
||||
public static bool ProbeWalkRootEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_WALK_ROOT") == "1";
|
||||
|
||||
/// <summary>Companion scratch for <see cref="ProbeWalkRootEnabled"/>
|
||||
/// (throwaway, same investigation): the frame phase relative to the
|
||||
/// interior depth clear ("pre"/"post"), stamped by the pass executor so
|
||||
/// particle/alpha probe lines can tag WHEN a submission or drain ran.
|
||||
/// Written only while the probe is on.</summary>
|
||||
public static string WalkRootPhase = "";
|
||||
|
||||
/// <summary>Companion scratch for <see cref="ProbeWalkRootEnabled"/>
|
||||
/// (throwaway, same investigation): true on the frames (~every 90th)
|
||||
/// where the walk's PView logs its per-portal seen/in-view/clip
|
||||
/// decisions as <c>[walk-portal]</c> lines — pins WHY an exit view
|
||||
/// collapses at a given camera pose. Set by the renderer before
|
||||
/// Collect; read by WalkPView.</summary>
|
||||
public static bool WalkPortalProbeThisFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Bounded-propagation port apparatus (2026-06-08). When true, PortalVisibilityBuilder.Build emits
|
||||
/// one [portal-churn] summary line per call: per-cell pop count (re-pops = churn), total re-enqueues,
|
||||
|
|
|
|||
|
|
@ -321,9 +321,6 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
rangeMultiplier = 1f;
|
||||
|
||||
LastRetailViewEmitterVisitCount = 0;
|
||||
_probeAdmissionDumpThisCall =
|
||||
AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled
|
||||
&& ++_probeAdmissionCallCounter % 120 == 0;
|
||||
foreach (int handle in _worldSimulationHandles)
|
||||
{
|
||||
if (!_byHandle.TryGetValue(handle, out ParticleEmitter? emitter))
|
||||
|
|
@ -349,33 +346,9 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
|| float.IsNaN(maxDistance)
|
||||
|| distance <= maxDistance);
|
||||
RefreshRenderableIndex(emitter, wasRenderable);
|
||||
|
||||
// ACDREAM_PROBE_WALK_ROOT companion (Phase I cathedral
|
||||
// instrumentation, throwaway): admission verdicts for the F418
|
||||
// watch set — the real falls owners 0xCF418000-13 and every
|
||||
// F418-cell emitter — every ~120th ApplyRetailView pass.
|
||||
if (_probeAdmissionDumpThisCall
|
||||
&& AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled
|
||||
&& ((emitter.AttachedObjectId >= 0xCF418000u
|
||||
&& emitter.AttachedObjectId <= 0xCF418014u)
|
||||
|| (emitter.OwnerCellId >> 16) == 0xF418u))
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[falls-adm] owner={emitter.AttachedObjectId:x} "
|
||||
+ $"cell={emitter.OwnerCellId:x8} "
|
||||
+ $"pos=({emitter.OwnerPosition.X:F1},{emitter.OwnerPosition.Y:F1},{emitter.OwnerPosition.Z:F1}) "
|
||||
+ $"dist={distance:F1} max={maxDistance:F1} "
|
||||
+ $"inSet={(visibleCellIds.Contains(emitter.OwnerCellId) ? 1 : 0)} "
|
||||
+ $"eligible={(emitter.ViewEligible ? 1 : 0)} "
|
||||
+ $"active={emitter.ActiveCount} pass={emitter.RenderPass}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase I instrumentation rate limiter (see the probe block above).
|
||||
private static uint _probeAdmissionCallCounter;
|
||||
private static bool _probeAdmissionDumpThisCall;
|
||||
|
||||
/// <summary>
|
||||
/// Changes only render presentation. Logical lifetime is unaffected.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public class Issue130DoorwayStripTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ExitDoorTopEdge_GateCoversTheDrawnApertureWithinPixelTolerance()
|
||||
public void ExitDoorTopEdge_GateCoversLiftedApertureWithinPixelTolerance()
|
||||
{
|
||||
var datDir = CornerFloodReplayTests.ResolveDatDir();
|
||||
if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md."); }
|
||||
|
|
@ -78,15 +78,16 @@ public class Issue130DoorwayStripTests
|
|||
Assert.True(exitIdx >= 0, "0x0170 has no exit portal polygon");
|
||||
|
||||
var localPoly = root.PortalPolygons[exitIdx];
|
||||
// Campaign FW3.3: ShellDrawLiftZ is retired — shells, seal fans, and
|
||||
// the gate all live in the ONE dat space, so the drawn aperture IS
|
||||
// the physics polygon. This test survives as the coverage proof
|
||||
// (gate covers the drawn hole within tolerance across the sweep);
|
||||
// the historical lifted-vs-unlifted strip test's premise (two
|
||||
// spaces) no longer exists and that test is deleted.
|
||||
// DRAWN space: the shell that rasterizes the aperture (and the seal fan)
|
||||
// draws +ShellDrawLiftZ above the physics transform — the gate must be
|
||||
// compared against the drawn hole, not the physics polygon (#130: the
|
||||
// unlifted gate left a 2 cm background strip under the drawn lintel).
|
||||
var worldPoly = new Vector3[localPoly.Length];
|
||||
for (int i = 0; i < localPoly.Length; i++)
|
||||
{
|
||||
worldPoly[i] = Vector3.Transform(localPoly[i], root.WorldTransform);
|
||||
worldPoly[i].Z += PortalVisibilityBuilder.ShellDrawLiftZ;
|
||||
}
|
||||
|
||||
Vector3 centroid = Vector3.Zero;
|
||||
foreach (var w in worldPoly) centroid += w;
|
||||
|
|
@ -145,7 +146,7 @@ public class Issue130DoorwayStripTests
|
|||
aperture[i] = new Vector2(clip[i].X / clip[i].W, clip[i].Y / clip[i].W);
|
||||
|
||||
var pv = PortalVisibilityBuilder.Build(root, eye, Lookup, viewProj,
|
||||
buildingMembership: null);
|
||||
buildingMembership: null, drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv);
|
||||
if (asm.OutsideViewSlices.Length == 0)
|
||||
{
|
||||
|
|
@ -202,6 +203,103 @@ public class Issue130DoorwayStripTests
|
|||
$"plane gate under-covers the aperture top edge by {worstPlaneGapPx:F2}px @ {worstDesc}"));
|
||||
}
|
||||
|
||||
/// <summary>Sensitivity proof + regression documentation: a gate built in
|
||||
/// PHYSICS space (drawLiftZ 0) against the DRAWN (lifted) aperture shows a
|
||||
/// multi-pixel strip at a close doorway — the user-visible #130 strip
|
||||
/// (f35cb8b split the lift out of the visibility transform; the OutsideView
|
||||
/// kept gating drawn color in unlifted space). If this stops failing-by-gap,
|
||||
/// the lift is gone and the production drawLiftZ plumbing can go too.</summary>
|
||||
[Fact]
|
||||
public void UnliftedGate_LeavesTheStripAtTheDrawnTopEdge()
|
||||
{
|
||||
var datDir = CornerFloodReplayTests.ResolveDatDir();
|
||||
if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md."); }
|
||||
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
var cells = CornerFloodReplayTests.LoadBuilding(dats);
|
||||
var root = cells[ExitCellId];
|
||||
LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out var c) ? c : null;
|
||||
|
||||
int exitIdx = -1;
|
||||
for (int i = 0; i < root.Portals.Count; i++)
|
||||
{
|
||||
if (root.Portals[i].OtherCellId == 0xFFFF && i < root.PortalPolygons.Count
|
||||
&& root.PortalPolygons[i].Length >= 3)
|
||||
{ exitIdx = i; break; }
|
||||
}
|
||||
Assert.True(exitIdx >= 0);
|
||||
|
||||
var localPoly = root.PortalPolygons[exitIdx];
|
||||
var worldPoly = new Vector3[localPoly.Length];
|
||||
Vector3 centroid = Vector3.Zero;
|
||||
for (int i = 0; i < localPoly.Length; i++)
|
||||
{
|
||||
worldPoly[i] = Vector3.Transform(localPoly[i], root.WorldTransform);
|
||||
worldPoly[i].Z += PortalVisibilityBuilder.ShellDrawLiftZ; // drawn space
|
||||
centroid += worldPoly[i];
|
||||
}
|
||||
centroid /= worldPoly.Length;
|
||||
|
||||
var plane = root.ClipPlanes[exitIdx];
|
||||
var worldNormal = Vector3.TransformNormal(plane.Normal, root.WorldTransform);
|
||||
var cellCenterWorld = Vector3.Transform(
|
||||
(root.LocalBoundsMin + root.LocalBoundsMax) * 0.5f, root.WorldTransform);
|
||||
if (Vector3.Dot(worldNormal, cellCenterWorld - centroid) < 0)
|
||||
worldNormal = -worldNormal;
|
||||
worldNormal = Vector3.Normalize(worldNormal);
|
||||
|
||||
// d=2.4 m, eye low (0.9 m above the opening's base), gaze at the
|
||||
// centroid — the main sweep's clean case, where the aperture top edge
|
||||
// projects ON SCREEN (y≈0.79; a closer/higher eye pushes the lintel
|
||||
// past the screen top and the seam becomes unmeasurable).
|
||||
var eye = centroid + worldNormal * 2.4f;
|
||||
eye.Z = centroid.Z - 1.0f + 0.9f;
|
||||
var viewProj = ViewProjFor(eye, centroid);
|
||||
|
||||
var clip = new Vector4[worldPoly.Length];
|
||||
for (int i = 0; i < worldPoly.Length; i++)
|
||||
clip[i] = Vector4.Transform(new Vector4(worldPoly[i], 1f), viewProj);
|
||||
var aperture = new Vector2[clip.Length];
|
||||
for (int i = 0; i < clip.Length; i++)
|
||||
aperture[i] = new Vector2(clip[i].X / clip[i].W, clip[i].Y / clip[i].W);
|
||||
|
||||
var pvUnlifted = PortalVisibilityBuilder.Build(root, eye, Lookup, viewProj); // drawLiftZ 0
|
||||
var asmUnlifted = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pvUnlifted);
|
||||
Assert.True(asmUnlifted.OutsideViewSlices.Length > 0);
|
||||
(float unliftedGapPx, _, _) = MeasureTopEdgeGap(aperture, asmUnlifted.OutsideViewSlices, 1920, 1080);
|
||||
|
||||
var pvLifted = PortalVisibilityBuilder.Build(root, eye, Lookup, viewProj,
|
||||
buildingMembership: null, drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var asmLifted = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pvLifted);
|
||||
Assert.True(asmLifted.OutsideViewSlices.Length > 0);
|
||||
(float liftedGapPx, _, _) = MeasureTopEdgeGap(aperture, asmLifted.OutsideViewSlices, 1920, 1080);
|
||||
|
||||
_out.WriteLine(FormattableString.Invariant(
|
||||
$"top-edge gap vs the DRAWN aperture at d=2.4 m: unliftedGate={unliftedGapPx:F2}px liftedGate={liftedGapPx:F2}px"));
|
||||
var dbg = new System.Text.StringBuilder(" aperture(LIFTED):");
|
||||
foreach (var v in aperture) dbg.Append(FormattableString.Invariant($" ({v.X:F4},{v.Y:F4})"));
|
||||
_out.WriteLine(dbg.ToString());
|
||||
foreach (var poly in pvUnlifted.OutsideView.Polygons)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder(" unliftedGatePoly:");
|
||||
foreach (var v in poly.Vertices) sb.Append(FormattableString.Invariant($" ({v.X:F4},{v.Y:F4})"));
|
||||
_out.WriteLine(sb.ToString());
|
||||
}
|
||||
foreach (var poly in pvLifted.OutsideView.Polygons)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder(" liftedGatePoly:");
|
||||
foreach (var v in poly.Vertices) sb.Append(FormattableString.Invariant($" ({v.X:F4},{v.Y:F4})"));
|
||||
_out.WriteLine(sb.ToString());
|
||||
}
|
||||
|
||||
// The strip the user saw: physics-space gate vs drawn hole, several px.
|
||||
Assert.True(unliftedGapPx > 2.0f, FormattableString.Invariant(
|
||||
$"expected the unlifted gate to show the strip (>2px), got {unliftedGapPx:F2}px"));
|
||||
// The fix: a gate in drawn space covers the drawn hole.
|
||||
Assert.True(liftedGapPx <= 1.2f, FormattableString.Invariant(
|
||||
$"lifted gate still under-covers by {liftedGapPx:F2}px"));
|
||||
}
|
||||
|
||||
private static string DescribePolys(CellView view)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ public class Issue131SetupProbeTests
|
|||
var root = cells[0xA9B40171u];
|
||||
var pv = AcDream.App.Rendering.PortalVisibilityBuilder.Build(
|
||||
root, eye, Lookup, viewProj,
|
||||
buildingMembership: null);
|
||||
buildingMembership: null,
|
||||
drawLiftZ: AcDream.App.Rendering.PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
_out.WriteLine(FormattableString.Invariant(
|
||||
$"main flood={pv.OrderedVisibleCells.Count} outPolys={pv.OutsideView.Polygons.Count}"));
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace AcDream.App.Tests.Rendering;
|
|||
/// where its geometry was; stair cells failing admission = the pop).
|
||||
///
|
||||
/// Production-matched inputs: Build(root, eye, lookup, viewProj,
|
||||
/// buildingMembership: null) — the production call shape (FW3.3 retired drawLiftZ)
|
||||
/// buildingMembership: null, drawLiftZ: ShellDrawLiftZ) — the drawLiftZ
|
||||
/// mirrors RetailPViewRenderer.DrawInside.
|
||||
///
|
||||
/// Scenarios:
|
||||
|
|
@ -54,7 +54,8 @@ public class Issue176177FacilityHubFloodReplayTests
|
|||
Func<uint, LoadedCell?> lookup = id => cells.TryGetValue(id, out var c) ? c : null;
|
||||
var frame = PortalVisibilityBuilder.Build(
|
||||
cells[rootId], eye, lookup, ViewProjFor(eye, gazeDir),
|
||||
buildingMembership: null);
|
||||
buildingMembership: null,
|
||||
drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var result = new List<uint>(frame.OrderedVisibleCells);
|
||||
result.Sort();
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ public class Issue177StairDescentCameraFloodTests
|
|||
string admit = "?";
|
||||
if (viewer != 0u && renderCells.TryGetValue(viewer, out var rc))
|
||||
{
|
||||
var f = PortalVisibilityBuilder.Build(rc, eye, lookup, cam.View * cam.Projection, null);
|
||||
var f = PortalVisibilityBuilder.Build(rc, eye, lookup, cam.View * cam.Projection, null, PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var s = f.OrderedVisibleCells;
|
||||
admit = string.Concat(stair.Select(low => (s.Contains(FacilityHub | low) ? "1" : "0")));
|
||||
}
|
||||
|
|
@ -259,7 +259,7 @@ public class Issue177StairDescentCameraFloodTests
|
|||
for (int i = 0; i < 140; i++) cam.Update(playerPos, yaw, Vector3.Zero, true, Vector3.UnitZ, 1f / 60f, pcell, 0x5000000A);
|
||||
uint viewer = cam.ViewerCellId;
|
||||
if (viewer == 0u || !renderCells.TryGetValue(viewer, out var rc)) continue;
|
||||
var f = PortalVisibilityBuilder.Build(rc, cam.Position, lookup, cam.View * cam.Projection, null);
|
||||
var f = PortalVisibilityBuilder.Build(rc, cam.Position, lookup, cam.View * cam.Projection, null, PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
int c = f.OrderedVisibleCells.Count; sum += c; n++;
|
||||
if (c > maxCells) { maxCells = c; bestViewer = viewer; bestSet = CellSet(f.OrderedVisibleCells.OrderBy(v => v)); }
|
||||
}
|
||||
|
|
@ -326,7 +326,7 @@ public class Issue177StairDescentCameraFloodTests
|
|||
float d = PortalPlaneDistance(root, upIdx, eye);
|
||||
var frame = PortalVisibilityBuilder.Build(
|
||||
root, eye, lookup, ViewProj(eye, gaze),
|
||||
buildingMembership: null);
|
||||
buildingMembership: null, drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var vis = frame.OrderedVisibleCells.OrderBy(v => v).ToList();
|
||||
bool up = vis.Contains(upperId);
|
||||
string admit = up ? "ADMITTED" : "DROPPED";
|
||||
|
|
@ -339,8 +339,8 @@ public class Issue177StairDescentCameraFloodTests
|
|||
// spiral, root 0x01C8, portals off-screen/sliver, camera NOT collision-jammed).
|
||||
// Kept as a mechanism pin: an eye exactly in the ceiling-portal plane drops the upper
|
||||
// cell (edge-on → <3 clip), an eye 0.4 m off admits it via the normal clip.
|
||||
var fEdge = PortalVisibilityBuilder.Build(root, eyeEdgeOn, lookup, ViewProj(eyeEdgeOn, gaze), null);
|
||||
var fOff = PortalVisibilityBuilder.Build(root, eyeOffPlane, lookup, ViewProj(eyeOffPlane, gaze), null);
|
||||
var fEdge = PortalVisibilityBuilder.Build(root, eyeEdgeOn, lookup, ViewProj(eyeEdgeOn, gaze), null, PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var fOff = PortalVisibilityBuilder.Build(root, eyeOffPlane, lookup, ViewProj(eyeOffPlane, gaze), null, PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
Assert.DoesNotContain(upperId, fEdge.OrderedVisibleCells); // edge-on drops (mechanism)
|
||||
Assert.Contains(upperId, fOff.OrderedVisibleCells); // off-plane admits (control)
|
||||
}
|
||||
|
|
@ -438,7 +438,7 @@ public class Issue177StairDescentCameraFloodTests
|
|||
var vp = cam.View * cam.Projection;
|
||||
if (viewer != 0u && renderCells.TryGetValue(viewer, out var rootCell))
|
||||
{
|
||||
var f = PortalVisibilityBuilder.Build(rootCell, eye, lookup, vp, null);
|
||||
var f = PortalVisibilityBuilder.Build(rootCell, eye, lookup, vp, null, PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var vis = f.OrderedVisibleCells.OrderBy(v => v).ToList();
|
||||
string flags = $"0178={(vis.Contains(FacilityHub | 0x0178u) ? "Y" : "-")} 0182={(vis.Contains(FacilityHub | 0x0182u) ? "Y" : "-")} 0183={(vis.Contains(FacilityHub | 0x0183u) ? "Y" : "-")} 0181={(vis.Contains(FacilityHub | 0x0181u) ? "Y" : "-")}";
|
||||
_out.WriteLine(FormattableString.Invariant(
|
||||
|
|
@ -519,7 +519,7 @@ public class Issue177StairDescentCameraFloodTests
|
|||
{
|
||||
var frame = PortalVisibilityBuilder.Build(
|
||||
rootCell, eye, lookup, viewProj,
|
||||
buildingMembership: null);
|
||||
buildingMembership: null, drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var vis = frame.OrderedVisibleCells.OrderBy(v => v).ToList();
|
||||
floodStr = $"{vis.Count,2} [{CellSet(vis)}]";
|
||||
bool ramp = vis.Contains(FacilityHub | 0x0182u);
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ public class Issue181VisFlapReplayTests
|
|||
Func<uint, LoadedCell?> lookup = id => cells.TryGetValue(id, out var c) ? c : null;
|
||||
var frame = PortalVisibilityBuilder.Build(
|
||||
cells[rootId], eye, lookup, ViewProjFor(eye, gazeDir),
|
||||
buildingMembership: null);
|
||||
buildingMembership: null,
|
||||
drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
var result = new List<uint>(frame.OrderedVisibleCells);
|
||||
result.Sort();
|
||||
return result;
|
||||
|
|
@ -87,7 +88,8 @@ public class Issue181VisFlapReplayTests
|
|||
{
|
||||
var frame = PortalVisibilityBuilder.Build(
|
||||
cells[LiveRoot], eye, lookup, ViewProjFor(eye, gaze),
|
||||
buildingMembership: null);
|
||||
buildingMembership: null,
|
||||
drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
|
||||
|
||||
if (!frame.CellViews.TryGetValue(flapper, out var view) || view.IsEmpty)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,33 +14,326 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
private static readonly RenderSceneGeneration Generation =
|
||||
RenderSceneGeneration.FromRaw(1);
|
||||
|
||||
// Campaign FW3.2b-2 (adjudicated 2026-08-30): Builder_PreservesPViewRoutesAndExcludesShellsAndWithdrawnOwners,
|
||||
// Builder_OrdersMultiSliceBuildingShellsAfterLookIns, and
|
||||
// Builder_InterleavesEachLookInWithItsOwnPackedBuildingShellRoutes are
|
||||
// DELETED — all three asserted route-range interleaving for
|
||||
// LandscapeOutdoorStatic/LandscapeBuildingShell/CellStatic, which
|
||||
// RenderScenePViewFrameBuilder no longer emits at all (WalkFrameDriver
|
||||
// draws every outdoor static, building shell, and cell static — look-in
|
||||
// included — directly through OrderedDrawStream; plan §FW3
|
||||
// "FW3.2b-2 — the production rooting", item 3). There is no successor
|
||||
// assertion to write: the routes themselves are retired, not relocated.
|
||||
[Fact]
|
||||
public void Builder_PreservesPViewRoutesAndExcludesShellsAndWithdrawnOwners()
|
||||
{
|
||||
using var scene = new ArchRenderScene(Generation);
|
||||
RenderProjectionRecord outdoor = Record(
|
||||
0x0100_0000_0000_0001,
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
RenderProjectionRecord buildingShell = Record(
|
||||
0x0100_0000_0000_0008,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
isBuildingShell: true);
|
||||
RenderProjectionRecord hidden = Record(
|
||||
0x0100_0000_0000_0002,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
flags: RenderProjectionFlags.Hidden
|
||||
| RenderProjectionFlags.SpatiallyResident);
|
||||
RenderProjectionRecord withdrawn = Record(
|
||||
0x0100_0000_0000_0003,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
flags: RenderProjectionFlags.Draw);
|
||||
RenderProjectionRecord shell = Record(
|
||||
0x0200_0000_0000_0004,
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
parentCell: Cell);
|
||||
RenderProjectionRecord cellStatic = Record(
|
||||
0x0100_0000_0000_0005,
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
parentCell: Cell);
|
||||
RenderProjectionRecord outdoorDynamic = Record(
|
||||
0x0300_0000_0000_0006,
|
||||
RenderProjectionClass.LiveDynamicRoot);
|
||||
RenderProjectionRecord cellDynamic = Record(
|
||||
0x0300_0000_0000_0007,
|
||||
RenderProjectionClass.LiveDynamicRoot,
|
||||
parentCell: Cell);
|
||||
RenderProjectionRecord[] records =
|
||||
[
|
||||
outdoor,
|
||||
buildingShell,
|
||||
hidden,
|
||||
withdrawn,
|
||||
shell,
|
||||
cellStatic,
|
||||
outdoorDynamic,
|
||||
cellDynamic,
|
||||
];
|
||||
var deltas = new RenderProjectionDelta[records.Length];
|
||||
for (int index = 0; index < records.Length; index++)
|
||||
{
|
||||
deltas[index] = RenderProjectionDelta.Register(
|
||||
Generation,
|
||||
(ulong)index + 1,
|
||||
records[index]);
|
||||
}
|
||||
scene.Apply(deltas);
|
||||
|
||||
PortalVisibilityFrame portal = Portal(Cell);
|
||||
ClipFrameAssembly clip = FullScreenClip(Cell);
|
||||
ViewconeCuller viewcone =
|
||||
ViewconeCuller.Build(clip, Matrix4x4.Identity);
|
||||
var exchange = new RenderFrameExchange();
|
||||
var builder = new RenderScenePViewFrameBuilder();
|
||||
RenderSceneDigest digest =
|
||||
scene.BuildDigest(new RenderSceneDigestBuffer());
|
||||
var input = new RenderScenePViewBuildInput(
|
||||
scene.OpenQuery(),
|
||||
digest,
|
||||
portal,
|
||||
clip,
|
||||
viewcone,
|
||||
[],
|
||||
[Cell],
|
||||
EmptyCellSource.Instance,
|
||||
[],
|
||||
RootIsOutdoor: true);
|
||||
|
||||
builder.Build(exchange, frameSequence: 1, in input);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
try
|
||||
{
|
||||
Assert.Equal(
|
||||
[outdoor.Id, hidden.Id, buildingShell.Id],
|
||||
view.OutdoorStaticCandidates.ToArray()
|
||||
.Select(static item => item.Id));
|
||||
Assert.Equal(
|
||||
[cellStatic.Id],
|
||||
view.CellStaticCandidates.ToArray()
|
||||
.Select(static item => item.Id));
|
||||
Assert.Equal(
|
||||
[outdoorDynamic.Id, cellDynamic.Id],
|
||||
view.DynamicCandidates.ToArray()
|
||||
.Select(static item => item.Id));
|
||||
Assert.Equal(6, view.Transforms.Length);
|
||||
Assert.Equal(6, view.RouteCandidates.Length);
|
||||
Assert.Equal(3, view.RouteRanges.Length);
|
||||
RenderFrameCandidateRange outdoorRange = Assert.Single(
|
||||
view.RouteRanges.ToArray(),
|
||||
range => range.Route
|
||||
== RenderFrameCandidateRoute.LandscapeOutdoorStatic);
|
||||
Assert.Equal(3, outdoorRange.Count);
|
||||
Assert.Contains(
|
||||
view.RouteCandidates.Slice(
|
||||
outdoorRange.Offset,
|
||||
outdoorRange.Count).ToArray(),
|
||||
item => item.Id == buildingShell.Id);
|
||||
Assert.DoesNotContain(
|
||||
view.RouteRanges.ToArray(),
|
||||
range => range.Route
|
||||
== RenderFrameCandidateRoute.LandscapeBuildingShell);
|
||||
Assert.DoesNotContain(
|
||||
view.RouteCandidates.ToArray(),
|
||||
item => item.Id == withdrawn.Id || item.Id == shell.Id);
|
||||
Assert.Equal(
|
||||
new RenderFrameDiagnosticCounts(
|
||||
OutdoorStaticCandidates: 3,
|
||||
CellStaticCandidates: 1,
|
||||
DynamicCandidates: 2,
|
||||
TransformCount: 6,
|
||||
OpaqueClassificationCount: 0,
|
||||
AlphaClassificationCount: 0,
|
||||
LightSetCount: 0,
|
||||
SelectionPartCount: 0,
|
||||
RouteCandidateCount: 6,
|
||||
EntityCandidateCount: 6,
|
||||
MeshPartCount: 6),
|
||||
view.DiagnosticCounts);
|
||||
Assert.Equal(6, view.EntityCandidates.Length);
|
||||
Assert.Equal(6, view.MeshParts.Length);
|
||||
Assert.Same(portal, view.PortalFrame);
|
||||
Assert.Same(clip, view.ClipAssembly);
|
||||
Assert.Equal(digest, view.SourceDigest);
|
||||
}
|
||||
finally
|
||||
{
|
||||
exchange.Release(in view);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_OrdersMultiSliceBuildingShellsAfterLookIns()
|
||||
{
|
||||
using var scene = new ArchRenderScene(Generation);
|
||||
RenderProjectionRecord outdoor = Record(
|
||||
0x0100_0000_0000_0101,
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
RenderProjectionRecord buildingShell = Record(
|
||||
0x0100_0000_0000_0102,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
isBuildingShell: true,
|
||||
buildingShellAnchorCellId: Cell);
|
||||
RenderProjectionRecord lookInObject = Record(
|
||||
0x0100_0000_0000_0103,
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
parentCell: Cell);
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Register(Generation, 1, outdoor),
|
||||
RenderProjectionDelta.Register(Generation, 2, buildingShell),
|
||||
RenderProjectionDelta.Register(Generation, 3, lookInObject),
|
||||
]);
|
||||
|
||||
PortalVisibilityFrame portal = Portal(Cell);
|
||||
PortalVisibilityFrame lookIn = Portal(Cell);
|
||||
lookIn.SourceBuildingKey = 2u;
|
||||
lookIn.SourceBuildingLandblockId = Cell & 0xFFFF0000u;
|
||||
var anchorCell = new LoadedCell
|
||||
{
|
||||
CellId = Cell,
|
||||
BuildingId = 2u,
|
||||
};
|
||||
ClipFrameAssembly clip = TwoSliceClip(Cell);
|
||||
clip.LookInCellToViewSlices[new LookInClipCell(0, Cell)] =
|
||||
[clip.CellIdToViewSlices[Cell][0]];
|
||||
ViewconeCuller viewcone =
|
||||
ViewconeCuller.Build(clip, Matrix4x4.Identity);
|
||||
var exchange = new RenderFrameExchange();
|
||||
var builder = new RenderScenePViewFrameBuilder();
|
||||
RenderSceneDigest digest =
|
||||
scene.BuildDigest(new RenderSceneDigestBuffer());
|
||||
var input = new RenderScenePViewBuildInput(
|
||||
scene.OpenQuery(),
|
||||
digest,
|
||||
portal,
|
||||
clip,
|
||||
viewcone,
|
||||
[lookIn],
|
||||
[Cell],
|
||||
new DictionaryCellSource(anchorCell),
|
||||
[],
|
||||
RootIsOutdoor: true);
|
||||
|
||||
builder.Build(exchange, frameSequence: 1, in input);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
try
|
||||
{
|
||||
Assert.Equal(
|
||||
[
|
||||
(RenderFrameCandidateRoute.LandscapeOutdoorStatic, 0, 0u),
|
||||
(RenderFrameCandidateRoute.LandscapeOutdoorStatic, 1, 0u),
|
||||
(RenderFrameCandidateRoute.LookInObject, 0, Cell),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 0, 0u),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 1, 0u),
|
||||
(RenderFrameCandidateRoute.CellStatic, 0, 0u),
|
||||
],
|
||||
view.RouteRanges.ToArray().Select(static range =>
|
||||
(range.Route, range.RouteIndex, range.CellId)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
exchange.Release(in view);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_InterleavesEachLookInWithItsOwnPackedBuildingShellRoutes()
|
||||
{
|
||||
const uint secondCell = 0xA9B40180u;
|
||||
using var scene = new ArchRenderScene(Generation);
|
||||
RenderProjectionRecord firstShell = Record(
|
||||
0x0100_0000_0000_0201,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
isBuildingShell: true,
|
||||
buildingShellAnchorCellId: Cell);
|
||||
RenderProjectionRecord secondShell = Record(
|
||||
0x0100_0000_0000_0202,
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
isBuildingShell: true,
|
||||
buildingShellAnchorCellId: secondCell);
|
||||
RenderProjectionRecord firstObject = Record(
|
||||
0x0100_0000_0000_0203,
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
parentCell: Cell);
|
||||
RenderProjectionRecord secondObject = Record(
|
||||
0x0100_0000_0000_0204,
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
parentCell: secondCell);
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Register(Generation, 1, firstShell),
|
||||
RenderProjectionDelta.Register(Generation, 2, secondShell),
|
||||
RenderProjectionDelta.Register(Generation, 3, firstObject),
|
||||
RenderProjectionDelta.Register(Generation, 4, secondObject),
|
||||
]);
|
||||
|
||||
PortalVisibilityFrame portal = Portal(Cell);
|
||||
PortalVisibilityFrame firstLookIn = Portal(Cell);
|
||||
firstLookIn.SourceBuildingKey = 2u;
|
||||
firstLookIn.SourceBuildingLandblockId = Cell & 0xFFFF0000u;
|
||||
PortalVisibilityFrame secondLookIn = Portal(secondCell);
|
||||
secondLookIn.SourceBuildingKey = 3u;
|
||||
secondLookIn.SourceBuildingLandblockId = secondCell & 0xFFFF0000u;
|
||||
var firstAnchor = new LoadedCell
|
||||
{
|
||||
CellId = Cell,
|
||||
BuildingId = 2u,
|
||||
};
|
||||
var secondAnchor = new LoadedCell
|
||||
{
|
||||
CellId = secondCell,
|
||||
BuildingId = 3u,
|
||||
};
|
||||
ClipFrameAssembly clip = TwoSliceClip(Cell);
|
||||
clip.LookInCellToViewSlices[new LookInClipCell(0, Cell)] =
|
||||
[clip.CellIdToViewSlices[Cell][0]];
|
||||
clip.LookInCellToViewSlices[new LookInClipCell(1, secondCell)] =
|
||||
[clip.CellIdToViewSlices[Cell][0]];
|
||||
ViewconeCuller viewcone =
|
||||
ViewconeCuller.Build(clip, Matrix4x4.Identity);
|
||||
var exchange = new RenderFrameExchange();
|
||||
var builder = new RenderScenePViewFrameBuilder();
|
||||
RenderSceneDigest digest =
|
||||
scene.BuildDigest(new RenderSceneDigestBuffer());
|
||||
var input = new RenderScenePViewBuildInput(
|
||||
scene.OpenQuery(),
|
||||
digest,
|
||||
portal,
|
||||
clip,
|
||||
viewcone,
|
||||
[firstLookIn, secondLookIn],
|
||||
[Cell],
|
||||
new DictionaryCellSource(firstAnchor, secondAnchor),
|
||||
[],
|
||||
RootIsOutdoor: true);
|
||||
|
||||
builder.Build(exchange, frameSequence: 1, in input);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
try
|
||||
{
|
||||
Assert.Equal(
|
||||
[
|
||||
(RenderFrameCandidateRoute.LookInObject, 0, Cell),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 0, 0u),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 1, 0u),
|
||||
(RenderFrameCandidateRoute.LookInObject, 1, secondCell),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 2, 0u),
|
||||
(RenderFrameCandidateRoute.LandscapeBuildingShell, 3, 0u),
|
||||
],
|
||||
view.RouteRanges.ToArray()
|
||||
.Where(static range =>
|
||||
range.Route is RenderFrameCandidateRoute.LookInObject
|
||||
or RenderFrameCandidateRoute.LandscapeBuildingShell)
|
||||
.Select(static range =>
|
||||
(range.Route, range.RouteIndex, range.CellId)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
exchange.Release(in view);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_ReusesOrderedIndicesWhileRefreshingDirtyRecords()
|
||||
{
|
||||
// Campaign FW3.2b-2 (adjudicated 2026-08-30): re-expressed against
|
||||
// the DYNAMIC index (LiveDynamicRoot / DynamicCandidates) instead of
|
||||
// the now-dead OutdoorStatic index — LoadSceneIndices' dirty-record
|
||||
// refresh + sort-key reorder mechanism this test proves is SHARED
|
||||
// between the two indices; the OutdoorStatic side was deleted along
|
||||
// with BuildOutdoorRoutes (its only reader).
|
||||
using var scene = new ArchRenderScene(Generation);
|
||||
RenderProjectionRecord later = Record(
|
||||
0x0100_0000_0000_0012,
|
||||
RenderProjectionClass.LiveDynamicRoot);
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
RenderProjectionRecord earlier = Record(
|
||||
0x0100_0000_0000_0011,
|
||||
RenderProjectionClass.LiveDynamicRoot);
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Register(Generation, 1, later),
|
||||
|
|
@ -60,7 +353,7 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
{
|
||||
Assert.Equal(
|
||||
[earlier.Id, later.Id],
|
||||
first.DynamicCandidates.ToArray()
|
||||
first.OutdoorStaticCandidates.ToArray()
|
||||
.Select(static record => record.Id));
|
||||
}
|
||||
finally
|
||||
|
|
@ -108,7 +401,7 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
projected.Transform.LocalToWorld);
|
||||
Assert.Equal(
|
||||
[earlier.Id, later.Id],
|
||||
second.DynamicCandidates.ToArray()
|
||||
second.OutdoorStaticCandidates.ToArray()
|
||||
.Select(static record => record.Id));
|
||||
}
|
||||
finally
|
||||
|
|
@ -138,7 +431,7 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
{
|
||||
Assert.Equal(
|
||||
[later.Id, earlier.Id],
|
||||
third.DynamicCandidates.ToArray()
|
||||
third.OutdoorStaticCandidates.ToArray()
|
||||
.Select(static record => record.Id));
|
||||
}
|
||||
finally
|
||||
|
|
@ -162,8 +455,7 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
[Cell],
|
||||
EmptyCellSource.Instance,
|
||||
[],
|
||||
RootIsOutdoor: true,
|
||||
RootFloodCells: new HashSet<uint> { Cell });
|
||||
RootIsOutdoor: true);
|
||||
builder.Build(exchange, frameSequence, in input);
|
||||
}
|
||||
}
|
||||
|
|
@ -171,18 +463,14 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
[Fact]
|
||||
public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate()
|
||||
{
|
||||
// Campaign FW3.2b-2 (adjudicated 2026-08-30): no static entities here
|
||||
// — LandscapeOutdoorStatic/CellStatic no longer flow through the
|
||||
// packed product at all (WalkFrameDriver owns every static now), so
|
||||
// a static observed on the LEGACY oracle side with nothing on the
|
||||
// packed side would report a permanent mismatch unrelated to what
|
||||
// this test is proving (withdrawal detection on the SURVIVING
|
||||
// DynamicLast route).
|
||||
WorldEntity outdoor = Entity(10, serverGuid: 0, parentCell: null);
|
||||
WorldEntity cellStatic = Entity(11, serverGuid: 0, parentCell: Cell);
|
||||
WorldEntity outdoorDynamic =
|
||||
Entity(1_000_012, 0x8000_0012, parentCell: null);
|
||||
WorldEntity cellDynamic =
|
||||
Entity(1_000_013, 0x8000_0013, parentCell: Cell);
|
||||
WorldEntity[] entities = [outdoorDynamic, cellDynamic];
|
||||
WorldEntity[] entities =
|
||||
[outdoor, cellStatic, outdoorDynamic, cellDynamic];
|
||||
var oracle = new CurrentRenderSceneOracle();
|
||||
var partition = new InteriorEntityPartition.Result();
|
||||
InteriorEntityPartition.Partition(
|
||||
|
|
@ -191,6 +479,16 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
[Entry(entities)],
|
||||
oracle);
|
||||
oracle.BeginPViewFrame();
|
||||
oracle.ObservePViewBucket(
|
||||
CurrentRenderPViewRoute.LandscapeOutdoorStatic,
|
||||
0,
|
||||
0,
|
||||
[outdoor]);
|
||||
oracle.ObservePViewBucket(
|
||||
CurrentRenderPViewRoute.CellStatic,
|
||||
0,
|
||||
0,
|
||||
[cellStatic]);
|
||||
oracle.ObservePViewBucket(
|
||||
CurrentRenderPViewRoute.DynamicLast,
|
||||
0,
|
||||
|
|
@ -198,6 +496,14 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
[outdoorDynamic, cellDynamic]);
|
||||
oracle.CompletePViewFrame();
|
||||
oracle.BeginDispatcherFrame();
|
||||
oracle.ObserveDispatcherDraw(
|
||||
WbDrawDispatcher.EntitySet.All,
|
||||
entitiesWalked: 1,
|
||||
[(outdoor, 0, Landblock)]);
|
||||
oracle.ObserveDispatcherDraw(
|
||||
WbDrawDispatcher.EntitySet.All,
|
||||
entitiesWalked: 1,
|
||||
[(cellStatic, 0, Landblock)]);
|
||||
oracle.ObserveDispatcherDraw(
|
||||
WbDrawDispatcher.EntitySet.All,
|
||||
entitiesWalked: 2,
|
||||
|
|
@ -238,15 +544,15 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
Assert.True(matched.Enabled);
|
||||
Assert.Equal(1uL, matched.ComparisonCount);
|
||||
Assert.Equal(1uL, matched.SuccessfulComparisonCount);
|
||||
Assert.Equal(2, matched.ExpectedCandidateCount);
|
||||
Assert.Equal(2, matched.ActualCandidateCount);
|
||||
Assert.Equal(4, matched.ExpectedCandidateCount);
|
||||
Assert.Equal(4, matched.ActualCandidateCount);
|
||||
Assert.Equal(0, matched.MismatchCount);
|
||||
Assert.Null(matched.FirstMismatch);
|
||||
Assert.Equal(matched.ExpectedDigest, matched.ActualDigest);
|
||||
Assert.Equal(1uL, matched.PackedInputComparisonCount);
|
||||
Assert.Equal(1uL, matched.PackedInputSuccessfulComparisonCount);
|
||||
Assert.Equal(2, matched.PackedInputExpectedCount);
|
||||
Assert.Equal(2, matched.PackedInputActualCount);
|
||||
Assert.Equal(4, matched.PackedInputExpectedCount);
|
||||
Assert.Equal(4, matched.PackedInputActualCount);
|
||||
Assert.Equal(0, matched.PackedInputMismatchCount);
|
||||
Assert.Null(matched.PackedInputFirstMismatch);
|
||||
Assert.Equal(
|
||||
|
|
@ -275,10 +581,10 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
Assert.Equal(2uL, mismatch.ComparisonCount);
|
||||
Assert.Equal(1uL, mismatch.SuccessfulComparisonCount);
|
||||
Assert.Equal(1, mismatch.MismatchCount);
|
||||
Assert.Contains("field=count expected=2 actual=1", mismatch.FirstMismatch);
|
||||
Assert.Contains("field=count expected=4 actual=3", mismatch.FirstMismatch);
|
||||
Assert.Equal(1, mismatch.PackedInputMismatchCount);
|
||||
Assert.Contains(
|
||||
"field=count expected=2 actual=1",
|
||||
"field=count expected=4 actual=3",
|
||||
mismatch.PackedInputFirstMismatch);
|
||||
Assert.Equal(2, logs.Count);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -876,15 +876,9 @@ public class RetailChaseCameraTests
|
|||
isOnGround: true, contactPlaneNormal: Vector3.UnitZ, dt: 1f / 60f,
|
||||
cellId: 0x100, selfEntityId: 0x5);
|
||||
|
||||
Step(); // frame 1: total fallback — viewer snaps to the player
|
||||
Step(); // frame 1: total fallback — viewer snaps to the player, cell 0
|
||||
Assert.Equal(Vector3.Zero, cam.Position);
|
||||
// FW3 visual-gate correction: retail's set_viewer(&player->
|
||||
// m_position, 1) copies the WHOLE Position — objcell_id included
|
||||
// — so the fallback viewer inherits the PLAYER'S cell (0x100
|
||||
// here), never 0. Rooting those frames on 0 made the walk treat
|
||||
// a doorway-graze frame as outdoor (grass over the stairwell —
|
||||
// the #119 family's transition arm).
|
||||
Assert.Equal(0x100u, cam.ViewerCellId);
|
||||
Assert.Equal(0u, cam.ViewerCellId);
|
||||
|
||||
Step(); // frame 2: the sweep target re-extends FROM the player
|
||||
float reach = Vector3.Distance(probe.Requests[1], Vector3.Zero);
|
||||
|
|
|
|||
|
|
@ -24,12 +24,8 @@ public sealed class RetailPViewPassExecutorTests
|
|||
"begin",
|
||||
"assemble",
|
||||
"append-look-in-clips",
|
||||
// FW4 slice 1: the clip-region publication moved below the
|
||||
// walk block (a walk-rooted interior frame re-derives the
|
||||
// outside-view slices from the walk's own views first, and
|
||||
// the appended slots must join the same single publication).
|
||||
"indoor-routing",
|
||||
"prepare-clip:3",
|
||||
"indoor-routing",
|
||||
"prepare-cells",
|
||||
"diagnostics",
|
||||
"terrain-clip",
|
||||
|
|
@ -200,15 +196,10 @@ public sealed class RetailPViewPassExecutorTests
|
|||
[Fact]
|
||||
public void DrawInside_production_product_supplies_the_ordered_entity_routes()
|
||||
{
|
||||
// Campaign FW3.2b-2: no cell-static entity here (adjudicated
|
||||
// 2026-08-30) — CellStatic no longer flows through the packed
|
||||
// product at all (WalkFrameDriver owns every cell static now, via
|
||||
// OrderedDrawStream, not this route); a static entity registered on
|
||||
// the LEGACY side with nothing on the packed side would report a
|
||||
// permanent MismatchCount, which is not what this test is proving.
|
||||
// The route-consumption-order assertion below still exercises the
|
||||
// one route that DID survive the cutover (DynamicLast).
|
||||
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
||||
WorldEntity cellStatic = Entity(
|
||||
30u,
|
||||
parentCellId: interior.CellId);
|
||||
WorldEntity dynamic = Entity(
|
||||
31u,
|
||||
serverGuid: 0x80000031u,
|
||||
|
|
@ -217,6 +208,17 @@ public sealed class RetailPViewPassExecutorTests
|
|||
var oracle = new CurrentRenderSceneOracle();
|
||||
using var shadow = new RenderSceneShadowRuntime(
|
||||
RenderSceneGeneration.FromRaw(1));
|
||||
RenderProjectionRecord staticRecord =
|
||||
RenderProjectionRecordFactory.ProjectEntity(
|
||||
StaticRenderProjectionJournal.StaticEntityId(
|
||||
landblockId,
|
||||
cellStatic.Id),
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
RenderOwnerIncarnation.FromRaw(cellStatic.Id),
|
||||
landblockId,
|
||||
interior.CellId,
|
||||
cellStatic,
|
||||
spatiallyVisible: true);
|
||||
RenderProjectionRecord dynamicRecord =
|
||||
RenderProjectionRecordFactory.ProjectEntity(
|
||||
LiveRenderProjectionJournal.ProjectionId(dynamic.Id),
|
||||
|
|
@ -226,6 +228,7 @@ public sealed class RetailPViewPassExecutorTests
|
|||
interior.CellId,
|
||||
dynamic,
|
||||
spatiallyVisible: true);
|
||||
shadow.Journal.Register(in staticRecord);
|
||||
shadow.Journal.Register(in dynamicRecord);
|
||||
shadow.DrainUpdateBoundary();
|
||||
var product = new RenderScenePViewFrameProductController(
|
||||
|
|
@ -235,12 +238,13 @@ public sealed class RetailPViewPassExecutorTests
|
|||
using var executor = new RecordingExecutor();
|
||||
|
||||
renderer.DrawInside(
|
||||
Frame(interior, [dynamic]),
|
||||
Frame(interior, [cellStatic, dynamic]),
|
||||
executor);
|
||||
|
||||
AssertAppearsInOrder(
|
||||
string.Join('|', executor.Operations),
|
||||
"entity-frame-begin",
|
||||
"entity-route:CellStatic:0:00000000",
|
||||
"entity-route:DynamicLast:0:00000000",
|
||||
"entity-frame-complete");
|
||||
Assert.Equal(0, product.Snapshot.MismatchCount);
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
using AcDream.App.Rendering.Walk;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Walk;
|
||||
|
||||
public sealed class LandWalkOrderTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(5, 5, 11)] // retail defaults: mid_width 11, viewer centered
|
||||
[InlineData(0, 0, 11)] // viewer at a grid corner
|
||||
[InlineData(10, 3, 11)] // viewer at an edge
|
||||
[InlineData(1, 1, 3)]
|
||||
public void Block_order_covers_every_slot_exactly_once_viewer_first(
|
||||
int vx, int vy, int width)
|
||||
{
|
||||
Span<int> order = new int[width * width];
|
||||
|
||||
int count = LandWalkOrder.GetBlockOrder(vx, vy, width, order);
|
||||
|
||||
Assert.Equal(width * width, count);
|
||||
Assert.Equal(vx * width + vy, order[0]);
|
||||
var seen = new HashSet<int>();
|
||||
foreach (int slot in order)
|
||||
Assert.True(seen.Add(slot), $"slot {slot} emitted twice");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Block_order_is_ring_monotone_near_to_far()
|
||||
{
|
||||
const int width = 11;
|
||||
const int vx = 5, vy = 5;
|
||||
Span<int> order = new int[width * width];
|
||||
LandWalkOrder.GetBlockOrder(vx, vy, width, order);
|
||||
|
||||
int previousRing = 0;
|
||||
foreach (int slot in order)
|
||||
{
|
||||
int ring = Math.Max(Math.Abs(slot / width - vx), Math.Abs(slot % width - vy));
|
||||
Assert.True(ring >= previousRing, "a later entry moved to a NEARER ring");
|
||||
previousRing = ring;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Block_order_ring_one_matches_the_decoded_slot_pattern()
|
||||
{
|
||||
// Hand-expanded from the byte-decoded tables for ring 1, step 0:
|
||||
// (0,+1) (-1,0) (0,-1) (+1,0) (+1,+1) (-1,+1) (-1,-1) (+1,-1)
|
||||
// — cardinals first (N W S E), then diagonals (NE NW SW SE).
|
||||
const int width = 3;
|
||||
Span<int> order = new int[9];
|
||||
LandWalkOrder.GetBlockOrder(1, 1, width, order);
|
||||
|
||||
int[] expected =
|
||||
[
|
||||
1 * 3 + 1, // viewer
|
||||
1 * 3 + 2, // (0,+1)
|
||||
0 * 3 + 1, // (-1,0)
|
||||
1 * 3 + 0, // (0,-1)
|
||||
2 * 3 + 1, // (+1,0)
|
||||
2 * 3 + 2, // (+1,+1)
|
||||
0 * 3 + 2, // (-1,+1)
|
||||
0 * 3 + 0, // (-1,-1)
|
||||
2 * 3 + 0, // (+1,-1)
|
||||
];
|
||||
Assert.Equal(expected, order.ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 8)]
|
||||
[InlineData(7, 7, 8)]
|
||||
[InlineData(3, 5, 8)]
|
||||
[InlineData(0, 0, 1)]
|
||||
public void Cell_order_fills_exactly_and_ends_at_the_closest_cell(
|
||||
int cx, int cy, int side)
|
||||
{
|
||||
Span<int> order = new int[side * side];
|
||||
order.Fill(-1);
|
||||
|
||||
LandWalkOrder.FillCellOrderFarToNear(cx, cy, side, order);
|
||||
|
||||
Assert.Equal(cx * side + cy, order[^1]);
|
||||
var seen = new HashSet<int>();
|
||||
foreach (int slot in order)
|
||||
{
|
||||
Assert.InRange(slot, 0, side * side - 1);
|
||||
Assert.True(seen.Add(slot), $"slot {slot} written twice");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cell_order_forward_walk_is_far_to_near()
|
||||
{
|
||||
const int side = 8;
|
||||
const int cx = 2, cy = 6;
|
||||
Span<int> order = new int[side * side];
|
||||
LandWalkOrder.FillCellOrderFarToNear(cx, cy, side, order);
|
||||
|
||||
int previousRing = int.MaxValue;
|
||||
foreach (int slot in order)
|
||||
{
|
||||
int ring = Math.Max(Math.Abs(slot / side - cx), Math.Abs(slot % side - cy));
|
||||
Assert.True(ring <= previousRing, "a later entry moved to a FARTHER ring");
|
||||
previousRing = ring;
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, LandDirection.InViewerBlock)]
|
||||
[InlineData(0, 3, LandDirection.North)]
|
||||
[InlineData(0, -1, LandDirection.South)]
|
||||
[InlineData(2, 0, LandDirection.East)]
|
||||
[InlineData(-4, 0, LandDirection.West)]
|
||||
[InlineData(-1, 1, LandDirection.NorthWest)]
|
||||
[InlineData(-2, -2, LandDirection.SouthWest)]
|
||||
[InlineData(3, 1, LandDirection.NorthEast)]
|
||||
[InlineData(1, -5, LandDirection.SouthEast)]
|
||||
public void Direction_mapping_matches_get_dir(int dx, int dy, LandDirection expected)
|
||||
=> Assert.Equal(expected, LandWalkOrder.GetDirection(dx, dy));
|
||||
|
||||
[Theory]
|
||||
[InlineData(LandDirection.InViewerBlock, 5, 3, 8, 5, 3)]
|
||||
[InlineData(LandDirection.North, 5, 3, 8, 5, 0)] // block north: south edge faces viewer
|
||||
[InlineData(LandDirection.South, 5, 3, 8, 5, 7)]
|
||||
[InlineData(LandDirection.East, 5, 3, 8, 0, 3)] // block east: west edge faces viewer
|
||||
[InlineData(LandDirection.West, 5, 3, 8, 7, 3)]
|
||||
[InlineData(LandDirection.NorthWest, 5, 3, 8, 7, 0)]
|
||||
[InlineData(LandDirection.SouthWest, 5, 3, 8, 7, 7)]
|
||||
[InlineData(LandDirection.NorthEast, 5, 3, 8, 0, 0)]
|
||||
[InlineData(LandDirection.SouthEast, 5, 3, 8, 0, 7)]
|
||||
public void Closest_cell_matches_the_direction_switch(
|
||||
LandDirection dir, int sqx, int sqy, int side, int expectedX, int expectedY)
|
||||
=> Assert.Equal((expectedX, expectedY), LandWalkOrder.ClosestCell(dir, sqx, sqy, side));
|
||||
|
||||
[Fact]
|
||||
public void Closest_cell_scales_the_viewer_coordinate_for_low_resolution_blocks()
|
||||
{
|
||||
// side_cell_count < 8: retail divides the viewer's cell coord by 8/side.
|
||||
Assert.Equal((2, 1), LandWalkOrder.ClosestCell(LandDirection.InViewerBlock, 5, 3, 4));
|
||||
Assert.Equal((0, 0), LandWalkOrder.ClosestCell(LandDirection.InViewerBlock, 7, 7, 1));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,789 +0,0 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Gpu.Vk;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
using AcDream.App.Tests.Rendering.Gpu;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Meshing;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Lib.IO;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW2: <see cref="WbDrawDispatcher.BuildOrderedMergeRuns"/>
|
||||
/// (pure CPU merge-run legality) and, from Campaign FW stage FW3.4a,
|
||||
/// <see cref="WbDrawDispatcher.PrepareOrderedStream"/> +
|
||||
/// <see cref="WbDrawDispatcher.DrawOrderedRange"/> (the same legality proven
|
||||
/// through actual recorded RHI calls against <see cref="RecordingGpuDevice"/>,
|
||||
/// replacing the single <c>SubmitOrderedStream</c> call those two now split).
|
||||
/// </summary>
|
||||
public sealed class OrderPreservingSubmitterTests
|
||||
{
|
||||
private static OrderedDrawCommand MakeCommand(
|
||||
int index,
|
||||
WalkDrawStage stage = WalkDrawStage.Terrain,
|
||||
TranslucencyKind translucency = TranslucencyKind.Opaque,
|
||||
CullMode cullMode = CullMode.CounterClockwise,
|
||||
uint detailCategory = 0) =>
|
||||
new(
|
||||
Key: new GroupKey(
|
||||
FirstIndex: (uint)index * 3,
|
||||
BaseVertex: index * 4,
|
||||
IndexCount: 3,
|
||||
TextureSlot: new GpuTextureSlot((uint)index),
|
||||
TextureLayer: 0,
|
||||
Translucency: translucency,
|
||||
FoliageFlags: 0,
|
||||
CullMode: cullMode),
|
||||
Transform: Matrix4x4.CreateTranslation(index, index * 2, index * 3),
|
||||
Stage: stage,
|
||||
CellId: 0x8C040100u + (uint)index,
|
||||
ClipSlot: 0,
|
||||
Lights: WbDrawDispatcher.InstanceLightSet.Disabled,
|
||||
IndoorFlag: 0,
|
||||
Alpha: 1f,
|
||||
SelectionLighting: Vector2.Zero,
|
||||
DetailCategory: detailCategory);
|
||||
|
||||
private static OrderedDrawStream StreamOf(params OrderedDrawCommand[] commands)
|
||||
{
|
||||
var stream = new OrderedDrawStream();
|
||||
foreach (OrderedDrawCommand command in commands)
|
||||
stream.Append(command);
|
||||
return stream;
|
||||
}
|
||||
|
||||
// ── Pure BuildOrderedMergeRuns — no GPU device ─────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void BuildOrderedMergeRuns_MergesThreeAdjacentSameStateCommandsIntoOneRun()
|
||||
{
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0), MakeCommand(1), MakeCommand(2));
|
||||
|
||||
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
||||
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
||||
|
||||
WbDrawDispatcher.OrderedMergeRun run = Assert.Single(runs);
|
||||
Assert.Equal(0, run.FirstCommand);
|
||||
Assert.Equal(3, run.CommandCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildOrderedMergeRuns_SplitsOnAPipelineBucketChange()
|
||||
{
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, translucency: TranslucencyKind.Opaque),
|
||||
MakeCommand(1, translucency: TranslucencyKind.Opaque),
|
||||
MakeCommand(2, translucency: TranslucencyKind.AlphaBlend));
|
||||
|
||||
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
||||
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
new WbDrawDispatcher.OrderedMergeRun(0, 2),
|
||||
new WbDrawDispatcher.OrderedMergeRun(2, 1),
|
||||
],
|
||||
runs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildOrderedMergeRuns_SplitsOnACullModeChange()
|
||||
{
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, cullMode: CullMode.None),
|
||||
MakeCommand(1, cullMode: CullMode.None),
|
||||
MakeCommand(2, cullMode: CullMode.Clockwise));
|
||||
|
||||
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
||||
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
new WbDrawDispatcher.OrderedMergeRun(0, 2),
|
||||
new WbDrawDispatcher.OrderedMergeRun(2, 1),
|
||||
],
|
||||
runs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The load-bearing new assertion: two commands whose material state
|
||||
/// (bucket, cull mode, detail category) is IDENTICAL still split into two
|
||||
/// runs when their <see cref="WalkDrawStage"/> differs. Nothing about the
|
||||
/// deferred-alpha template this submitter borrows from ever had to
|
||||
/// consider stage — walk order introduces it.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void BuildOrderedMergeRuns_SplitsOnAStageChangeEvenWithIdenticalMaterialState()
|
||||
{
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, stage: WalkDrawStage.Terrain),
|
||||
MakeCommand(1, stage: WalkDrawStage.CellStatic));
|
||||
|
||||
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
||||
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
new WbDrawDispatcher.OrderedMergeRun(0, 1),
|
||||
new WbDrawDispatcher.OrderedMergeRun(1, 1),
|
||||
],
|
||||
runs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildOrderedMergeRuns_ADetailCategoryCommandIsAlwaysSolo()
|
||||
{
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0),
|
||||
MakeCommand(1, detailCategory: 1),
|
||||
MakeCommand(2));
|
||||
|
||||
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
||||
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
new WbDrawDispatcher.OrderedMergeRun(0, 1),
|
||||
new WbDrawDispatcher.OrderedMergeRun(1, 1),
|
||||
new WbDrawDispatcher.OrderedMergeRun(2, 1),
|
||||
],
|
||||
runs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildOrderedMergeRuns_ThrowsNotSupportedForAPortalPunchCommand()
|
||||
{
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, stage: WalkDrawStage.Terrain),
|
||||
MakeCommand(1, stage: WalkDrawStage.PortalPunch));
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
() => WbDrawDispatcher.BuildOrderedMergeRuns(stream));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildOrderedMergeRuns_EveryCommandBelongsToExactlyOneRunInOrderWithNoGaps()
|
||||
{
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, stage: WalkDrawStage.Terrain, translucency: TranslucencyKind.Opaque, cullMode: CullMode.None),
|
||||
MakeCommand(1, stage: WalkDrawStage.Terrain, translucency: TranslucencyKind.Opaque, cullMode: CullMode.None),
|
||||
MakeCommand(2, stage: WalkDrawStage.Terrain, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.None),
|
||||
MakeCommand(3, stage: WalkDrawStage.Terrain, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.Clockwise),
|
||||
MakeCommand(4, stage: WalkDrawStage.CellStatic, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.Clockwise),
|
||||
MakeCommand(5, stage: WalkDrawStage.CellStatic, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.Clockwise, detailCategory: 1),
|
||||
MakeCommand(6, stage: WalkDrawStage.CellStatic, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.Clockwise));
|
||||
|
||||
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
||||
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
||||
|
||||
int coveredThrough = 0;
|
||||
int totalCommands = 0;
|
||||
foreach (WbDrawDispatcher.OrderedMergeRun run in runs)
|
||||
{
|
||||
Assert.Equal(coveredThrough, run.FirstCommand);
|
||||
Assert.True(run.CommandCount > 0);
|
||||
coveredThrough = run.FirstCommand + run.CommandCount;
|
||||
totalCommands += run.CommandCount;
|
||||
}
|
||||
Assert.Equal(stream.Count, coveredThrough);
|
||||
Assert.Equal(stream.Count, totalCommands);
|
||||
}
|
||||
|
||||
// ── PrepareOrderedStream + DrawOrderedRange — recorded RHI calls against
|
||||
// RecordingGpuDevice. Campaign FW3.4a replaced the single
|
||||
// SubmitOrderedStream call with this pair (prepare the whole stream once,
|
||||
// draw it via one or more ranges) — every test below that used to call
|
||||
// SubmitOrderedStream now calls Prepare once and Draw the WHOLE stream as
|
||||
// ONE range, which is exactly SubmitOrderedStream's old behavior; the
|
||||
// "several ranges" and "bind once" shapes get their own tests further
|
||||
// down since they have no FW2 analogue. ────────────────────────────────
|
||||
|
||||
private static void PrepareAndDrawWhole(WbDrawDispatcher dispatcher, DrawScope draw, OrderedDrawStream stream)
|
||||
{
|
||||
dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
|
||||
if (stream.Count > 0)
|
||||
dispatcher.DrawOrderedRange(draw.Pass, 0, stream.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareThenDraw_AlternatingStateCommandsRecordOneDrawEachInOrder()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, translucency: TranslucencyKind.Opaque),
|
||||
MakeCommand(1, translucency: TranslucencyKind.AlphaBlend),
|
||||
MakeCommand(2, translucency: TranslucencyKind.Opaque),
|
||||
MakeCommand(3, translucency: TranslucencyKind.AlphaBlend));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
List<(int Start, int Count)> ranges = DecodeDrawRanges(fx.Device);
|
||||
Assert.Equal([(0, 1), (1, 1), (2, 1), (3, 1)], ranges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareThenDraw_MergesAdjacentSameStateCommandsIntoOneMultiDrawIndirect()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0), MakeCommand(1), MakeCommand(2));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
List<(int Start, int Count)> ranges = DecodeDrawRanges(fx.Device);
|
||||
Assert.Equal([(0, 3)], ranges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareThenDraw_CullModeChangeRecordsSeparateCullCallsAndSplitsTheDraw()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, cullMode: CullMode.None),
|
||||
MakeCommand(1, cullMode: CullMode.None),
|
||||
MakeCommand(2, cullMode: CullMode.Clockwise));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
Assert.Equal([(0, 2), (2, 1)], DecodeDrawRanges(fx.Device));
|
||||
|
||||
List<GpuCullMode> cullCalls =
|
||||
[.. fx.Device.Calls.OfType<GpuRecordedCullMode>().Select(c => c.CullMode)];
|
||||
// ApplyCullModeRhi: CullMode.None -> GpuCullMode.None, CullMode.Clockwise -> GpuCullMode.Front.
|
||||
Assert.Equal([GpuCullMode.None, GpuCullMode.Front], cullCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareThenDraw_StageChangeSplitsTheDrawEvenWithIdenticalMaterialState()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, stage: WalkDrawStage.Terrain),
|
||||
MakeCommand(1, stage: WalkDrawStage.CellStatic));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
Assert.Equal([(0, 1), (1, 1)], DecodeDrawRanges(fx.Device));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareThenDraw_ADetailCategoryCommandRecordsItsOwnSoloDraw()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0),
|
||||
MakeCommand(1, detailCategory: 1),
|
||||
MakeCommand(2));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
Assert.Equal([(0, 1), (1, 1), (2, 1)], DecodeDrawRanges(fx.Device));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareThenDraw_OpaqueRunUsesRenderPassZeroAndAlphaBlendRunUsesRenderPassOne()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, translucency: TranslucencyKind.Opaque),
|
||||
MakeCommand(1, translucency: TranslucencyKind.AlphaBlend));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
List<(GpuPushConstants Constants, int Start, int Count)> runs = DecodeRuns(fx.Device);
|
||||
Assert.Equal(2, runs.Count);
|
||||
Assert.Equal(0, runs[0].Constants.RenderPass);
|
||||
Assert.Equal(1, runs[1].Constants.RenderPass);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareOrderedStream_ThrowsNotSupportedForAPortalPunchCommandBeforeAnyDraw()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, stage: WalkDrawStage.PortalPunch));
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
() => fx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity));
|
||||
|
||||
Assert.Empty(fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
|
||||
Assert.Empty(fx.Device.Calls.OfType<GpuRecordedStorageBind>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareOrderedStream_EmptyStreamRecordsNoDraws()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
fx.Dispatcher.PrepareOrderedStream(draw.Frame, new OrderedDrawStream(), Matrix4x4.Identity);
|
||||
|
||||
Assert.Empty(fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
|
||||
}
|
||||
|
||||
// ── Campaign FW3.4a — the shapes with no FW2 analogue: drawing the SAME
|
||||
// prepared stream as several ranges, the bind-once optimization, the
|
||||
// fail-loud range check, and the assert-don't-slice straddle guard. ───
|
||||
|
||||
/// <summary>
|
||||
/// The whole point of the split: drawing the SAME stream as TWO ranges
|
||||
/// (with the boundary between them supplied to Prepare, exactly as
|
||||
/// WalkFrameDriver.Replay supplies its recorded mark positions) produces
|
||||
/// the identical total recorded draw/cull/push-constant calls as drawing
|
||||
/// it as one range — the range split changes nothing about what reaches
|
||||
/// the GPU, only how many DrawOrderedRange calls got there.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DrawOrderedRange_AsTwoRangesAtASegmentBoundary_MatchesOneRangeOverTheWholeStream()
|
||||
{
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, translucency: TranslucencyKind.Opaque),
|
||||
MakeCommand(1, translucency: TranslucencyKind.Opaque),
|
||||
MakeCommand(2, translucency: TranslucencyKind.Opaque),
|
||||
MakeCommand(3, translucency: TranslucencyKind.Opaque));
|
||||
|
||||
using var wholeFx = new DispatcherFixture();
|
||||
using (DrawScope draw = wholeFx.BeginDraw())
|
||||
{
|
||||
wholeFx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
|
||||
wholeFx.Dispatcher.DrawOrderedRange(draw.Pass, 0, stream.Count);
|
||||
}
|
||||
List<(int Start, int Count)> wholeRanges = DecodeDrawRanges(wholeFx.Device);
|
||||
|
||||
using var splitFx = new DispatcherFixture();
|
||||
using (DrawScope draw = splitFx.BeginDraw())
|
||||
{
|
||||
// Command 2 is a segment boundary (mirrors a mark WalkFrameDriver
|
||||
// would record there, e.g. a cell shell between two same-state
|
||||
// segments) — without it, all four commands would merge into ONE
|
||||
// run; the boundary forces two.
|
||||
splitFx.Dispatcher.PrepareOrderedStream(
|
||||
draw.Frame, stream, Matrix4x4.Identity, forcedBreaksAscending: [2]);
|
||||
splitFx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 2);
|
||||
splitFx.Dispatcher.DrawOrderedRange(draw.Pass, 2, 2);
|
||||
}
|
||||
List<(int Start, int Count)> splitRanges = DecodeDrawRanges(splitFx.Device);
|
||||
|
||||
// The split path draws two runs where the whole-range path drew one
|
||||
// (the forced boundary is the only difference) — but every command
|
||||
// reaches the GPU exactly once, in order, with identical coverage.
|
||||
Assert.Equal([(0, 4)], wholeRanges);
|
||||
Assert.Equal([(0, 2), (2, 2)], splitRanges);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The FW3.4a perf shape itself: the nine per-instance storage binds plus
|
||||
/// the warm-up pipeline bind happen on the FIRST DrawOrderedRange call in
|
||||
/// a frame only — a second call over the same prepared payload issues no
|
||||
/// further StorageBind calls, which is the whole reason this stage exists
|
||||
/// (the old SubmitOrderedStream rebound everything on every call).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DrawOrderedRange_EveryCallRebindsTheStorageSections()
|
||||
{
|
||||
// The corrected FW3.4a contract (the dense-Arwic device-lost fix):
|
||||
// between ordered ranges the walk's leaf draws and RetailAlphaQueue
|
||||
// flushes rebind the SAME set-0 slots to THEIR sections, so every
|
||||
// DrawOrderedRange call must re-bind its own — a latched skip draws
|
||||
// the next range against foreign buffers. Only the ring WRITES are
|
||||
// once-per-frame (PrepareOrderedStream); binds repeat per range,
|
||||
// exactly like DrawPreparedAlphaBatchRhi.
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0, stage: WalkDrawStage.Terrain),
|
||||
MakeCommand(1, stage: WalkDrawStage.CellStatic));
|
||||
|
||||
fx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
|
||||
fx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 1);
|
||||
int boundAfterFirst = fx.Device.Calls.OfType<GpuRecordedStorageBind>().Count();
|
||||
Assert.True(boundAfterFirst > 0);
|
||||
|
||||
fx.Dispatcher.DrawOrderedRange(draw.Pass, 1, 1);
|
||||
int boundAfterSecond = fx.Device.Calls.OfType<GpuRecordedStorageBind>().Count();
|
||||
|
||||
Assert.Equal(boundAfterFirst * 2, boundAfterSecond);
|
||||
// Both commands drew; the rebinds changed nothing about coverage.
|
||||
Assert.Equal([(0, 1), (1, 1)], DecodeDrawRanges(fx.Device));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fail-loud range check (mirrors DrawPreparedAlphaBatchRhi's): a range
|
||||
/// outside what PrepareOrderedStream uploaded throws rather than drawing
|
||||
/// garbage or silently clamping — including a draw attempted before ANY
|
||||
/// Prepare call this frame.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DrawOrderedRange_RangeExceedingThePreparedPayload_Throws()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(MakeCommand(0));
|
||||
fx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() => fx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 2));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() => fx.Dispatcher.DrawOrderedRange(draw.Pass, 1, 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawOrderedRange_BeforeAnyPrepareCallThisFrame_Throws()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() => fx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The plan's "assert it" rule: a range that does not align with a merge
|
||||
/// run boundary throws rather than silently slicing the run — proven
|
||||
/// directly here (skipping the boundary a real WalkFrameDriver mark would
|
||||
/// supply) since production code always supplies the boundary and would
|
||||
/// never exercise this path.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DrawOrderedRange_RangeStraddlingAMergeRun_Throws()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
// All four commands share stage/bucket/cull — ONE merge run [0, 4) —
|
||||
// and no forced break is supplied, so a [0, 2) range straddles it.
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0), MakeCommand(1), MakeCommand(2), MakeCommand(3));
|
||||
fx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => fx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fail-loud invariant: whatever the state pattern, the recorded
|
||||
/// MultiDrawIndirect calls' DrawCounts always sum to the stream's Count —
|
||||
/// no command is ever silently skipped, and none is drawn twice.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PrepareThenDraw_TotalRecordedDrawCountAlwaysEqualsTheStreamCount()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
var stream = new OrderedDrawStream();
|
||||
var stages = new[] { WalkDrawStage.Terrain, WalkDrawStage.CellStatic, WalkDrawStage.BuildingShell };
|
||||
var blends = new[]
|
||||
{
|
||||
TranslucencyKind.Opaque, TranslucencyKind.AlphaBlend,
|
||||
TranslucencyKind.Additive, TranslucencyKind.InvAlpha,
|
||||
};
|
||||
var culls = new[] { CullMode.None, CullMode.Clockwise, CullMode.CounterClockwise };
|
||||
const int commandCount = 11;
|
||||
for (int i = 0; i < commandCount; i++)
|
||||
{
|
||||
stream.Append(MakeCommand(
|
||||
i,
|
||||
stage: stages[i % stages.Length],
|
||||
translucency: blends[i % blends.Length],
|
||||
cullMode: culls[i % culls.Length],
|
||||
detailCategory: i == 5 ? 1u : 0u));
|
||||
}
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
List<(int Start, int Count)> ranges = DecodeDrawRanges(fx.Device);
|
||||
int sum = ranges.Sum(r => r.Count);
|
||||
Assert.Equal(commandCount, sum);
|
||||
|
||||
int coveredThrough = 0;
|
||||
foreach ((int start, int count) in ranges)
|
||||
{
|
||||
Assert.Equal(coveredThrough, start);
|
||||
coveredThrough += count;
|
||||
}
|
||||
Assert.Equal(commandCount, coveredThrough);
|
||||
}
|
||||
|
||||
// ── Decode helpers ──────────────────────────────────────────────────────
|
||||
|
||||
private static List<(int Start, int Count)> DecodeDrawRanges(RecordingGpuDevice device) =>
|
||||
[.. DecodeRuns(device).Select(r => (r.Start, r.Count))];
|
||||
|
||||
private static List<(GpuPushConstants Constants, int Start, int Count)> DecodeRuns(
|
||||
RecordingGpuDevice device)
|
||||
{
|
||||
GpuPushConstants? lastConstants = null;
|
||||
uint? commandBase = null;
|
||||
var result = new List<(GpuPushConstants, int, int)>();
|
||||
foreach (var call in device.Calls)
|
||||
{
|
||||
if (call is GpuRecordedPushConstants pc)
|
||||
{
|
||||
lastConstants = pc.Constants;
|
||||
}
|
||||
else if (call is GpuRecordedMultiDrawIndirect mdi)
|
||||
{
|
||||
Assert.Equal((uint)WbDrawDispatcher.DrawCommandStride, mdi.StrideBytes);
|
||||
commandBase ??= mdi.OffsetBytes;
|
||||
int start = (int)((mdi.OffsetBytes - commandBase.Value) / mdi.StrideBytes);
|
||||
Assert.NotNull(lastConstants);
|
||||
result.Add((lastConstants!.Value, start, (int)mdi.DrawCount));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Fixture: a real WbDrawDispatcher against RecordingGpuDevice ─────────
|
||||
|
||||
private readonly struct DrawScope : IDisposable
|
||||
{
|
||||
private readonly IDisposable _publication;
|
||||
private readonly IGpuPassEncoder _pass;
|
||||
|
||||
public DrawScope(IGpuFrame frame, IGpuPassEncoder pass, IDisposable publication)
|
||||
{
|
||||
Frame = frame;
|
||||
_pass = pass;
|
||||
_publication = publication;
|
||||
}
|
||||
|
||||
public IGpuFrame Frame { get; }
|
||||
|
||||
public IGpuPassEncoder Pass => _pass;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_publication.Dispose();
|
||||
_pass.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DispatcherFixture : IDisposable
|
||||
{
|
||||
private readonly WbMeshAdapter _meshAdapter;
|
||||
private readonly TextureCache _textures;
|
||||
|
||||
public DispatcherFixture()
|
||||
{
|
||||
Device = new RecordingGpuDevice();
|
||||
FrameLifetime = new GpuDeviceFrameLifetime(Device);
|
||||
Scope = new VulkanWorldPassScope(sampleCount: 1);
|
||||
_textures = new TextureCache(Device, new NoopDatReaderWriter());
|
||||
_meshAdapter = new WbMeshAdapter(
|
||||
Device,
|
||||
new NoopDatReaderWriter(),
|
||||
new NullPreparedAssetSource(),
|
||||
NullLogger<WbMeshAdapter>.Instance,
|
||||
Device.Retirement);
|
||||
var entitySpawnAdapter = new EntitySpawnAdapter(
|
||||
_textures,
|
||||
_ => throw new NotSupportedException(
|
||||
"Not exercised by SubmitOrderedStream tests."));
|
||||
|
||||
Dispatcher = new WbDrawDispatcher(
|
||||
Device,
|
||||
FrameLifetime,
|
||||
Scope,
|
||||
_textures,
|
||||
_meshAdapter,
|
||||
entitySpawnAdapter,
|
||||
new EntityClassificationCache(),
|
||||
new AcDream.Core.Rendering.TranslucencyFadeManager());
|
||||
}
|
||||
|
||||
public RecordingGpuDevice Device { get; }
|
||||
|
||||
public GpuDeviceFrameLifetime FrameLifetime { get; }
|
||||
|
||||
public VulkanWorldPassScope Scope { get; }
|
||||
|
||||
public WbDrawDispatcher Dispatcher { get; }
|
||||
|
||||
/// <summary>Opens a frame and a backbuffer pass, publishes it on
|
||||
/// <see cref="Scope"/>, then clears the recorded calls so a test only
|
||||
/// sees what its own <c>SubmitOrderedStream</c> call produced.</summary>
|
||||
public DrawScope BeginDraw()
|
||||
{
|
||||
FrameLifetime.BeginFrame();
|
||||
IGpuFrame frame = FrameLifetime.CurrentFrame!;
|
||||
IGpuPassEncoder pass = frame.BeginPass(
|
||||
GpuPassDescription.BackbufferClear(
|
||||
"fw2-ordered-stream-test", Vector4.Zero, sampleCount: 1));
|
||||
IDisposable publication = Scope.Publish(pass);
|
||||
Device.Clear();
|
||||
return new DrawScope(frame, pass, publication);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispatcher.Dispose();
|
||||
_meshAdapter.Dispose();
|
||||
_textures.Dispose();
|
||||
Device.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NullPreparedAssetSource : IPreparedAssetSource
|
||||
{
|
||||
public PreparedAssetSourceStats Stats => default;
|
||||
|
||||
public CacheStats DecodedTextureCacheStats => default;
|
||||
|
||||
public PreparedAssetPresence Probe(
|
||||
AcDream.Content.Pak.PakAssetType type,
|
||||
uint sourceFileId) =>
|
||||
PreparedAssetPresence.Missing;
|
||||
|
||||
public PreparedAssetReadResult Read(
|
||||
in PreparedAssetRequest request,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
PreparedAssetReadResult.Missing;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NoopDatReaderWriter : IDatReaderWriter
|
||||
{
|
||||
private readonly StubDatabase _portal = new();
|
||||
private readonly StubDatabase _highRes = new();
|
||||
private readonly StubDatabase _language = new();
|
||||
private readonly StubDatabase _cell = new();
|
||||
|
||||
public string SourceDirectory => string.Empty;
|
||||
|
||||
public IDatDatabase Portal => _portal;
|
||||
|
||||
public IDatDatabase Cell => _cell;
|
||||
|
||||
public ReadOnlyDictionary<uint, IDatDatabase> CellRegions { get; } =
|
||||
new(new Dictionary<uint, IDatDatabase>());
|
||||
|
||||
public IDatDatabase HighRes => _highRes;
|
||||
|
||||
public IDatDatabase Language => _language;
|
||||
|
||||
public IDatDatabase Local => _language;
|
||||
|
||||
public ReadOnlyDictionary<uint, uint> RegionFileMap { get; } =
|
||||
new(new Dictionary<uint, uint>());
|
||||
|
||||
public int PortalIteration => 0;
|
||||
|
||||
public int CellIteration => 0;
|
||||
|
||||
public int HighResIteration => 0;
|
||||
|
||||
public int LanguageIteration => 0;
|
||||
|
||||
public bool TryGetFileBytes(
|
||||
uint regionId,
|
||||
uint fileId,
|
||||
ref byte[] bytes,
|
||||
out int bytesRead)
|
||||
{
|
||||
bytesRead = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj =>
|
||||
Array.Empty<uint>();
|
||||
|
||||
public IEnumerable<IDatReaderWriter.IdResolution> ResolveId(uint id) =>
|
||||
Array.Empty<IDatReaderWriter.IdResolution>();
|
||||
|
||||
public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public bool TrySave<T>(
|
||||
uint regionId,
|
||||
T obj,
|
||||
int iteration = 0) where T : IDBObj =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
[return: MaybeNull]
|
||||
public T Get<T>(uint fileId) where T : IDBObj => default;
|
||||
|
||||
public bool TryGet<T>(
|
||||
uint fileId,
|
||||
[MaybeNullWhen(false)] out T value) where T : IDBObj
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class StubDatabase : IDatDatabase
|
||||
{
|
||||
public DatDatabase Db => throw new NotSupportedException();
|
||||
|
||||
public int Iteration => 0;
|
||||
|
||||
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj =>
|
||||
Array.Empty<uint>();
|
||||
|
||||
public bool TryGet<T>(
|
||||
uint fileId,
|
||||
[MaybeNullWhen(false)] out T value) where T : IDBObj
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetFileBytes(
|
||||
uint fileId,
|
||||
[MaybeNullWhen(false)] out byte[] value)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetFileBytes(
|
||||
uint fileId,
|
||||
ref byte[] bytes,
|
||||
out int bytesRead)
|
||||
{
|
||||
bytesRead = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
using AcDream.Core.Meshing;
|
||||
using DatReaderWriter.Enums;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW2: pure data-structure tests for
|
||||
/// <see cref="OrderedDrawStream"/>. No GPU device, no dispatcher — this is the
|
||||
/// append-only struct-of-arrays storage on its own.
|
||||
/// </summary>
|
||||
public sealed class OrderedDrawStreamTests
|
||||
{
|
||||
private static OrderedDrawCommand MakeCommand(
|
||||
int index,
|
||||
WalkDrawStage stage = WalkDrawStage.Terrain,
|
||||
TranslucencyKind translucency = TranslucencyKind.Opaque,
|
||||
CullMode cullMode = CullMode.CounterClockwise,
|
||||
uint detailCategory = 0) =>
|
||||
new(
|
||||
Key: new GroupKey(
|
||||
FirstIndex: (uint)index * 3,
|
||||
BaseVertex: index * 4,
|
||||
IndexCount: 3,
|
||||
TextureSlot: new GpuTextureSlot((uint)index),
|
||||
TextureLayer: 0,
|
||||
Translucency: translucency,
|
||||
FoliageFlags: 0,
|
||||
CullMode: cullMode),
|
||||
Transform: Matrix4x4.CreateTranslation(index, index * 2, index * 3),
|
||||
Stage: stage,
|
||||
CellId: 0x8C040100u + (uint)index,
|
||||
ClipSlot: (uint)index + 1,
|
||||
Lights: WbDrawDispatcher.InstanceLightSet.Disabled,
|
||||
IndoorFlag: (uint)(index % 2),
|
||||
Alpha: 1f - index * 0.01f,
|
||||
SelectionLighting: new Vector2(index, index + 1),
|
||||
DetailCategory: detailCategory);
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_HasZeroCount()
|
||||
{
|
||||
var stream = new OrderedDrawStream();
|
||||
|
||||
Assert.Equal(0, stream.Count);
|
||||
Assert.Empty(stream.Keys);
|
||||
Assert.Empty(stream.Transforms);
|
||||
Assert.Empty(stream.Stages);
|
||||
Assert.Empty(stream.CellIds);
|
||||
Assert.Empty(stream.ClipSlots);
|
||||
Assert.Empty(stream.Lights);
|
||||
Assert.Empty(stream.IndoorFlags);
|
||||
Assert.Empty(stream.Alphas);
|
||||
Assert.Empty(stream.SelectionLighting);
|
||||
Assert.Empty(stream.DetailCategories);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Append_GrowsCountAndEveryParallelListInLockstep()
|
||||
{
|
||||
var stream = new OrderedDrawStream();
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
stream.Append(MakeCommand(i));
|
||||
|
||||
Assert.Equal(5, stream.Count);
|
||||
Assert.Equal(5, stream.Keys.Count);
|
||||
Assert.Equal(5, stream.Transforms.Count);
|
||||
Assert.Equal(5, stream.Stages.Count);
|
||||
Assert.Equal(5, stream.CellIds.Count);
|
||||
Assert.Equal(5, stream.ClipSlots.Count);
|
||||
Assert.Equal(5, stream.Lights.Count);
|
||||
Assert.Equal(5, stream.IndoorFlags.Count);
|
||||
Assert.Equal(5, stream.Alphas.Count);
|
||||
Assert.Equal(5, stream.SelectionLighting.Count);
|
||||
Assert.Equal(5, stream.DetailCategories.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Append_PreservesEveryFieldAtItsIndex()
|
||||
{
|
||||
var stream = new OrderedDrawStream();
|
||||
OrderedDrawCommand[] commands =
|
||||
[
|
||||
MakeCommand(0, WalkDrawStage.Terrain),
|
||||
MakeCommand(1, WalkDrawStage.CellStatic),
|
||||
MakeCommand(2, WalkDrawStage.BuildingShell),
|
||||
];
|
||||
|
||||
foreach (OrderedDrawCommand command in commands)
|
||||
stream.Append(command);
|
||||
|
||||
for (int i = 0; i < commands.Length; i++)
|
||||
{
|
||||
Assert.Equal(commands[i].Key, stream.Keys[i]);
|
||||
Assert.Equal(commands[i].Transform, stream.Transforms[i]);
|
||||
Assert.Equal(commands[i].Stage, stream.Stages[i]);
|
||||
Assert.Equal(commands[i].CellId, stream.CellIds[i]);
|
||||
Assert.Equal(commands[i].ClipSlot, stream.ClipSlots[i]);
|
||||
Assert.Equal(commands[i].Lights, stream.Lights[i]);
|
||||
Assert.Equal(commands[i].IndoorFlag, stream.IndoorFlags[i]);
|
||||
Assert.Equal(commands[i].Alpha, stream.Alphas[i]);
|
||||
Assert.Equal(commands[i].SelectionLighting, stream.SelectionLighting[i]);
|
||||
Assert.Equal(commands[i].DetailCategory, stream.DetailCategories[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(WalkDrawStage.Terrain)]
|
||||
[InlineData(WalkDrawStage.CellStatic)]
|
||||
[InlineData(WalkDrawStage.BuildingShell)]
|
||||
[InlineData(WalkDrawStage.PortalPunch)]
|
||||
[InlineData(WalkDrawStage.LookInStatic)]
|
||||
[InlineData(WalkDrawStage.Dynamic)]
|
||||
internal void Append_AcceptsEveryStageIncludingPortalPunch(WalkDrawStage stage)
|
||||
{
|
||||
// The stream itself is a dumb data structure — it accepts every stage
|
||||
// unconditionally. Only the SUBMITTER rejects PortalPunch (see
|
||||
// OrderPreservingSubmitterTests), so stage-separation gates can
|
||||
// exercise the boundary at the stream level too.
|
||||
var stream = new OrderedDrawStream();
|
||||
|
||||
stream.Append(MakeCommand(0, stage));
|
||||
|
||||
Assert.Equal(1, stream.Count);
|
||||
Assert.Equal(stage, stream.Stages[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_ClearsEveryParallelListToZeroCount()
|
||||
{
|
||||
var stream = new OrderedDrawStream();
|
||||
for (int i = 0; i < 7; i++)
|
||||
stream.Append(MakeCommand(i));
|
||||
Assert.Equal(7, stream.Count);
|
||||
|
||||
stream.Reset();
|
||||
|
||||
Assert.Equal(0, stream.Count);
|
||||
Assert.Empty(stream.Keys);
|
||||
Assert.Empty(stream.Transforms);
|
||||
Assert.Empty(stream.Stages);
|
||||
Assert.Empty(stream.CellIds);
|
||||
Assert.Empty(stream.ClipSlots);
|
||||
Assert.Empty(stream.Lights);
|
||||
Assert.Empty(stream.IndoorFlags);
|
||||
Assert.Empty(stream.Alphas);
|
||||
Assert.Empty(stream.SelectionLighting);
|
||||
Assert.Empty(stream.DetailCategories);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_ThenAppend_StartsAFreshInOrderSequence()
|
||||
{
|
||||
var stream = new OrderedDrawStream();
|
||||
stream.Append(MakeCommand(0, WalkDrawStage.Terrain));
|
||||
stream.Append(MakeCommand(1, WalkDrawStage.Terrain));
|
||||
stream.Reset();
|
||||
|
||||
stream.Append(MakeCommand(9, WalkDrawStage.Dynamic));
|
||||
|
||||
Assert.Equal(1, stream.Count);
|
||||
Assert.Equal(WalkDrawStage.Dynamic, stream.Stages[0]);
|
||||
Assert.Equal(0x8C040100u + 9u, stream.CellIds[0]);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue