Commit graph

404 commits

Author SHA1 Message Date
Erik
035d7b0148 fix(render) Campaign FW3.2b-1: interior draw order - landscape before cells
The oracle trace order DI|DC|LS is breakpoint-ENTRY order; retail's
actual DRAW order inside PView::DrawCells @0x005a4840 for an interior
root is LScape::draw FIRST (pc:432719, only when exit views survive),
then the depth clear (pc:432731-432732), the exit-portal seals
(pc:432785-432786), THEN the flood's own cells far-to-near. The
driver drew flood cells before the landscape - inverted.

RetailFrameWalk.DrawInside gains the additive
OnInteriorFloodDrawTurn(cells) hook firing after the conditional
landscape turn; the DC EVENT stays at its original point (conformance
untouched - 40/1 InstalledDat green). WalkFrameDriver records the
interior flood at the DC turn and draws it at the new turn:
flush -> ClearInteriorDepth -> flush -> DrawExitSeals -> per-cell
shell-then-contents. Building look-in floods still draw immediately at
their building turn (retail's reentrant DrawCells with no clear/seal).
Two new leaf members map to IWorldPassScope.ClearInteriorDepth and the
seal-fan machinery at FW3.2b-2. Reconciliation note recorded: the
driver clears unconditionally for interior roots while production
stages the clear on OutsideViewSlices>0 - observably equivalent at
ov=0, awaiting a firmer decomp read of the clear's gate.

Suites: full Release build 0 warnings; Walk lane 201/1 skip;
hermetic 6,753/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 14:34:45 +02:00
Erik
03f63686cc feat(render) Campaign FW3.2b-1: the walk frame driver
WalkFrameDriver executes one full static-content frame from the walk's
turns so GPU command-buffer order equals retail's walk order. One rule
does the interleaving: the accumulated OrderedDrawStream flushes
through SubmitOrderedStream immediately before EVERY non-stream draw
(sky, terrain slice, cell shell, punch fan, alpha barrier).

Turn script, all decomp-cited and two of them corrected in review:
- Interior flood: per cell IN FLOOD ORDER, shell first then contents
  (PView::DrawCells @0x005a4840: DrawEnvCell @0x005a4abe precedes
  DrawObjCellForDummies @0x005a4b0d).
- Landscape: sky once, terrain per active slice, then blocks
  far-to-near; per cell the building turn precedes the cell's outdoor
  statics (DrawSortCell @0x0059f140).
- Building (DrawBuilding @0x0059f2a0): the BLD probe event stays at
  entry, but the ENTIRE body - alpha barrier, portal passes, shell -
  sits inside retail's gfxobj[deg_level]!=0 gate @0x0059f2d3, and the
  order is FlushAlphaList @0x0059f30b -> the two-pass punch/look-in
  walk -> THEN the shell draw @0x0059f345. The driver review caught
  both the missing gate and a shell-before-punch inversion; fixed
  with the addresses cited.

Walk seam: three additive default-implemented IWalkEventSink hooks
(OnLandscapeCellTurn / OnBuildingTurn / OnBuildingShellTurn /
OnPunchGeometry) - every existing sink and all FW1 conformance
fixtures unchanged. WalkLandBlock gains LandblockId for the cell-id
encoding. Leaf draws go through IWalkFrameLeafRenderer so FW3.2b-2
wires the real renderers and the referee suite runs on fakes +
RecordingGpuDevice.

Flagged for FW3.2b-2/FW4 adjudication (documented in code):
FlushFartherThan(building distance) vs retail flush-all
FlushAlphaList(0f); terrain-before-statics within the landscape turn.

Suites: full Release build 0 warnings; Walk lane 200/1 skip;
InstalledDat Walk conformance 40/1 untouched; hermetic 6,752/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 14:23:17 +02:00
Erik
81c6531727 feat(render) Campaign FW3.2a: the walk-to-draw population layer
The piece that turns walk-visited static content into draws, with no
production frame wiring (FW3.2b roots the frame):

- TryClassifyBatch: ONE shared per-batch classify core (the #426
  untextured gate, #188 opacity promotion, texture resolve, foliage
  classification, in the exact original order) extracted from
  ClassifyBatches; the classic and packed classifiers now call it -
  behavior-identical, proven by the full hermetic + InstalledDat +
  Core Wb suites.
- ClassifyEntityForWalk / WalkClassifiedBatch: the per-entity seam
  yielding per-batch keys + instance data WITHOUT InstanceGroup
  bucketing, plus the per-part selection data (picking stays alive on
  the walk path - the survey's unlisted-consumer fix).
- WalkStaticStreamPopulator: per-entity walk-ordered opaque appends
  (under depth Less, opaque order is pixel-relevant only for coplanar
  surfaces, which retail resolves first-drawn-wins in ITS order -
  never material-grouped), translucent instances to the SAME
  RetailAlphaQueue via SubmitWalkAlphaInstance (identical viewer
  distances; walk-order submission improves retail's tie fidelity),
  selection parts published per entity.
- SubmitOrderedStream now owns _orderedDrawCullModes, retiring the
  FW2-recorded alpha-scope interleaving constraint;
  DrawIndirectRangeRhi takes an optional cull array (all existing
  call sites unchanged). The referee test was verified to FAIL
  against the old shared-scratch behavior.
- WalkDrawStage.OutdoorStatic added for the landscape turn.

Suites: full Release build 0 warnings; Walk lane 195/1 skip;
hermetic 6,747/0 (the two failures the implementation round reported
were transient - both pass in isolation and in the full run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 13:52:23 +02:00
Erik
b10ad662b0 feat(render) Campaign FW3.1: production walk world data behind the seam
The retail frame walk's world model now materializes from production
landblock-build owners through the legal IDatReaderWriter seam, with
zero frame wiring (FW3.2 roots the frame):

- WalkCellFactory: WalkCell built in the SAME pass as LoadedCell
  (EnvCellLandblockBuild.BuildVisibilityCell) from the raw portal
  Flags/polygons/planes/stab lists already parsed there; stored as
  LoadedCell.Walk, committed atomically with the cell. The
  fixture-pinned decodes (inverse-0x2 portal side, 0xFFFF->0xFFFFFFFF
  exit widening) live here.
- WalkBuildingFactory + WalkBuildingRegistry: the production
  WalkBuilding build (drawing BSP with PORT nodes, degrade ladder,
  portal sides/stab lists, sort center, model frame) from the SAME
  LandBlockInfo the streaming build already fetches, under the
  factory's existing DAT lock - closing the gap where BuildingLoader
  drops every walk field at load.
- WalkLandscapeAssembler: the retail 51x51 viewer-centred grid
  (mid_radius 25) fed incrementally from landblock publish/retire;
  per-block z-slab (heightTable[max]+200 / [min]-1) computed
  worker-side in LandblockBuildFactory from the heights already in
  hand. O(1) SetViewer on same-block frames.
- WalkProductionFrameContext: the walk's frame contexts over
  CellVisibility + WalkBuildingRegistry with a generic
  inverse-view-projection ray caster (rays feed cross products only -
  scale-free) and the znear=0.1 CY plane.
- Publication: LandblockRenderPublisher owns both walk registries,
  publishing in the same AdvanceCompleteOne step as BuildingRegistry
  and retiring in RemoveBuildingRegistry - same commit, same
  retirement, no new ticket stage.

Conformance: ALL TEN oracle fixtures replay identically through the
PRODUCTION builders (WalkProductionWorldConformanceTests) - same
signatures as the test adapter, first run. Known gap documented for
FW3.2: far-tier landblocks carry no EnvCell transaction, so their
z-slab never reaches the assembler.

Suites: full Release build 0 warnings; Walk lane 186/1 skip;
hermetic 6,738/0 (+24); RuntimeDatAccessArchitectureTests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 13:19:22 +02:00
Erik
e65644cb33 feat(render) Campaign FW2: OrderedDrawStream + walk-order submitter
The walk-order submission layer over the existing RHI (plan section FW2):

- OrderedDrawStream: append-only walk-ordered draw commands
  (GroupKey + transform + per-instance data + WalkDrawStage + cell
  provenance), struct-of-arrays with one lockstep Reset (#193 shape).
  The PortalPunch stage exists but has no FW2 submission path - the
  submitter throws on it; punch emission lands with FW3 wiring.
- WbDrawDispatcher.OrderedStream partial: per-instance-first emission
  (the deferred-alpha shape - command i owns instance i, walk order
  survives into the indirect array), each SSBO section written once,
  then one DrawIndirectRangeRhi call per maximal merge run. Runs are
  built by pure-CPU BuildOrderedMergeRuns and may never span a stage,
  pipeline-bucket, or cull boundary; ValidateMergeRun re-checks every
  emitted run and throws (the campaign fail-loud rule). Nothing is
  sorted, reordered, or dropped: N commands in, N indirect commands
  out, covered exactly once.
- WorldDepthContract: retail world depth verified verbatim from the
  decomp - Render::zfuncVal @0x00820e1c = 0x2, SetDepthBufferMode
  @0x005a2d10 writes the enum directly as D3DRS_ZFUNC so the value IS
  D3DCMP_LESS, applied by the surface-state applier @0x0059c80a with
  Z-write toggled by blend; the LESSEQUAL sites are GameSky::Draw-local.
  Seven world pipeline sites now cite the named constant (no value
  changes).
- Plan updated: FW1 status block + gate amendment (the ten pose-stamped
  retail traces supersede re-expressing the old-builder replay
  fixtures; those retire with the old builder at FW4 and their
  scenario classes re-verify at the FW3/FW4 connected gates).

Known FW2 scope notes recorded in the code: the building-detail
overlay replay is production wiring (FW3); the _drawCullModes scratch
may not interleave with a mid-flight RetailAlphaQueue scope (FW3
sequencing constraint). The pixel A/B equivalence proof rides FW3's
cutover toggle where a walk-driven scene first exists.

Suites: full Release build 0 warnings; Walk lane 154/1 skip;
hermetic 6,714/0 (+27 new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 12:31:31 +02:00
Erik
77f5342b62 feat(render) Campaign FW1: doorway-still conformant - deg_mul is dynamic
Render::deg_mul is auto-tuned by frame load and SWUNG between oracle
captures: doorway-still ran right after the heavy terrace-edge capture
with the multiplier depressed (mul <= 0 puts thresholds at/below ideal,
so 001e/0026/002f select the portless level 1 - retail's zero look-in
floods, reproduced exactly at mul = 0), while every other fixture pins
~ +0.99 (thresholds at max). The recon session's "-0.99" live dump was
real - taken under the same cdb load. RetailFrameWalk now exposes the
multiplier; the doorway test pins 0, the rest use the default.

foundry-entry: frames 1-66 reproduce exactly; the F67-F79 standing
segment diverges ONLY in building 0036's intra-building DC order
(retail 116,118,11d vs replay 11d,116,118). The BSP-traversal microscope
pins the flip to 0036's ROOT plane (N=(0,0,-1) D=2.8): replay eye z
2.33 (d=+0.47, NEG-first) vs retail behaving as d<0 (POS-first) - a
structural ~0.5 m frame question (positionPush(2)/part-scale), only
adjudicable live. Turnkey probe:
tools/walk-oracle/fw1-f67-viewpoint-probe.cdb.

Fixture status: nine of ten fully conformant; foundry-entry exact
through F66 with the 13-frame order segment parked on the probe.
Suites: Walk 127/1 skip; hermetic 6,687/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 11:49:27 +02:00
Erik
c1a029edf9 feat(render) Campaign FW1: NINE of ten fixtures conformant - the degrade arm
The moving-fixture divergence was the building degrade ladder:
GfxObjDegradeInfo::get_degrade @0x0051e4b0 (Ghidra-verified - BN's
FPU-flag pseudo-C misread BOTH arm selection and one formula) slides
each level's threshold from ideal toward MAX as the multiplier
approaches 1, and the live client runs the positive arm at ~0.99, so
level 0's portal-bearing BSP survives to ~max_dist (48 for the Holtburg
cottages), not ideal (24). The recon note's "deg_mul = -0.99" was a
sign misread; the negative arm's threshold slides toward MIN and
contradicts the fixtures from both directions. With the two-arm port,
holtburg-walkout, holtburg-transitions, and holtburg-walkabout pass
every pairable frame - the walkout-F2 microscope's prediction
(103,100 | 100x3 | 124 through the cottage exit views) landed exactly.

Also this round, falsified and reverted: a BN-driven swap of the
portal walker's negative/in-plane arms (Ghidra shows side 1 = negative
EMITS, side 2 = in-plane does not - the original port was correct; the
swap broke four fixtures). The walker docs and unit tests now pin the
Ghidra-verified truth table.

Parked with findings: foundry-entry F67 (right flood set, one
plane-side classification at the +/-eps boundary orders 11d before
116/118) and doorway-still (retail shows zero floods at a pose one
meter from walkout-F2's flooding pose; multi-portal clip boundary).

Suites: Walk 124/3 skips; hermetic 6,687/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 11:34:45 +02:00
Erik
17ee543cb1 test(render) Campaign FW1: moving-tail state + falsified hypotheses recorded
The moving driver gains adjacent-pose tolerance (the marker-lag capture
artifact) and stays parked: the four moving fixtures diverge at
punch-edge frames under BOTH adjacent poses. FALSIFIED this round and
reverted: the raw-decode + flipped-gates convention (it broke three
still fixtures - the inverted-decode convention stands, six still
fixtures frame-exact). Remaining instruments in the Skip note: znear,
per-view punch ordering inside DrawMesh, exit-view precision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 11:12:22 +02:00
Erik
66f9e0d459 feat(render) Campaign FW1: SIX OF TEN FIXTURES FULLY CONFORMANT
Three final pins complete the still-fixture set: (1) the CELL portal
side decode is the INVERSE of the 0x2 bit (uniform with the building
convention; the doorway-still flood proved it - ov=2 n=3 exact, and
foundry-deep stays green); (2) interior frames key the landscape order
off the OUTSIDE-projected landcell (get_outside_cell_id - derived from
the camera origin); (3) the outdoor pview has draw_landscape=FALSE so
look-in floods discard exit portals - the ov=0 pattern of every traced
look-in. CONFORMANT: foundry-deep (every frame), doorway-still,
street-outdoor, terrace-center, terrace-edge (the #456 acceptance
pose), cathedral-arrival - full frames identical to retail. The moving
four diverge only at punch-edge frames (walkabout F9, foundry-entry
F67) - pose-timing sensitivity parked in the driver Skip note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 11:07:29 +02:00
Erik
28475d0544 feat(render) Campaign FW1: street fixture retail-exact except flood depth
Three pinned corrections close the outdoor frame: (1) the camera basis
RE-pinned to +Y-forward - the terrace-edge fixture EXTERNAL ground
truth (the ledge faces the F518 vista, east) overrules the walkabout
motion sweep, whose camera was mouse-turned off the run line; (2)
alwaysDrawObjects (.data default 1 @0x00820ed4) drawn into the
DrawSortCell gate - cell contents draw for every cell of an in-view
block; (3) the building portal side decode data-pinned as the INVERSE
of the cell 0x2 bit (the sweep winning arm). Street result: all 13
buildings in retail exact order (a9b3003c in, aab50002 correctly
absent), punches at exactly 001a and 0022 opening the exact first
cells 16e/164. SOLE remaining delta: look-in flood depth (mine 1 cell,
retail 3-4) - the doorway-still fixture adjudicates the interior flood
in isolation next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 11:02:16 +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
e23a589a6b feat(render) Campaign FW1: port the exact retail degrade selection
UpdateViewerDistance @0x0050e030 + get_degrade @0x0051e4b0 ported with
live-pinned globals: distance measured to the part SCALED SORT CENTER,
effective = max(0, dist - s_rDegradeDistance [live 100]), level = first
with effective < IdealDist (the live auto_update_deg_mul<=0 arm), else
the last level. WalkBuildingDegradeLevel carries the full authored
bands; the adapter fills sort centers; the replay context measures to
the transformed sort center. Sweep state after the rule: the level-0
building set is now correct; the residual divergence is the per-portal
side/clip gate (my arms punch a near-complement of retail two) - the
sweep driver carries the next instrument in its Skip note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:55:19 +02:00
Erik
844a6c097d test(render) Campaign FW1: park the sweep driver with the degrade-gate state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:51:34 +02:00
Erik
b3ac5872e9 feat(render) Campaign FW1: the degrade-level BSP gate kills the wrong punches
The offline degrade probe proved the mechanism: every Holtburg building
carries PORT nodes ONLY in its level-0 GfxObj (out to ~24-48 m); every
degraded level has zero. Retail walks the CURRENT degrade level BSP
(part->gfxobj[deg_level]) - that is what limits look-in punches to the
nearest buildings. WalkBuilding gains the degrade ladder +
SelectDrawingBsp (band pick; UpdateViewerDistance hysteresis is a port
TODO), the walk selects per viewer distance, the adapter builds
per-level BSPs, and the stab-list load rule (CLandBlock::init_buildings
@0052fd80: a full-res block loads exactly its buildings portal stab
cells) replaces load-everything in the landscape builder. The sweep now
shows clean rosters with all far-building punches gone; remaining
deltas: the near buildings 001a/0022 (50 m/28 m center distance vs the
48 m band edge - sphere-adjusted distance/hysteresis to port) and the
one ring-1 frustum boundary pair (aab50002/a9b3003c).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:51:05 +02:00
Erik
e5cdd2364e feat(render) Campaign FW1: look-in adjudication - the GetVisible load gate
The per-building join diagnostic PASSES (001a/0022 portal tables lead
exactly to the traced a9b4016x punch cells; every BSP PortalRef indexes
validly), and the eight-arm gate-decode sweep proves NO plane-sign x
side-flag combination reproduces retail. Together they pin the missing
mechanism: CEnvCell::GetVisible gates punches by the LOADED interior
cell set around the player - retail punched only the two buildings
nearest the player cell; the replay loads every interior so geometry
alone over-punches. Next port piece: the interior load radius (the
landcell stab-list pull). Both diagnostics stay in the suite (the sweep
Skip-parked with the verdict).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:43:27 +02:00
Erik
faba9be635 feat(render) Campaign FW1: pin the camera basis; outdoor adjudication state
The moving-fixture sweep pinned the pose decode (quat storage w,x,y,z;
facing = rotated +X; the consistent triple right=-rot(Y), fwd=rot(X),
up=rot(Z) - mean 20.8 deg vs motion, alternatives >=52). With it the
street-outdoor BLD roster and order match retail EXACTLY except one
ring-1 boundary pair (aab50002 extra / a9b3003c missing). Look-in
punches fire at the wrong buildings under BOTH GfxObj plane-winding
conventions (retail punched only 001a and 0022 into the a9b4016x
cells) - the next adjudication targets the PortalRef.PortalIndex join
and the BuildingPortal side-flag decode via a per-building dump. The
driving diff test carries the state in its Skip note; the flip toggle
stays for the next arm.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:40:22 +02:00
Erik
e3f0c6a1a1 feat(render) Campaign FW1: the landscape replay world + first outdoor diff
WalkLandscapeDatBuilder assembles the replay grid with the LIVE-dumped
retail landscape model (LScape mid_radius=25 -> 51x51; the resolution
pyramid observed on the capture client: side_cell_count 8 in the 3x3
core, 4 at ring 2, 2 at rings 3-4, 1 beyond; buildings attach only to
full-res blocks - matching the traces roster), retail z-slabs
(heightTable[max]+200 / [min]-1 per CLandBlock unpack @0052f297), and
camera-block-local coordinates. The replay context gains the building
half (placements, active-view install, building-polygon clip). The
street-outdoor conformance diff now reaches real adjudication: the walk
over-culls six traced buildings (jagged boundary - not a clean frustum
edge) and the look-in floods differ; the driving test is parked Skip
while the visibility map is instrumented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:36:09 +02:00
Erik
e8df809c40 feat(render) Campaign FW1: pin the exact retail projection in the replay context
Live-dumped from the capture client (Render::bw/bh 1024x720,
xinvscale/yinvscale 0.00025, tx 0.127875, ty 0.089875, vdst 0.1330767):
the replay context now uses retail unproject formula verbatim for the
ray caster and the exact frustum (fovY = 2*atan(ty/vdst), aspect =
tx/ty) for projection - no guessed camera constants remain.
foundry-deep conformance stays green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:27:38 +02:00
Erik
6caedf25f9 fix(render) Campaign FW1: keep the conformance DAT adapter behind the seam guard
RuntimeDatAccessArchitectureTests correctly flagged WalkWorldDatAdapter
taking raw DatCollection in production. The adapter is the conformance
harness world builder, so it moves into the test assembly; the FW3
production adapter will consume the content-owner seam. Hermetic suite
back to 6,687/0; adapter + conformance 10/10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:24:51 +02:00
Erik
0f01fb4430 feat(render) Campaign FW1: FIRST TRACE CONFORMANCE GREEN (foundry-deep)
The replay harness reconstructs the camera from a pose-stamped oracle
frame (Frame quaternion w,x,y,z storage order; +Y forward / +Z up;
landblock-local origin) and drives the ported walk over adapter-built
cells (cell transforms from EnvCell.Position now populated). The
foundry-deep fixture - the pure-interior frame shape - reproduces
retail EXACTLY on every complete frame: DI + DC(ov=0, [cell]) with no
landscape, 39/39. Conventions are now pinned by live retail output; the
remaining nine fixtures need the outdoor world build-out (landscape
blocks, terrain z-slabs, building transforms + active-view clip) and
join the same gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:23:39 +02:00
Erik
f289366700 test(render) Campaign FW1: pose-aware oracle trace parser
WalkOracleTrace decodes the P lines of the pose-stamped capture round
(raw IEEE-754 dwords from Render::viewer_pos @0x0081ef00: camera cell,
world origin, the four Frame quaternion components in storage order)
into WalkOraclePose on each frame. Poseless FW0 fixtures parse as
before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:19:45 +02:00
Erik
17c57bbd1d feat(render) Campaign FW1: the DAT adapter + ground-truth roster conformance
WalkWorldDatAdapter builds WalkCell/WalkBuilding from real DATs: cell
portals with the PortalSide flag bit and signed OtherPortalId, portal
polygons + planes via the production formula, stab lists, building
position cells (24 m landcell math), building portals with stab lists,
and the drawing-BSP -> WalkBspNode conversion incl. PORT-node PortalRef
polygons (the previously missing consumers the 2026-08-30 survey named).
Lane=InstalledDat conformance: every BLD id the FW0 oracle traces saw
retail draw (26 buildings over 7 landblocks) resolves in the adapter
roster at its EXACT position cell; the traced look-in cells build with
portals/polygons/stabs; the sanctuary BSP carries portal refs. 9/9.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:16:33 +02:00
Erik
4b401a08ed feat(render) Campaign FW1: the frame-walk composition root
RetailFrameWalk composes the ported machinery into retail frame shapes:
SmartBox::RenderNormalMode rooting (camera-cell low word < 0x100),
PView::DrawInside + the DrawCells event half (interior pview, the
traces pv=009d4a80), LScape::draw (per-view visibility, blocks
far-to-near, per-block cells far-to-near, buildings at their cell turn)
and RenderDeviceD3D::DrawBuilding (BLD at entry before the degrade
check; two-pass BSP portal walk per active view on the outdoor pview,
the traces pv=009d4b08). WalkLandscape ports the block grid +
draw_check_blocks/landcell_check visibility (192 m/24 m pitch,
viewer-relative, union across views, never downgrade). Six composition
tests: outdoor far-to-near emission, degrade-entry event, interior
ov=0/ov=1 shapes, rooting, complete view unwind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:06:09 +02:00
Erik
f05b93b5b5 feat(render) Campaign FW1: port the building look-in machinery
WalkBuildingPortals transcribes the portal-only drawing-BSP walk
(BSPTREE/BSPNODE::build_draw_portals_only @0x00539860/@0x0053c100,
BSPPORTAL::portal_draw_portals_only @0x0053d870 - opposite-child-first
= far-to-near emission, IN_PLANE PORT arm emits nothing),
PView::DrawPortal @0x005a5ab0 (stab add/remove_views around the
look-in, DrawCells on pass-2 success), and the CBldPortal
PView::ConstructView overload @0x005a59a0 (side must EQUAL the authored
portal_side, clip survival, Visible destination, punch on pass 1, flood
recursion on pass 2). Punch surfaces via the pass sink for FW1
conformance; the depth-fan submission itself is FW2. Six tests: BSP
emission order both viewer sides, in-plane suppression, pass-1
punch-no-flood, pass-2 flood + DC event, sidedness rejection, unloaded
destination skip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:01:45 +02:00
Erik
161ffaf4e9 feat(render) Campaign FW1: port the PView interior flood
WalkPView transcribes ConstructView @0x005a57b0, InitCell @0x005a4b70,
InsCellTodoList @0x005a4f50 (nearest-first pop), ClipPortals @0x005a5520,
OtherPortalClip @0x005a5400 (inverted ==0 sidedness), AddViewToPortals
@0x005a52d0 (update_count watermark, duplicate-reach arm), AddToCell
@0x005a4d90, SetOtherSeen @0x005a4e30, FixCellList/AdjustCellPlace/
AdjustDrawList/AdjustCellView @0x005a5250/0x005a5010/0x005a4e90/
0x005a5770, plus set_view and PView::GetClip @0x005a4320 over the
WalkScreenClip chain. Resolves and documents the portal-flag convention
(inflag=1 = the portal FACES the viewer and feeds max_indist; inflag=0 +
armed seen = an opening — the same side condition building look-ins
require), which the PDB names obscure. WalkWorld supplies the cell model
and frame-context seam. Six synthetic-world flood tests pass on first
run: traversal, facing rejection with the exact distance key, exit-view
raising, entry-portal no-ping-pong, chain ordering, unloaded skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:58:28 +02:00
Erik
11ca527fb9 feat(render) Campaign FW1: port the view machinery (xformStart, polyClipFinish, copy_view)
WalkScreenClip ports PrimD3DRender::xformStart @0x0059b990 (homogeneous
viewport coords, y-flip, no divide) and ACRender::polyClipFinish
@0x006b6d00 (w>=cdstW plane then last-to-first edge passes, inside =
side<=0 homogeneous 2D cross, reverse-scan passes with original-winding
restore, <3 early-outs). WalkViews ports the view_type/portal_view_type
data model and Render::copy_view @0x0054dfc0 exactly: in-place divide,
the keep/last/stl/second pruning bookkeeping with all three closing
wrap checks, <3 reject leaving dest untouched, cap 31, pool-base reset
at view_count==0, retail fabs on copy, and edge planes
N=normalize(cross(ray[k+1],ray[k])), d=-dot(N,eye) behind an
IWalkRayCaster seam. Thirteen new tests; Walk namespace 85/85.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:50:03 +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
d1fc4a0986 feat(render) Campaign FW1: port the retail landscape draw-order machinery
LandWalkOrder ports LScape::get_block_order @0x00504c50 (near-to-far ring
list, walked backwards by LScape::draw), CLandBlock::calc_sq_draw_order
@0x0052f4a0 (cell array filled backwards from the closest cell),
LandDefs::get_dir @0x005a9aa0, and the 9-case closest-cell direction
switch. The shared 8-slot ring coefficient tables were byte-decoded from
the PDB-paired binary (block VA 0x0081cc6c.., cell VA 0x0081df88.. -
identical sets); the intra-ring visit order is preserved exactly because
it decides draw order between equidistant blocks and the oracle traces
pin it. Thirty focused tests cover coverage/ordering invariants, the
hand-expanded ring-1 pattern, and the direction/closest-cell contracts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:35:26 +02:00
Erik
9f4c0f95e3 test(render) Campaign FW0: walk-oracle replay helper + fixture goldens
WalkOracleTrace parses the FW0 retail traces (frames of LS/BLD/DI/DC
events, truncated detach frame dropped) and loads fixtures from the
research directory. Nineteen tests pin the load-bearing shapes: the far
building drawn every terrace-edge frame, the cathedral roster cull, the
stable doorway root, the one-frame walkout handover, camera-cell rooting
(porch-cam), the foundry landscape drop, and bit-identical stationary
frames. FW1 conformance builds on these.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:14:55 +02:00
Erik
e102fb363e fix(render) #132: outdoor alpha drains follow retail walk order
Some checks failed
CI / linux-portable (push) Successful in 3m35s
CI / windows-gate (push) Failing after 6m46s
CI / release (push) Has been skipped
Retail draws every cell's objects inside LScape::draw's far-to-near walk
(DrawSortCell @0x005A17C0) and drains the one delayed-alpha list at the
DrawCells boundary AFTER the finished walk (@0x005A4872). Our outdoor
frame drained at the landscape-stage end and then drew punches, interior
shells, cell objects, and ALL dynamics — every one of those opaque
passes overwrote the already-composited flames (the reopened #132
candle class: "the door draws over the candle", creatures at openings).
Depth and barrier A/Bs were no-ops because the eraser is opaque color
painted after the drain.

Two retail-cited ordering corrections, outdoor-node roots only:

1. The stage-boundary drain is skipped and FlushLandscapeAlpha() runs
   after DrawDynamicsLast, where the frame's opaque world depth is
   complete — the one far-to-near list composites over everything,
   exactly like retail's boundary flush relative to its finished walk.
2. Before DrawExitPortalMasks, FlushLandscapeAlphaFartherThan(
   ExitPortalMaskBarrierDistance(...)) drains everything at or beyond
   the nearest cell whose exit-portal mask is about to write far-Z —
   retail DrawBuilding @0x0059F2A0 runs FlushAlphaList(0f) BEFORE its
   portal-only pass, so in the far-to-near walk nothing already drained
   can meet a punched aperture's falsified depth. Without this, the
   first correction let exterior waterfalls z-pass across punched
   apertures whose true landscape depth the punch erased (found live at
   the cathedral gate). Nearer content stays queued and legitimately
   composites in front of punched structures.

Interior roots keep the pre-clear stage-boundary drain unchanged.

User-gated live: Holtburg sign candle whole in front of the sign and
tower door at the aligned pose; cathedral waterfalls contained at every
camera zoom, inside and outside. Register row AP-236 retired (the
walk-order outcome reconstruction is complete; AP-34 remains the
umbrella for the CYpt-sort reconstruction itself). Filed #456 for the
separate occluded-distant-building/creature admission residual this
session diagnosed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 19:03:35 +02:00
Erik
cd1cdee0e5 fix(render) #443: private viewports take the ring transform path
The paperdoll was visible only in portal space. Root cause: the classic
WbDrawDispatcher.Draw path appended its transforms into the SHARED world
transform frame (WorldTransformFrameArena.Append) with a non-zero base
instance, but the default mesh shaders index every parallel per-instance
array - clip slots, light sets, indoor, OPACITY, selection lighting,
detail category - zero-based; only the packed world submission's shader
convention subtracts the shared-arena prefix. With a world frame active
the doll drew all instances at per-instance opacity 0 into a cleared
target: counted draws, blank pixels, deterministic. Portal space worked
because no world transform frame is active there, so the same code took
the ring path with base 0. The private viewports are the only production
consumers of the classic path, hiding the defect everywhere else.

Fix: WbDrawDispatcher.NextClassicDrawIsPrivatePass - the private
viewport renderer marks its draw and WriteWorldTransformSection routes
private passes onto the plain ring path unconditionally (self-contained
render state: the private pass owns its own camera, lighting, and
target, and must not depend on the world frame's pose address space).

Also landed, each independently justified:
- Per-GPU-flight-slot private targets (PrivateViewportFlightTargets),
  restoring the pre-f6fe0f2a design: that revert's claim that frame
  submission order protects the single target's write->sample transition
  is not guaranteed across Vulkan command buffers. Per-slot completed
  scenes fix the cleared-sibling-after-reveal wart the old attempt had.
- Paperdoll resource preparation moved to the frame resource phase
  (IPrivateEntityViewportResourcePreparation) before world draws consume
  the bounded composite-upload budget.
- The presenter redresses on every dirty edge (an appearance-equal clone
  can pin retired readiness across generations; the renderer's two-phase
  promote keeps the last completed image visible during replacement),
  publishes only non-zero handles, and clears the viewport exactly once
  at the explicit character-session boundary.

Verified live on the clean build: doll visible in the NORMAL world,
visible through portal space, and still visible after arrival - the
exact reported repro cycle. 26 paperdoll/private-viewport/preparation
tests plus 60 renderer-suite tests pass; owner visual gate pending.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 16:51:32 +02:00
Erik
fc30285fd7 fix(render): drawn-once look-in dynamics + farther-than-building alpha drains
Two retail-parity corrections on top of 684380d4, user-gated together
(no regressions; door/creature-through-opening rendering verified live
at Holtburg; cathedral waterfall and look-north casts pass):

1. Drawn-once look-in dynamics. Retail marks every drawn non-player
part for the frame (DrawMeshInternal @0x0059F360 GetDrawnThisFrame), so
an object draws once, with its cell. acdream drew a look-in cell's
dynamics twice under an outdoor root - once correctly with the look-in,
then again in dynamics-last after the boundary alpha drain, where the
second draw overpainted nearer flames. Both the accepted path
(_lookInCellIds) and the frame product (BuildDynamicLastRoute) now
exclude dynamics whose parent cell drew as a look-in.

2. Pre/inter-building barriers drain only content FARTHER than the
building they precede (FlushLandscapeAlphaFartherThan +
RetailAlphaQueue.FlushFartherThan + conservative anchor-origin
threshold). Retail's far-to-near walk guarantees a building's
FlushAlphaList(0f) @0x0059F2A0 has only farther content queued; a
nearer emitter composites at a later flush. AP-236 documents the
remaining barrier-order divergence.

The #132 candle-before-door overdraw is NOT yet fixed by these steps
and stays open: the current suspect is that houses without a
constructed look-in still draw their interior-parented door in
dynamics-last after the outdoor candle's boundary drain (see the
2026-08-29 ledger for the retail flush-after-objects hypothesis).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 16:06:04 +02:00
Erik
684380d421 fix(render): particles draw unclipped, once, in their retail stage
Retail never clips a particle to a portal view: each emitter's polys
join the ONE alpha list during its owner cell's far-to-near walk turn
(LScape::draw @0x00506330 iterates block_draw_list reversed; DrawBlock
@0x005A17C0 walks cells; ShouldDrawParticles @0x0050FE60 gates by cell
and distance), and occlusion is the depth test at FlushAlphaList
@0x0059D2E0 (its float is a COUNT threshold - 0f = flush all). The
1d2f2f73 architecture instead re-submitted particles once per
OutsideView slice under that slice's hardware clip slot, which cut
effects at aperture boundaries and drew nothing when no outside slice
was in view (the cathedral look-north disappearance).

Now: unattached emitters submit once per frame by owner-cell kind
(outdoor landcells in the landscape stage, interior EnvCells in the
final world scope - new UnattachedEmitterCellScope filter); cell,
shell-route, barrier-static, and late-stage owners submit their
per-slice cone-cull UNION once with clipSlot 0; and particles emit in
the stage matching their PARENT CELL - an interior dynamic whose
sphere straddles an exit-portal plane keeps its mesh in both stages
(#118) but its particles move to the final pass, so the interior
stage can no longer repaint over them (the aperture-band star cut).

Also lands the inert Change-2 primitives for the AP-236 retirement
(candle-behind-door): RetailAlphaQueue.FlushFartherThan drains only
the far prefix without resetting sources, plus the executor
passthrough and the conservative look-in threshold helper - nothing
calls them yet.

User-gated 2026-08-29 round 2 at the Sanctuary Cathedral: spell and
recall stars cover the whole room at every camera direction including
north; waterfall containment holds on retail's depth/seal mechanism;
adjacent-room particles/lights, walls, Holtburg, recall unregressed
(paperdoll remains pre-existing intermittent #443). Register: AP-236
filed for the remaining barrier-order divergence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 12:35:30 +02:00
Erik
85530c0b7e fix(render): feed look-in cells to particle and light visibility
Look-in rooms (cells reached through building portals) drew their
geometry but never entered the visible-cell set consumed by the
particle gate and the point-light candidacy scope, so their emitters
stayed frozen and their lanterns dark until the player entered or the
camera left the building. Retail has no look-in split:
ShouldDrawParticles @0x0050FE60 gates on cell->IsInView(), and a cell
installed by DrawBuilding @0x0059F2A0 -> PView::ConstructView
@0x005A57B0 is drawn by the same DrawCells traversal as any flooded
cell, so it is in view identically (per-cell in_view stamped by
PView::InitCell @0x005A4B70).

RetailPViewFrameResult now carries InViewCells (main flood + look-in
cells, the existing prepareCells union; the borrowed-scratch contract
is unchanged because RecycleLookInFrames runs at the start of the next
DrawInside). WorldSceneRenderer feeds InViewCells to
ParticleVisibilityController and the point-light scope. DrawableCells
is untouched for seals, the outside-stage predicate, diagnostics, and
the packed-product referee. The legacy 4-arg Reset defaults
InViewCells = DrawableCells for standalone callers.

User-gated 2026-08-29 at the Sanctuary Cathedral: adjacent look-in
rooms show particles and lantern lights from the root cell without
entry or an outside camera; recall/waterfall/Holtburg/paperdoll
unregressed. The pre-existing #132-family candle-behind-opening
report remains open and is unchanged by this fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 11:44:19 +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
d123c4b67c test(ci): harden scheduling and allocation gates
All checks were successful
CI / linux-portable (push) Successful in 3m30s
CI / windows-gate (push) Successful in 6m15s
CI / release (push) Successful in 2m11s
2026-08-27 19:28:10 +02:00
Erik
c4608b1127 Merge origin/main into MossTank automation work
Some checks failed
CI / linux-portable (push) Successful in 3m51s
CI / windows-gate (push) Failing after 7m8s
CI / release (push) Has been skipped
# Conflicts:
#	docs/ISSUES.md
2026-08-27 19:01:26 +02:00
Erik
4e6e9bc9d9 feat(mosstank): add VTank-style automation PoC 2026-08-27 18:57:21 +02:00
Erik
1d2f2f738f fix #451: stabilize portal seam rendering
All checks were successful
CI / linux-portable (push) Successful in 3m32s
CI / windows-gate (push) Successful in 6m55s
CI / release (push) Successful in 2m12s
2026-08-27 14:30: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
f160f3fee1 fix(ui): wait for private viewport mesh residency 2026-08-25 19:16:53 +02:00
Erik
82e4b4cb6d fix(render): harden portal exit handoff
Some checks failed
CI / linux-portable (push) Successful in 3m19s
CI / windows-gate (push) Failing after 6m43s
CI / release (push) Has been skipped
2026-08-25 17:39:44 +02:00
Erik
0c5057c9ff fix #435 (part 1): delete 17 probes that outlived their closed investigations
Each of these was temporary apparatus added to chase one bug, and each was
supposed to be deleted in the commit that fixed it. Fourteen closed issues
later they were still here: #337's support/wire-mesh trio, #171's sticky
timeline, #119's viewer and entity dumps, #113's phantom probe, and a dozen
more. 3,493 lines removed; the client now reads 144 environment variables
instead of 161, and 47 temporary probes remain instead of 64.

This is not only tidying. Every probe leaves a branch on its hot path when
unset, several re-read the environment per call rather than caching, and
the volume buries the diagnostics that are actually load-bearing. It is
also a headless correctness matter: HeadlessStaticStateAudit reflects over
PhysicsDiagnostics' flags to refuse a multi-session host when any is set,
and cannot see probes that live outside that owner.

Four files went entirely — WalkMissDiagnostic.cs, CollisionMeshWireframe.cs
and two test files whose only subject was a deleted probe.
TransitionTypes.SetContactPlane also sheds its CallerMemberName /
CallerLineNumber parameters, which existed solely for #337's cpSrc=
attribution and carried the instruction to strip them with the probe
family; no call site passed them, so no behavior changes. F2's collision
overlay survives and reverts to its proxy-cylinder form, which is what
removing the ACDREAM_WIRE_MESH upgrade means.

LaunchOptionsDocumentationTests earned its keep here: it refused the
deletion until docs/launch-options.md moved the 17 rows into Retired and
the frozen direct-read counts came down (PhysicsEngine.cs to zero,
TransitionTypes.cs 3 to 2). The documentation could not drift during a
cleanup this wide.

The 14 probes that name no owning issue are deliberately NOT deleted.
Nothing records when they became safe to remove, and guessing is how a
future investigation loses apparatus it needed; #435 stays open for their
attribution.

Full hermetic suite 15,321 passed / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 11:41:20 +02:00
Erik
92999b0101 fix #432: allocation-free oracle fingerprint sort — the ~6 MB/frame diagnostics tax
Second instance of the #429 (ad695589) boxing-comparer defect class, this
time in the measurement harness rather than production:

ACDREAM_AUTOMATION_ARTIFACT_DIR (with retained-UI screenshots) constructs
CurrentRenderSceneOracle, whose presence as partition observer re-enables
the G5-retired legacy InteriorEntityPartition every frame with per-entity
fingerprinting. Complete() then sorts one fingerprint per RESIDENT entity
(~60k across the streaming window), and the comparer's first key bound
x.ProjectionClass.CompareTo(y.ProjectionClass) to Enum.CompareTo(object),
boxing both operands. The 3-value enum almost always ties, so the boxing
ran on essentially every comparison: a measured ~6.2 MB and ~14 ms per
frame, everywhere — not town-specific and not view-triggered, which is
also why it appeared to "latch" (the resident set drives it, not the view).

Comparing the underlying integral value keeps the identical order. Hermetic
gate: one warmed observed partition of 20,000 entities allocated 15,876,088
bytes before, and passes a <64 KiB bound after
(OracleObservedPartitionAllocationTests).

Ordinary play never constructs the oracle, so no player-visible behavior
changes; what changes is that captures taken with the automation artifact
directory set are no longer taxed. The #429 acceptance data is unaffected
(owner drives and the deciding A/B arms ran with the artifact dir null).

The temporary [pview-alloc] attribution probe that localized this is
retired in the same commit; the gate test now guards the defect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 10:10:46 +02:00
Erik
ad69558908 fix #429: allocation-free shadow topology rebuild + churn-frame pipelining
The directional-shadow topology rebuilt on every streaming-churn frame
and was the measured body of the run-hitch stalls (701 of 708 baseline
stalls alloc-correlated):

- The draw sort comparer's enum-vs-enum CompareTo bound to
  Enum.CompareTo(object) and boxed BOTH operands on every comparison —
  a constant ~38.9 MB of garbage per topology rebuild (~4M boxes),
  handing the GC a forced gen0 collection mid-frame. The full ~100k-draw
  sort is replaced outright: draws hash-group by exact DrawKey in one
  O(n) pass over retained chained-index arrays, and only the
  few-thousand DISTINCT group keys sort (order-preserving packed
  material|cull|firstIndex|baseVertex + count|slot|layer|foliage keys,
  first-appearance tie-break) — bit-identical emission order to the old
  stable sort, near-zero allocation, and no per-draw comparisons at all.
- The caster frame sorts 4-byte indices keyed on SortKey.Value instead
  of shuffling multi-hundred-byte records through a boxing comparer.
- Owner-approved pipelining: on a frame whose shadow inputs just changed
  (the same frame already paying frame-view/landscape rebuilds), the
  caster-frame and prepared-draws topology rebuilds defer to the next
  quieter frame, capped at two consecutive deferrals — inside the GPU
  fence depth, so retained draws never reference a released arena range.
  First build, generation change, caster BuildSequence change, and
  journal overflow force the immediate path; deferred refreshes skip
  identity-mismatched journal rows.

Owner-accepted in both presentation modes: stall frames 5.8/s -> ~0.45/s
uncapped (0.49/s capped), median stall 20.3 -> 13.7 ms, >25 ms frames
near zero, 275 fps uncapped baseline restored. Allocation gate: a warmed
topology rebuild must allocate <2 KiB (DirectionalShadowPreparedDrawTests).
docs/ISSUES.md carries the full evidence trail; the residual
content-proportional rebuild milliseconds are filed as the
incremental-topology successor, and the pre-existing town-view scaling
latch is filed as #432.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 09:16:58 +02:00
Erik
0330fcd0d1 perf(render) #429: allocation-exact streamed-mesh completion
UploadGfxObjMeshData built every completed mesh's index data three-plus
times over in LINQ transients (per-batch Indices.ToArray copies plus an
unsized SelectMany growth) on the render thread, up to the per-frame
upload budget. The conversion now fills one exact-size retained
CPUIndices array (the same one the B.4b pick path keeps) and hands the
shared arena (offset, count) segments of it; CPUPositions fills by a
direct pre-sized loop; the Sum/Any/FirstOrDefault transients are gone.
GlobalMeshBuffer.UploadMesh takes the segment form — the staged bytes
per batch are unchanged. Gate: a warmed completion must allocate near
its retained-copy size (MeshPipelineDeviceSeamTests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 09:14:00 +02:00
Erik
9cf15e1f13 perf(sky): ~10x cheaper night-sky lattice — one-round cell hash, 2x2x2 block, bit-sliced stars
Some checks failed
CI / linux-portable (push) Failing after 1m37s
CI / windows-gate (push) Successful in 6m35s
CI / release (push) Has been skipped
The owner measured 63% GPU at night vs 28% by day: the starfield
lattice was the whole difference. Three lossless cuts:
- one packed-multiply hash round per cell instead of ~9 chained PCG
  rounds (position/presence bit-sliced from one result, brightness/tint
  from a second);
- the 3x3x3 neighbourhood sweep becomes the 2x2x2 block around the
  sample — a star's visible footprint (a few px) is far smaller than a
  lattice cell on screen, so any star outside that block is over a full
  cell (>=15 px) away and contributes nothing;
- background mottle drops its invisible third octave.

Same densities, sizes, colours, and look; star positions reshuffle
(procedural layout, not authored). sky.frag.spv re-pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 18:31:39 +02:00
Erik
9fcc3f4870 Revert "feat(sky): the night sky wheels with the Dereth clock; uniform time fade with a scoped twilight band"
This reverts commit 0dac024cdb.
2026-08-23 18:26:16 +02:00
Erik
0dac024cdb feat(sky): the night sky wheels with the Dereth clock; uniform time fade with a scoped twilight band
Rotation (user-directed): the procedural starfield rotates once per
Dereth day (~2 real hours - constellations visibly wheel through a
night) about a celestial pole ~41 deg above the northern horizon, plus
dayOfYear/360 of seasonal drift so the 360-day year changes the night
sky. One SkyParams float (272-byte block, layout test re-pinned)
carries dayFraction + dayOfYear/360 from the world clock; sky.frag
applies a Rodrigues rotation to the sample direction so stars and
mottle turn together. Impossible with retail's static stretched layer.

Fade rework (the 2026-08-23 two-screenshot gate finding): the
per-vertex vTint signal carried the sun-facing product and blanked
stars across the entire twilight half of the sky. The fade now reads
the UNIFORM ambient term - identical star visibility in every compass
direction, same dusk-to-dawn schedule - with one deliberate exception:
a thin suppression band hugging the low sky toward the sun's azimuth
while the sun term is strong, so stars still wash out inside the
actual twilight glow.

Guards updated (rotation anchor, uniform-fade anchor, 272-byte layout);
both sky SPIR-V hashes re-pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 18:21:17 +02:00
Erik
91084b9a83 perf(sky): skip the night-sky lattice entirely while the day fade is zero
The enhanced sky multiplied the full 4-tier star lattice by a night
factor of 0.0 all day long. Early-out when the star layer's lighting
product says daytime, so the pack's night sky costs nothing outside
dusk-to-dawn. sky.frag.spv re-pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 18:09:04 +02:00