Commit graph

968 commits

Author SHA1 Message Date
Erik
651badc2b9 fix(render): S3 chunk 3 round 1 - slot key, deferred cross-block terrain batches, per-frame terrain diagnostic
Fixes the three-lens review blockers against 671eb3ad4 (S3 section 9.6 F1-F6).

F1 - slot key (blocking, retail). TerrainModernRenderer.DrawLandCells
normalizes every incoming landblockId to (id & 0xFFFF0000u) | 0xFFFFu
before the _idToSlot lookup: the walk hands 0xXXYY0000
(WalkLandBlock.LandblockId) but AddLandblock stores under the DAT id
0xXXYYFFFF (LandblockRenderPublisher.LandblockId) - every walk lookup
was missing and the walk path drew NO terrain. Unit-tested end-to-end
through a real RecordingGpuDevice-backed TerrainModernRenderer
(TerrainWalkSlotKeyNormalizationTests): AddLandblock(0xA9B4FFFF, ...)
is found by a 0xA9B40000 lookup, an unknown landblock is a silent
per-entry no-op, and a batch mixing a known and unknown entry submits
only the known one.

F2 - deferred cross-block batching (blocking, driver). Retail's
DrawSortCell always follows DrawLandCell (LC/SC strictly alternate,
never two LC in a row - S3 section 9 R1), so chunk 3's "merge
consecutive same-landblock LandCell events" rule never actually
merged anything; the driver review flagged batching as inert.
WalkFrameDriver.Replay now keeps ONE pending terrain batch across
landblocks ((landblockId, side, cellIndex) entries, cleared at
Replay's own start); a LandCell event only appends; every OTHER event
kind that will itself submit GPU work (StreamMark, Sky, CellShell,
PunchFan, AlphaBarrier, LandscapeFlush, ClearInteriorDepth,
ExitSeals) flushes the pending batch first; a StaticParticles/
CellParticles turn asks the new ParticleSystem.
HasRenderableEmittersInCell (an allocation-free sibling of
CopyRenderableEmittersInCell) and, when the cell has no renderable
emitter, submits nothing and does NOT flush either - the whole point
of the deferred rule. The end of Replay flushes the remainder. This
is order-preserving by construction: a flush always lands at the
exact point the unbatched draw would have, so GPU submission order -
and therefore pixels - is identical to the unbatched baseline; only
the number of small terrain draw calls shrinks.
TerrainModernRenderer.DrawLandCellRuns becomes DrawLandCells(
viewProjection, IReadOnlyList<(uint LandblockId, int SideCellCount,
int CellIndex)>) - one MultiDrawIndexedIndirect over every entry's
runs, unknown slots skipped per-entry. IWalkFrameLeafRenderer.
DrawLandCellBatch drops its separate landblockId parameter to match
(a batch can span several landblocks now) and gains
HasRenderableEmittersInCell.

Batch-count demonstration: driven through a real WalkFrameDriver
Replay (OnLandCellTurn_MergesAcrossLandblocksOverAnEmptyParticleTurn_
RealSubmissionsSplit), 4 LandCell turns across 3 distinct landblocks,
separated only by an empty particle turn, a real StreamMark, and a
building's alpha barrier, submit as exactly 3 DrawLandCellBatch calls
(2+1+1) instead of 4 - the empty particle turn's non-flush merges two
otherwise-separate cross-landblock entries. At production scale the
same mechanism is expected to cut the terrace-edge frame's ~578
individual DrawLandCell events (S3 section 9's captured transcript
count) to "tens" of submitted batches, per the contract's own
expectation: most terrain cells have no particle owner nearby, so the
strict LC/[empty-SC]/LC/[empty-SC]/... run collapses into one batch
per region bounded by real content (a building, a StreamMark-worthy
cell, or a genuine emitter) rather than per cell.

F3 - per-frame terrain diagnostic (blocking, build/test). The walk
leaf no longer brackets each batch with TerrainDrawDiagnosticsController
.Begin()/Complete() (a per-batch Stopwatch Restart/Stop pair that was
pushing one timing SAMPLE per batch, not per frame).
RetailPViewPassExecutor.DrawWalkLandCellBatch instead times its own
call with a raw Stopwatch.GetTimestamp() delta (no allocation) and
hands the ticks to the controller's new AccumulateWalkBatch;
RetailPViewRenderer.DrawWalkDrivenStatics calls the new
CompleteWalkTerrainFrame() exactly once, immediately after
driver.Replay finishes - "the end of the walk replay", where the
deleted whole-stage terrain leaf's own Begin()/Complete() bracket
used to close - which pushes ONE elapsed-time sample (even a
zero-batch frame pushes a zero sample: one sample per frame, not per
landscape turn) and publishes on the existing 5-second cadence.
TerrainRenderDiagnosticFacts gains a Draws field alongside
VisibleSlots (both were the same field before); TerrainModernRenderer
tracks its own per-frame WalkVisibleSlotCount/WalkDrawCount (a
HashSet<int>/int cleared in BeginFrame, populated by DrawLandCells),
and the diagnostics source reports those whenever the walk drew at
least one batch this frame, falling back to the non-walk Draw()
path's VisibleSlots otherwise (the two paths never both run in the
same frame). The [TERRAIN-DIAG] line's meaning (cpu_us per frame) is
unchanged, so the S3 section 9.5 before/after compare stays valid.

F4 - driver pins for the LandCell position (major). Three RunFrame-
level pins replace the deleted TERRAIN:0 pins: an outdoor-root
sequence (SKY, then one LANDCELL, driving RetailFrameWalk.
DrawLandscape directly with a one-view/zero-vertex WalkPortalView so
WalkLandscape.CheckBlocks' admission stays the same deterministic
"CY-only" test RetailFrameWalkTests already relies on, while still
satisfying WalkFrameDriver's real >=1-active-view fail-loud guard);
an interior-root test with one real exit view and one populated
block (SKY, LANDCELL, LFLUSH, SEALS, SHELL...) built on the existing
RunFrame_InteriorFloodWithExitView_... fixture; and the T4 batching
pin re-expressed for the F2 rule (OnLandCellTurn_
MergesAcrossLandblocksOverAnEmptyParticleTurn_RealSubmissionsSplit,
described above). The fake leaf's DrawLandCellBatch now logs
LANDCELL:<lb>:<side>:<idx>[,...] per batch and gains
HasRenderableEmittersInCell backed by an opt-out CellsWithoutEmitters
set (default true - has-emitters - so every pre-existing pin in the
file keeps its old unconditional-submission behavior unchanged).

F5 - no code change: the walk's in-view gate is unchanged; no
whole-block terrain re-added.

F6 - minor/notes: DrawLandCells' own comment now states the walk's
CheckBlocks/landcell_check admission is the sole terrain culling
authority (retail has no separate terrain frustum test); the
HandleLandscapeTurn comment's inverted claim is corrected (a FARTHER
building's punch survived because NEARER terrain was drawn BEFORE
it, not after - the interleave now draws it after, matching retail);
the "flat/directional-shadow paths" claim is corrected to the one
actual caller, WorldScenePassExecutor.DrawFlatTerrain (a directional-
shadow receiver selects its pipeline inside the SAME DrawRhi call,
not through a second caller); the cathedral order-trace token gains
the LOD side/index (":LC<lb>/<side>:<idx>"); T2's vacuous "no
TERRAIN event" assertion in RetailFrameWalkTests is replaced by a
comment pointing at the F4 driver-level pins; and the stale
"Confirmed OH5 defect" row in oh1-construction-landscape-contract.md
is retired with "FIXED by S3 chunk 3 (commit 671eb3ad4 + fix round
1)".

App hermetic lane: 6,795/6,795 (up from 671eb3ad4's 6,786 baseline -
net +9 tests: 3 F1 slot-key tests, 2 F4a/b driver RunFrame pins, 3
TerrainDrawDiagnosticsController walk-frame tests, plus the T4->F4c
rewrite and the RetailPViewPassExecutorTests split are net neutral).
InstalledDat lane: 241 passed, the same 3 accepted failures (2
pre-existing #383 layout fixture-drift tests, 1 TowerAscent
Status=KnownFailure) - unchanged from baseline. Core Vfx tests:
109/109 (108 baseline + 1 new HasRenderableEmittersInCell lifecycle
pin mirroring CopyRenderableEmittersInCell's own add/move/remove
test).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 09:57:34 +02:00
Erik
eb80c80eb6 docs(render): S3 chunk 3 fix round 1 — slot key, cross-block deferred batching, per-frame diagnostic, LandCell pins
Three-lens review of 671eb3ad: the terrain slot map is keyed by the DAT id
(0xXXYYFFFF) while the walk passes 0xXXYY0000 (no terrain drawn); the
adjacent-same-landblock batching can never merge because retail's own
DrawSortCell interposes a particle turn after every land cell; the timing
diagnostic moved inside the per-batch loop. Round 1 of two.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 08:58:43 +02:00
Erik
bbe41d5993 docs(render): S3 chunk 1 contract — transcript kinds, fixtures, print-only emitter
Runs after chunk 3 (LC/SC need per-cell terrain). One print-only flag,
the FW0 parser extended for the OH kinds, the five captures as fixtures
in the existing still-frame conformance harness, and an offline signature
diff as the transcript gate. No validator, no canonical format.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:49:56 +02:00
Erik
542b049b15 docs(render): import the five retail capture transcripts as S3 fixtures
Walk (5 frames with LC/SC), parts (3) and alpha-depth (5) per pose from
the paired 2013 client, plus the recon log and the owner's retail
screenshot of the cathedral leak. logs/ is ignored; these copies are the
durable record S3 chunk 1's parser consumes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:47:33 +02:00
Erik
51d5323208 docs(render): record foundry-deep; the retail capture session is complete
All four kit poses plus the extra cathedral-leak pose have walk (with
LC/SC), parts and alpha-depth transcripts from the paired 2013 client.
S3 chunk 1 is unblocked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:43:20 +02:00
Erik
09db7f943b docs(render): terrace-edge record — the vista building draws every complete frame, set identical to FW0
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:40:00 +02:00
Erik
6e1042b568 docs(render): record the terrace-edge retail capture (S3 §6b)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:39:37 +02:00
Erik
c36488dfb1 docs(render): record the holtburg-doorway-still retail capture (S3 §6b)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:36:54 +02:00
Erik
4bc9c796b4 docs(render): S3 chunk 4 contract draft — the deletions
Retail CPU-clips only portal polygons, punch fans and exit seals
(polyClipFinish); ordinary meshes, shells, terrain, particles and the
weather are never view-clipped. Inventory at edb32783: keep the seal and
punch clips (with equivalence pins), delete the per-slice weather loop,
the sky-post-scene particle clip slot, the dead cell-shell clip routing,
the slot-list shape of the part classifier, and the seven
ProbeCathedral* discriminators with their launch-option rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:31:25 +02:00
Erik
edb3278370 docs(render): chunk 3 R5 — the second sky pass is retail's weather pass
GameSky::Draw 0x00506ff0 arg2==1 is the weather pass gated on
SmartBox::is_player_outside 0x00451e80, already modeled by RenderWeather;
chunk 3 only has to keep it after the last land cell.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:29:55 +02:00
Erik
296bfd8d48 docs(render): S3 chunk 3 contract -- the landscape interleave
Verified against the two cathedral transcripts and the decomp: DrawBlock
0x005a17c0 draws LandCell then SortCell per land cell (alwaysDrawObjects=1,
flush=0.75 makes the per-cell alpha flush inert); the ported ring order,
closest-cell table and ring-to-LOD mapping reproduce every captured LC/SC
sequence at both poses with zero mismatches once LOD cell ids are read as
x*8+y+1 in LOD coordinates; one land cell's terrain is arithmetic on the
existing cell-major 384-index landblock mesh. The interleave is expected to
remove the Holtburg terrain-punch leak by draw order alone (S4 hypothesis);
LScape::draw draws the sky twice (before and after the blocks).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:28:10 +02:00
Erik
fa217bc6c7 docs(render): record the retail land-cell draw order from the cathedral captures (S3 chunk 3 oracle)
Frame 2 of both cathedral transcripts: one LS, then LC/SC in strict
per-cell alternation, blocks far-to-near with the diagonal interleave
from the Chebyshev-10 ring inward, far blocks at one cell per block, and
SC for all 64 cells of a near block while LC fires only for in-view
cells. This is the interleave chunk 3 ports and the transcript pins.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:18:36 +02:00
Erik
5c64679b11 docs(render): S3 chunk 2 contract -- exact PView state
Decomp-verified during the capture session: PView::DrawCells 0x005a4840
keeps the landscape flush, stamp advance, gated Z clear and exit seals
inside outside_view.view_count > 0; the clear is gated by the persistent
D3DPolyRender::portalsDrawnCount (0x008719b4), which only exit seals feed
in the building path (pass 1 punches pass TRUE; forceClear is never
written). acdream's unconditional clear/seals and the _skyDrawnThisFrame
proxy are the divergence chunk 2 retires. The captures' second PView is
outdoor_pview's look-in flood (RenderDeviceD3D::Init 0x0059efb0).

Also: the cathedral floating-stairs scene row now expects retail's
exterior-slab leak (retail axiom), the retail screenshot is saved at
logs/oh-capture/cathedral-leak.retail.png, and the S3 ledger row moves to
chunk 2 in progress.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:14:39 +02:00
Erik
c1b24ec0c0 docs(render): record the cathedral-arrival retail capture (S3 §6b)
Walk (5 frames), parts (3) and alpha-depth (5) transcripts captured from
the paired 2013 client at the owner's cathedral-arrival pose. The live DI
was f4180108 (stair chamber), not the f4180106 the kit README expected
from FW0's still; recorded as-is. A second PView (009d4530, ov=0) draws
f4180112-14 each frame with no DI of its own -- flagged for chunk 2.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:00:24 +02:00
Erik
cebc9f6af9 docs(render): cathedral-leak pose — owner's retail screenshot described beside the transcript
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 06:54:55 +02:00
Erik
9c0709d4b7 docs(render): S3 packet — capture session progress, cathedral-leak retail oracle
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 06:52:49 +02:00
Erik
b62dbf035c tools(walk-oracle): capture the per-land-cell draw order (LC/SC lines) for S3 chunk 3
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 06:31:54 +02:00
Erik
924652626b docs(render): OVERHAUL ledger — S2 chunk 6 landed, haze verified, handoff addendum
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 06:28:39 +02:00
Erik
2d20ee917b checkpoint(render): portal-haze root cause — emitters need own-cell membership (S2 chunk 6 contract)
Instrumented arrivals show the Hidden-state cloud's emitters spawn in the
arrival cell, become view-eligible when the world appears, and are never
drawn: the walk draws an owner's emitters only through the owner's registry
rows, and a hidden owner's shadow is suspended. Retail's
CPhysicsObj::add_particle_shadow_to_cell (0x00514a70) gives an emitter one
shadow in its own cell, drawn at that cell's turn regardless of the parent's
hidden state. S2 chunk 6 (packet §10) ports that: per-cell emitter draw
membership, owner-union particle path deleted. Route timing fixed so the
arrival screenshots anchor on the reveal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 05:49:45 +02:00
Erik
4b0b29e4cd docs(render): AD-117 item 1 retired — the render-only destination-cell move is retail's own zero-sphere find_cell_list
CObjCell::find_cell_list (0x0052b4e0) with num_sphere == 0 adds only the
current cell — interior CELLARRAY::add_cell at 0x0052b563, outdoor
CLandCell::add_all_outside_cells (0x00533630, the arg2 <= 0 branch:
adjust_to_outside + gid_to_lcoord + add_outside_cell) — and skips the
portal transit walk (`arg4 != 0 && arg2 != 0` at 0x0052b576). So a shapeless
object's transition cell array is its destination cell alone, exactly what
RefreshPositionRows publishes. Not an adaptation; the register row keeps
its three residual readings, the S3 packet drops the capture request, and
the registry/test comments cite the verified function.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 01:18:50 +02:00
Erik
3597c90446 docs(render): OVERHAUL ledger — connected nine-stop soak PASS on the final S2 build
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 01:13:26 +02:00
Erik
31465a0ece docs(render): OVERHAUL S3 ownership packet (draft, pre-capture)
s3-walk-ownership-map.md: the decomp-verified retail table for the walk
(views, stamps, DrawCells order, DrawBuilding sequence, landscape
far-to-near interleave), the current acdream owners with the S2-era
assessment, the four bounded chunks (transcript emitter blocked on the
owner's capture; exact PView state; landcell interleave; deletions), the
two review lenses, the automated gate, and the three exact traces to ask
for at the capture session. Ledger S3 row flipped to PACKET DRAFTED /
blocked on the capture; the G2 handoff points at it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 01:06:16 +02:00
Erik
78068b2cfe docs(render): OVERHAUL ledger — S2 code-complete, G2 provisional PASS, morning handoff
§13: S2 row records chunk 5 + closeout `c94a1a40`, the review fix batch, the
gate counts, every self-gate run (the chunk-5 FAIL on the Facility wall
panels and the candidate PASS), the membership-probe run and the chunk-4
connected soak; the gate table names the six S2 commits and the revert rule.
The G2 morning handoff gives the owner the candidate build, the checklist,
the PNG paths and the automated-gate summary; G2 stays provisional until the
owner double-checks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 01:03:26 +02:00
Erik
c94a1a407e feat(render): Campaign OVERHAUL S2 chunk 5 + closeout — registry is the only render membership owner
Chunk 5 (consumer cutover): WalkProductionWorldData's per-cell views are
borrowed from ShadowObjectRegistry.GetRetailPartEntriesInCell and resolved
through RenderSceneQuery.TryGetByLocalEntityId; every render-side sweep,
bucket, parent-cell and root-position fallback is deleted (AD-116 for the
one-frame registry→scene window, counted in UnregisteredRenderMembershipCount).
A live entity with visual parts but no collision geometry registers
render-only (LiveEntityCollisionBuilder computes the part array before the
empty-shapes gate).

Closeout fixes found while landing it:
- RefloodOwnerForLandblock forwards the retained part array — a reflood is
  retail's recalc_cross_cells over the SAME CPartArray; without it every owner
  touched by a landblock replacement commit lost its render membership.
- Non-colliding DAT statics register render-only from BOTH publishers
  (LandblockPhysicsPublisher.PublishStaticEntity,
  LandblockPhysicsContentBuilder.RegisterRenderOnlyStatic). The G2 self-gate
  pixel diff caught them vanishing (Facility Hub wall panels): retail floods
  every object regardless of collision (CEnvCell::init_static_objects
  0x0052c350, add_shadows_to_cells 0x00514ae0).
- S2 dual review fix batch (arch + retail lens, lead-verified):
  Suspend clears the retail product (remove_shadows_from_cells 0x00511230 is
  one transaction); AttachChild/DetachChild advance the mutation revision so
  a prepared SetPosition cannot clobber a child's rows; an attached child
  never floods on its own re-registration; RemoveLandblock and the non-rooted
  RetireOwnerFromLandblock prune retail rows (render-only statics end with
  their landblock); a render-only owner's no-cell-array commit republishes at
  its destination cell (AD-117); an empty non-null part array is treated as
  null; per-move closures/LINQ replaced by index loops; EnvCell shells stay
  out of the scene's LocalEntityId index (payload-less records); the index
  predicate compares the id; the dead per-cell scene indices are deleted.

Register: AD-116 (chunk 5), AD-117 (four residual Contract A/B readings).
Evidence: s2-membership-ownership-map.md §8 (chunk 5) and §9 (closeout).

Gates (Release): Core 4,984/4,984; Content 214/214; Runtime 1,884/1,884;
App hermetic lane 6,760/6,760; App InstalledDat lane 217 pass / 1 skip /
2 pre-existing #383 layout-fixture failures; App Windows lane 1/1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 01:01:47 +02:00
Erik
75ea269d35 feat(physics): S2 chunk 4 - movement publishes from the transition's cells; children inherit at registration
Movement: CommitSetPosition's RefreshPositionRows/ReplacePositionRows and
the staged apply publish the retail render product from the exact cell
list collision just used, the transition's cell_array retail feeds
add_shadows_to_cells in CPhysicsObj::SetPositionInternal @0x00515330
(pseudo-C 283526-283539); the separate move-path bbox recompute is deleted.
calc_cross_cells @0x00515230 stays the distinct full-recompute path
(PhysicsShadowCommitAction.Recalculate).

Children (Contract B recursion): ShadowObjectRegistry.AttachChild/DetachChild
give an attached object the root's current cells as part entries only,
republished whenever the root's array changes, detached at withdrawal and
cascaded from the root's Deregister; nested attachment resolves to the root
with a bounded, cycle-safe chain. EquippedChildRenderController attaches at
realization (FromSetupRenderParts over the child's Setup) and detaches at
its single removal funnel. WalkProductionWorldData's dynamic sweep reads
TryGetRetailCellArray directly; the 64-hop parent-chain walk and its
FindParentLocalId plumbing are deleted. CollisionWorldState.Clear now
also clears the retail products.

Gates (implementer's isolated worktree at identical content): Release
build 0/0; Core 4,970/4,970; App hermetic 6,761/6,761; targeted
walk/child/live-entity/placement/comparator 166/166; Runtime 1,884/1,884.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 23:37:34 +02:00
Erik
afbd241016 feat(physics): S2 chunk 3 - one flood per registration feeds collision rows and render entries
ShadowObjectRegistry computes the CELLARRAY once per registration under
Contract A (cylsphere route from the Setup's collision cylspheres, else the
bbox route over the whole visual part array) and derives BOTH products from
it: the collision rows (_entityToCells/_cells, CShadowObj per cell via
add_shadows_to_cells @0x00514ae0) and the per-part render entries
(AddPartsShadow @0x00517e40). The second, independent collision flood is
gone. A caller that supplies no part array floods from its collision shapes
exactly as before, so every legacy expectation holds byte-for-byte.

The staged SetPosition pipeline now carries the retail part array, cell
array, route, and entries through TryCaptureOwnerState/InstallOwnerState
and publishes them beside the collision cell replacements, honoring the
keep-when-empty rule (SetPositionInternal num_cells gate, pc:283540) for
both products together; two new tests pin a cross-cell move and the
keep-when-empty case. Behavior change, retail-exact: an object with
decorative non-BSP parts now has its collision shapes registered in every
cell those parts reach (pinned by a two-cell fixture); all-BSP objects are
unchanged. Movement paths still take collision cells from the transition
and recompute the retail product separately; chunk 4 unifies them on the
transition's array as retail does. No particle emitter reaches this registry.

Gates (implementer's isolated worktree at identical content): Release
build 0/0; Core 4,961/4,961; App hermetic 6,760/6,760; collision/InstalledDat
fixtures 63/63; Runtime 1,884/1,884; Content 213/213.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 23:02:47 +02:00
Erik
707d2803a4 feat(render): S2 chunk 2 - render statics borrow the registry's retail cell array
WalkProductionWorldData no longer floods; its indoor and outdoor static
sweeps read ShadowObjectRegistry.TryGetRetailCellArray (retail's
calc_cross_cells_static @0x00515160 -> AddPartsShadow @0x00517e40
CELLARRAY, computed once at registration). The App-owned render flood
(ResolveStaticRenderCells, its fingerprint cache, the primitive-Setup
special case) and Core's ComputeStaticRenderCells are deleted. The one
remaining fallback, an entity the physics publisher has not registered yet
while the projection journal already published it, buckets to the authored
parent cell and is counted per frame (UnregisteredStaticRenderFallbackCount)
for chunk 5 to judge on the connected route.

The Facility stair pin now registers at the projection's own entity id
(the old pure-function test never carried identity) and reads the retail
array; the installed-DAT comparator compares retail against collision.

Gates (run in the implementer's isolated worktree at identical content):
Release build 0/0; Core Physics 2,202/2,202; App hermetic 6,760/6,760 (the
two added WalkProductionWorldData tests); installed-DAT walk/flood/stair
family 24/24; Runtime 1,884/1,884.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 22:31:46 +02:00
Erik
5a2792d689 feat(physics): S2 chunk 1b - callers supply the visual part array; installed-DAT comparator
Every production registration now hands ShadowObjectRegistry the object's
whole visual part array beside its collision dispatch: static publication
(App LandblockPhysicsPublisher and the headless Content twin) through
ShadowShapeBuilder.FromStaticRenderParts, live entities (Runtime
LiveEntityCollisionBuilder) through the new FromSetupRenderParts, which walks
every Setup part with the same physics-sphere-else-drawing-sphere and
part-box rule from the PhysicsDataCache Runtime already reaches. Nothing
consumes the retail products yet; the App hermetic lane still passes
6,758/6,758.

The Lane=InstalledDat comparator registers five real fixtures through the
real publication inputs and prints retail CELLARRAY, collision cells, and
the old render cells side by side: Facility Hub stair Setup 0x02000623
(7 cells incl. 0x8A02015F/015E), cathedral ramp 0x020009A2 (3 cells, the
genuine multi-part case), the #334 Neftet formation (25 cells), and a
landblock-edge crosser (6 cells, 2 in the neighbor block). All three
answers agree for BSP-bearing objects, as the shared primitive predicts;
the divergence chunk 3 expects appears only for decorative non-BSP parts.

Core Physics 2,202/2,202; Runtime 1,884/1,884; App hermetic 6,758/6,758;
comparator + stair pin 5/5; solution Release build 0 warnings / 0 errors.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 21:52:35 +02:00
Erik
e7ad25a705 docs(render): S2 plan packet - membership ownership map and revised chunks
Read-only trace of every production owner of cell membership (render and
physics), the retail transaction they must collapse into (Contracts A/B),
and the five S2 chunks re-cut on that evidence: the two static floods are
one CellTransit walk with different part inputs; retail uses the whole
part array once and registers every part in every array cell; children
inherit at registration; topology consolidation moves to S3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 20:57:04 +02:00
Erik
acf172469e feat(content): S1 exact CellStruct surface-index construction, recipe 8
Campaign OVERHAUL S1 chunk A. Retail's D3DPolyRender::ConstructMesh
@0x0059DFA0 is ported as one pure Core descriptor plus the Content
extraction that consumes it:

- side candidates come only from sides_type (0/1/2); NoPos/NoNeg mean
  UV-array absence only and never suppress a side (CPolygon::UnPack
  @0x00538650);
- ST_DOUBLE's second copy is reversed with a negative normal; ST_BOTH's
  negative side has a negative normal and forward fan order (reverse is on
  the copy ordinal, not the side ordinal);
- an absent UV-index array is UV index 0 (ConstructMesh @0x0059E691
  xor ebx,ebx, arbitrated on the PDB-paired binary); copyVert @0x0059C080
  zeroes coordinates only for a negative or out-of-range index or a vertex
  without UVs, never by clamping to slot 0;
- the subset owner is the source surface-array index, emitted in ascending
  slot order with retail's per-slot mask (2 > 8 > 4 precedence, positive
  surface OR on signed stippling > 0);
- built-EnvCell admission is (Surface.Type & (BASE1_IMAGE|BASE1_CLIPMAP))
  != 0 after surface resolution (DrawEnvCell @0x0059F170 -> DrawMesh
  @0x0059D4A0 arg4=1); untextured slots are constructed but not emitted;
- cell batches carry SourceSurfaceIndex, RetailSurfaceMask, RawSurfaceType,
  IsCellShell, and fixed clockwise raster cull (RenderMeshSubset
  @0x0059CA10); authored sides_type is no longer stored as GPU cull.

Prepared-mesh serializer gains the four fields; bake recipe 7 -> 8 with a
FullRebuild migration; pak format stays 2 (pinned). Ordinary GfxObj
extraction is unchanged. AP-234's register row and CellMesh unification
land in chunk B.

Core: 32 descriptor tests. Content: 170/170. Bake: 18/18. Launcher.Core:
365/365 (Lane!=Linux). Solution Release build 0 warnings / 0 errors.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 18:40:33 +02:00
Erik
13fc7d8349 docs(render): rescope Campaign OVERHAUL to five production slices
v2 replaces the twelve-stage plan. OH1's evidence-grammar stage is parked
on quarantine/oh1-evidence-grammar-2026-09-02; the committed research
contracts stay binding. Stage mapping: OH2 -> S1, OH3 -> S2, OH4+OH5 -> S3,
OH6+OH7 -> S4, OH8-OH11 -> S5. Adds the working model (lead in the loop,
bounded chunks, time-box), the single retail capture session before S3,
and per-slice minimal evidence products. Carries the InitCell inflag
prose correction in the flood appendix and marks the T3 handoff superseded.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 17:50:25 +02:00
Erik
5d907ae9ad docs(render): define OH1 retail world contract 2026-09-01 19:33:15 +02:00
Erik
5cd4fd2c71 docs(render): define OH2 CellStruct contract 2026-09-01 19:11:32 +02:00
Erik
eaea877662 docs(render): clean OH0 baseline formatting 2026-09-01 18:06:21 +02:00
Erik
fba07e7712 docs(render): establish Campaign OVERHAUL baseline 2026-09-01 18:05:40 +02:00
Erik
c287db8651 docs: full session handover - cathedral state, open through-wall defect
Owner-requested handover for a successor session: worktree/branch/HEAD,
tonight''s ledger (falls + rain + torch fixes user-verified; slices 4+5
reverted; the seven-report corpus), the OPEN through-wall remote-player
defect with both attempted fixes and the post-fix [dyn-route] evidence
(the DynamicLast root-flood gate works; the remaining draw path is the
legacy look-in machinery), the leading punch-depth hypothesis marked
unverified, the retail-shaped fix direction, the remaining queue, all
probe families, and the binding lessons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 22:17:30 +02:00
Erik
6d990d38b3 docs: the cathedral seven-report synthesis + adjudicated FW4 plan
Files the independent review corpus (fable/opus/external + audit,
DAT-format, F418 DAT-geometry, retail decomp reconstruction) and the
synthesis that adjudicates their disagreements: retail roots INTERIOR
at the posed terrace defect pose with a ONE-CELL flood and ov=1 (the
free captures rooted outdoor - both were real); the falls are outdoor
landblock emitters 0xCF418000-13; the "thin seam bands" claim is
DAT-refuted (full-size exits; the seam is a coincident double
exit-to-outside plane at y=48); clear+seals live inside retail''s ov>0
block with a one-frame seal latch. Carries the instrument-first plan
(falls-owner trace, depth checkpoints, pose conformance) and the
decomp-pinned repair sequence toward FW4''s single-owner goal.

Also sweeps in the probe scaffolding slice 5''s path-limited add had
missed (RenderingDiagnostics field + the launch-options row wording).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 20:52:14 +02:00
Erik
5f18ba5507 test(render) Campaign FW1: the portal-gate microscope + its verdict
Per-portal dump for the street fixture (archived beside the fixtures):
retail punched exactly the buildings whose portal polygons MY camera
model clips to NOTHING (001a/0022: clipN=0, doorways ~75 deg off my
north-facing axis) and skipped the ones mine puts on-screen (001e/0026:
clipN=4-6) - a clean inversion isolating the final look-in delta to
either the camera FACING decode (re-check the terrace-edge fixture
against its known vista direction; the motion sweep's 20-degree
residual is now suspect) or a portal-pass clip mechanism that does not
gate on the active view (cdb ConstructView(CBldPortal) trace next
retail session). All layers above are retail-pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:58:09 +02:00
Erik
a6b37c0aac research(render) Campaign FW1: the ten pose-stamped oracle fixtures
Second capture round (owner-driven): every frame carries the camera
pose from Render::viewer_pos, closing the FW0 pose gap. The
holtburg-transitions fixture records a full out-and-back doorway
crossing with all four root flips. README documents the P-line format
and the marker-timing caveat that gates still fixtures first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:20:31 +02:00
Erik
368c480bc2 feat(render) Campaign FW1: flood decomp appendix + the visibility math port
The second decomp round (flood bookkeeping/propagation, view-clip
support, landscape visibility) is archived Ghidra-arbitrated - it caught
a load-bearing BN inversion (InsCellTodoList pops NEAREST-first, which
is what makes the draw list far-to-near) and three more traps (the 192m
elided constant, the min/max double positional swap, the copy_view
cross order - the walk doc section 6 is corrected). WalkVisibilityMath
ports get_pt_limit @0x0054b840, get_clip_height @0x0054cff0,
corner/block_plane_check @0x0054b930/@0x0054d060, block_check
@0x0054dc50, and viewconeCheck @0x0054c250 with retail boundary
semantics (strict cull, inclusive partial, touch-out=Outside /
touch-in=EntirelyInside) under 23 focused tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:44:49 +02:00
Erik
a6885aa2f0 research(render) Campaign FW0: the frame-walk pseudocode model + decomp appendix
The distilled port-ready model for FW1: camera-cell rooting, the
invisible-panel primitive (punch far-Z / seal own-depth, byte-verified
constants), the far-to-near landscape walk, the building two-pass portal
machinery with its push/pop asymmetry, the interior flood + DrawCells
passes, the view machinery, and the constants/struct anchor table. The
panel question is resolved: retail DOES draw depth-only portal-polygon
panels via DrawPortalPolyInternal - AD-117 re-invented a real mechanism
at the wrong site. FW0 is complete: oracle fixtures, replay helper,
decomp model.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:28:19 +02:00
Erik
71b11817ad research(render) Campaign FW0: retail walk-oracle harness + 10 live trace fixtures
cdb capture harness (recon offsets + per-pose scripts; .gitignore gains a
negation so the walk-oracle scripts are tracked despite the global *.cdb
ignore) and the FW1 conformance fixtures: terrace center/edge, cathedral
arrival, doorway still, cottage walkout, street porch-cam/outdoor, town
walkabout, foundry entry, foundry deep. Binding findings in the README:
the frame roots at the CAMERA cell; outdoor root draws the #456 far
building every frame and hides it by depth cover, never omission;
interior root gates through the exit-view chain; look-ins punch at the
owning building turn; transitions are one-frame flips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:10:07 +02:00
Erik
f7aa8e0eb7 fix: complete retail parity stability pass
All checks were successful
CI / linux-portable (push) Successful in 3m41s
CI / windows-gate (push) Successful in 6m49s
CI / release (push) Successful in 3m22s
2026-08-28 20:01:39 +02:00
Erik
4e6e9bc9d9 feat(mosstank): add VTank-style automation PoC 2026-08-27 18:57:21 +02:00
Erik
f6fe0f2a4f fix(client): restore retail interaction parity
All checks were successful
CI / linux-portable (push) Successful in 3m27s
CI / windows-gate (push) Successful in 6m42s
CI / release (push) Successful in 2m12s
Harden keyboard and camera routing, inventory and vendor interactions, chat/emotes, relog portal flow, and paperdoll rendering. Add retail research, connected gate coverage, and release-gate validation.
2026-08-26 20:45:11 +02:00
Erik
87e9839561 docs(AS): AS5 REVIEW-CLOSED; AS6 gate script committed — Campaign AS implementation COMPLETE
AS1-AS5 are all review-closed (AS5: 170/170 title strings verified, 6 of
them PE byte-decoded; zero behavioral findings across the campaign after
AS2). The AS6 connected-gate script is written and waits on the owner:
docs/research/2026-08-25-campaign-as-test-script.md, carrying the two
standing rulings (Society row colors are model-only pending AP-110
FontInfo — do not gate on them; the AD-114 animated paperdoll deviation
is expected) and the R3 unconditional-legend retail side-by-side check.

Also: the re-review's grep-hygiene tail — the last "retires AP-109"
phrasing in AppraisalUiController.cs now reads "closes AP-109's title-bar
residual", and the CT plan's stale "this campaign retires AP-109" intent
line (never executed — CT4 narrowed) carries a dated correction.

Branch remains unpushed per the owner's standing instruction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:33:49 +02:00
Erik
bf8f5b70a9 docs(AS): AS4 fix round — oracle corrections (D1), gate carry-note (D2), record fixes (D3); file #442
The AS4 dual-lens review approved the port as exact and required a
docs-only fix round:

- Ground truth §2b row 1: the Society gate is PRESENCE of Int 281
  (AppraisalProfile::InqInt @0x005B3830 returns found/not-found), not
  value!=0; the color rule is same-bit-first (@0x004b49fd/@0x004b4a49/
  @0x004b4a8b) so a multi-bit local player still resolves green on a
  match; the ??? arm precedes the Radiant Blood test.
- Ruling R5 corrected: the row model carries CreatureAppraisalValueStyle
  but ResolveColor is a deliberate no-op until AP-110's FontInfo-list
  residual lands — the Society green/red is model-only and invisible at
  the connected gate; AS6's script must not gate on row colors.
- Ledger: AS4 land 4ade9b04; true full-solution hermetic count is 15,528
  (the AS4 commit body's 15,410 was a mis-report; the review re-ran and
  reconciled 15,483 + 45 new = 15,528).
- #442 filed: pre-existing parallel-load flake in the shadow-caster
  zero-allocation pin, surfaced by the review's full-solution run;
  isolation evidence recorded; unrelated to Campaign AS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 09:58:47 +02:00
Erik
bc48e3216a docs(AS): AS3 REVIEW-CLOSED (APPROVE, no fix round); R3 legend hedge settled at source
The AS3 dual-lens review verified the armor-level trio, sentinel, legend,
and monster-path exclusion at offset level and disproved the R3
BN-flattening theory structurally (legend sits outside the InqCreature
block, pseudo-C line 189962). The ratings adjudication resolved in the
implementer's favor: the pre-AS3 composer already had retail's per-row
gating and spacer discipline. NITs 11/12 + the AP-110 narrowing ride AS4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 09:26:15 +02:00
Erik
0332466fdc docs: Campaign AS plan + AS1 ground-truth synthesis (assess window, player targets)
Owner report 2026-08-25: the examination window on an assessed PLAYER is
missing retail's identity block (gender/heritage, title, PK), the three
per-bodypart armor-level rows, allegiance/faction lines, and the
target-configurable extras. Three-lens research (our pipeline, ACE wire,
named retail decomp) established: the 0x00C9 parse is already complete
(ArmorLevels 0x4000 parsed and dead), everything arrives from ACE, and
the core defect is an element mis-mapping in the Character subview vs
CharExamineUI @0x004AD3C0 (heritage slot fed a raw string prop, title
slot fed AllegianceName, PK slot fed MonarchsName, allegiance slot fed
an invented "Assessment incomplete" literal). Zero Character-subview
tests existed, which is how it survived the Slice 3 gate.

Slices AS2-AS5 (serial, coupled files) + AS6 gate script; ground truth
doc carries the binding line-composition tables, gap ledger G1-G10, and
rulings R1-R8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 08:19:38 +02:00
Erik
025108a8aa fix(CT-GF1): review fix round — literal DrawHere clip shape, empty-clip cull, popup input routing
Applies all 11 items from the Opus dual-lens review of 989f6652 (0
blockers, 7 SHOULD-FIX, 4 NOTE):

- S2: UiElement.DrawSelfAndChildren now pushes the ambient clip right
  after PushAlpha and wraps OnDraw + the children walk +
  OnDrawAfterChildren in ONE block — the literal UIRegion::DrawHere
  @0x0069FA30 shape, which clips an element's OWN DrawSelf too, not
  just its children (UIElement_Text::DrawSelf @0x00467AA0 locks glyph
  blits to its own clipped surface rect; UIRegion::DrawSelf
  @0x0069F1A0 blits per clip rect). Deleted the two now-redundant
  ad-hoc self-clips this supersedes: UiText.DrawText and
  UiField.DrawMultiLine both pushed their own (0,0,Width,Height) —
  exactly what the new ambient clip already provides one level up.
  Kept UiButton.DrawBlockLabel's clip: it clips to LabelBox/ValueBox,
  an authored INNER sub-rect that can be smaller than and offset from
  the button's own full rect — a genuine narrower viewport, not a
  redundant duplicate.
- S3: deleted UiItemList's `ClipsChildren => CellWidth > 0f` override
  — correct under the old opt-in-false default, inverted under the
  new default-true (an unconfigured list would stop clipping instead
  of clipping like everything else).
- S4: pinned the escaped-popup input path end to end. New
  UiAncestorClipTests test mounts a menu inside a short window on a
  real UiRoot, opens it, and proves a click in the escaped popup
  region reaches the menu through UiRoot.PopupHit (a plain top-down
  walk is proven to reject the same point first). UiRoot.WantsMouse
  now also checks PopupHit — it previously only checked Captured/
  HitTestTopDown, so a game action could fire underneath an open
  dropdown's escaped region. OnMouseDown/OnScroll already routed
  through PopupHit first (#374); unchanged.
- S5: strengthened the Titles-divider regression test's positive
  half. The old assertion only checked SOME quad's Y fell in a band —
  vacuously true given other same-band content. Now asserts the
  divider's exact rect (X and Y), then diffs against the same rect
  with the divider hidden (Visible=false) to prove the quad was
  actually attributable to it.
- S1: added UiWindowDrawCaptureSweepTests — Character/Chat/Vendor/
  Options mounted through their real production Bind entry points
  with a non-zero sprite resolver, drawn via RecordingGpuDevice,
  asserting a per-window vertex floor (~40-45% of this session's
  observed baseline: Character 588, Chat 162, Vendor 54, Options 240)
  plus one key sprite id read LIVE off the bound controller/element
  (never hardcoded). Character's key sprite (RetailChromeSprites.
  TopEdge) specifically exercises OnDrawAfterChildren, the exact path
  S2's caution note flagged. Inventory/Paperdoll/social/map-house
  skipped — no single fixture-driven top-level Bind entry point.
- S6: added the CT-GF1 subsection to the campaign plan's ledger
  (989f6652 + this fix round; CT7 re-gate still owed).
- S7: UiRenderContext.PushClipUnbounded now resets to the CANVAS rect
  (0,0,ScreenSize), not null — retail's own popup region is
  SCREEN-clipped (UIElement_Menu::MakePopup spawns a top-level region
  bounded by the screen), not truly unbounded. AD-113 amended.
- N1: UiRoot overrides ClipsChildren => false — the root's own region
  IS the screen (the viewport already scissors it), so this is a
  safety net against a momentarily zero-sized root silently blanking
  the whole UI tree under the new ancestor-clip default.
- N2: added the empty-clip subtree cull (retail's var_24 gate
  @0x0069FB8E) to DrawSelfAndChildren only — DrawOverlays is a wholly
  separate traversal untouched by this change. New test proves a menu
  inside a fully-clipped (zero-width) window still draws its open
  popup via the overlay pass while the main pass draws nothing.
- N3: CT7 script §5 now names the collapsed-toolbar check and the
  four highest-overflow windows (combat/vitals bar, Options
  bottom-button row, map/house page, floaty chat) as explicit
  eyeball items for the re-gate.
- N4: verification below covers both the working tree and the clean
  committed tree.

Decomp anchors: UIRegion::DrawHere @0x0069FA30 (var_24 gate
@0x0069FB8E); UIElement_Text::DrawSelf @0x00467AA0 (self-clip);
UIRegion::DrawSelf @0x0069F1A0; UIElement_Menu::MakePopup (screen-
clipped popup region).

Verification (both runs green, --filter "Lane!=InstalledDat&
Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing&
Lane!=Windows&Lane!=Linux&Lane!=SystemFont&Purpose!=Diagnostic&
Status!=KnownFailure"): full Release solution build green; working
tree 14,900+ tests across every project (one LandblockPresentation
PipelineTests flake reproduced ONLY under full-solution parallel
load, passes standalone and on rerun — unrelated to this change,
streaming domain); InstalledDat lane green (ACDREAM_RUN_INSTALLED_DAT
_TESTS=1, Status!=KnownFailure, 205+34+3+172 App/Content/Bake/Core
tests). Clean committed tree (git stash push -u the uncommitted
owner probe + docs files, rerun, stash pop) reported in the session
summary.

src/AcDream.App/UI/UiRoot.cs carries an unrelated, pre-existing
uncommitted owner probe (ACDREAM_PROBE_UI_HOVER) — staged selectively
(git add -p) so only this commit's own two hunks (ClipsChildren
override, WantsMouse) landed; the probe hunk is untouched and stays
uncommitted, same as before this fix round.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 07:11:42 +02:00