Commit Graph
196 Commits
Author SHA1 Message Date
Fr0zkaandClaude Opus 5.5 6ee433efa5 test(gate): stress repro judges terrain penetration -- gate on 10/10 clear, gate off 3/3 falls through
The old criteria (travel distance) could not tell a working gate from a broken one: every run,
gate on or off, ended at (455.7, 0, -58.1). That spot is measured air ~1.2 m above a real floor
(density zero-crossing at world Z ~ -275.7 cm), so it is a terrain trap, not a fall-through.

The stress test now drops the pawn fast onto an unready support tile (0,0,-1) and samples the
density at the capsule feet every frame, sweeping at <= half a voxel between positions. PASS =
no measured clearance below -5 cm.

Owner config (radius 3, max level 6, distance 768, sheets off):
- gate on: 10/10 PASS, min clearance +120.5..+120.9 cm, one hold of 0.43-0.47 s each,
  support submitted in ~0.7 ms, collision ready in 0.43-0.47 s
- gate off: 3/3 FAIL terrain_penetration, clearance -5.7..-8.1 cm
Edit-ghost, clipmap no-overlap sweep: pass. Test-harness code only; no production path changed.
This validates a49725d.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-25 06:11:08 +02:00
Fr0zkaandClaude Opus 5.5 a49725d365 wip(gate): request the support tile at once; no gate timeout -- stress criteria NOT trusted yet
- RequestCollisionGateSupportTile submits the level-0 support tile immediately, even before the
  first desired set; the desired rebuild adopts the same pending key (1 early request, 1 adoption,
  1 ready callback in all 10 runs). First check -> submit went from ~2.2 s to ~1 us.
- The 2 s gate release timeout is removed: at the owner's startup the game thread applies nothing
  for ~2.2 s, so the timeout always fired and dropped the pawn. The gate holds downward Z only;
  horizontal movement stays free.
- Controls (previous round): legacy selector+reconcile failed the stress repro 0/5, aligned 1/5,
  so 85a803b did not regress the gate.

NOT VALIDATED: the stress repro's pass criteria were rewritten (support submitted/ready, a hold,
progress during a hold). 10/10 "pass" under them, but every one of those runs, and the gate-
disabled run, ends stuck at (455.7, 0, -58.1), ~1.55 m below the Z~97-100 at which the old
passing runs travelled 113 m. Whether that spot is a real pit or the pawn below the surface is
unresolved; until it is, a pass under these criteria proves nothing about falling through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-25 04:54:00 +02:00
Fr0zkaandClaude Opus 5.5 85a803b873 wip(streaming): LOD rings tile exactly once, cache race, reconcile 104x -- gate stress FAILS 3/4
- Clipmap: finer rings snap to child-pair boundaries, so no two desired tiles of different
  levels overlap. Owner config 635 overlapping pairs -> 0; LOD0 ring 343 -> 512 tiles.
  New test sweeps 6,174 cases; fails on 7486c29 as intended, passes here.
- Shared room-graph cache: Entry->Cache read/written only under the mutex (race fix).
- ReconcileReadyTransitionVisibility indexed: moving p95 32.0 ms -> 0.31 ms.
- Block-vs-scalar density equivalence test: 5.1M samples bit-identical; catches an injected
  optimistic EffectOverBox (5,612 mismatches).

UNVALIDATED for merge: collision-gate stress passed 1 of 4 runs on this code (fails at 4.56 m,
support collision unready at start); it passed on 7486c29. No control run yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-25 02:47:54 +02:00
Fr0zkaandClaude Opus 5 7486c299d4 fix(streaming): no stale geometry over its replacement -- edit ghost 40s -> 0.17s
The owner reported a wall staying visible after he carved through it, and overlapping LODs when crossing slopes at speed. Two causes: an edit only re-meshed the level-0 tiles of the touched chunks, so coarser tiles kept pre-edit geometry until something else re-queued them; and superseded tiles stayed drawn until the budgeted teardown reached them.

Now: edits fence late pre-edit async results and re-mesh every affected loaded/pending level; a replaced tile is hidden as soon as its replacement is fully covered, while destruction and collision removal stay budgeted (the anti-spike rule that made teardown budgeted in the first place). Load-before-unload is unchanged, so no holes.

Measured: edit ghost 40.095 s (unresolved at test exit) -> 0.172 s; 6000 cm/s slope overlap 19.952 s with 153 overlapping pairs -> 0.000 s with zero events. Gate stress PASS (113.68 m, 2 holds, 0 full stops). Apply-frame cost peaked at 0.979 ms against a 2 ms budget.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-23 21:10:20 +02:00
Fr0zkaandClaude Opus 5 f36bc1f9b2 wip(streaming): priority by class and level -- near ring 53.6s -> 1.2s, but crossing test fails
Scheduling rule: CollisionCritical -> Edited -> LOD0 -> Horizon, with coarse work capped so 4 task slots stay reserved for near work; result queues follow the same order; epoch and cancellation handling unchanged.

Measured at MaxClipLevel=5 / RenderDistanceChunks=768: spawn ring request-to-ready 53.632 s -> 1.171 s; spawn ring from trace start 68.252 -> 62.549 s; moving first complete ring 66.808 -> 63.917 s, warm ring p50 0.267 s, per-tile moving LOD0 p50 69.4 -> 62.0 ms; full settle of 10,841 tiles 110.7 -> 106.9 s. Gate stress PASS (139.99 m, 0 full stops); static perf PASS.

⚠️ NOT SAFE TO MERGE YET: the strate-crossing test fails in both patched runs with capsule_overlaps_pending_floor -- the exact guarantee the gate exists for. Codex suspects the owner's current DA_Settings (seal 50, clip level 5) rather than this change, since older passing captures used different settings. Control needed: same test, same assets, on 370c39e.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-23 19:04:13 +02:00
Fr0zkaandClaude Opus 5 370c39e473 feat(streaming): horizon distance with real tiles only -- sheets off by default
The sheet system models terrain as one ground and one cap height per column, which cannot express this world's overhangs, floating islands and open sky; three attempts to fix the MC/sheet seam produced holes, then caps over the void, then both. Measured alternative: cover the render distance with real marching-cubes tiles and switch the far-sheet ring off.

Benchmarks at RenderDistanceChunks=768, all tiles loaded at steady state: L4 57,473 tiles / 30.1M tris / 837 MiB / 2,514 s worker / 296 s to settle; L5 10,841 / 8.1M / 226 MiB / 375 s / 108 s; L6 3,281 / 2.9M / 82 MiB / 127 s / 77 s. Defaults now MaxClipLevel=5, bFarSheetRing=false. Invariants: strate crossing PASS, gate stress PASS (140.06 m, 0 full stops), determinism PASS with 10,841 tile hashes identical vs -voxel.CrtFma3=0. Peak memory ~4.2 GiB in all three.

Owner action required: his DA_Settings still serializes MaxClipLevel=3 (asset edits are out of scope for Codex), so his world needs that value raised to 5 to use the new far field. At twice the distance the estimate is ~39.5k tiles / 29-30M tris / ~0.8 GiB / ~1,500 worker-seconds, which would want L6 rather than L5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-23 17:39:49 +02:00
Fr0zkaandClaude Opus 5 ea3f0d9a52 fix(classifier): exact lattice check before a uniform verdict; ceiling cause is NOT false certificates
ParameterRoll's 5 false box verdicts are fixed: fine room-graph intervals are now checked against exact lattice samples before a uniform verdict is accepted, while the SDF bound keeps its conservative lower bound. Lattice sweep 600 tiles / 0 violations; ParameterRoll passes with 9,577 exact lattice leaves and 774 interval disagreements caught.

The owner's ceiling holes are NOT explained by this: in his recorded session outer_classifier_mode=0, solid-proof skips were 0, and air-proof skips were outside the strate. The trace points at the far-field sheet system instead: sheet tile L5 (0,1,0) is deliberately empty inside the sheet hole while its bridge tile L4 (0,1,-1) meshed 662 ceiling triangles. Continuous ceiling is NOT verified: every render launch failed (D3D12 driver error, stalls before map load, the 6 GB cap), so the round produced no matched after-render.

Cost note: this round used 3.46M tokens on Luna 6 at max, most of it burned on failed render launches. Verification by render is unreliable in this harness; verify ceiling continuity from tile/geometry data instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-23 06:30:05 +02:00
Fr0zkaandClaude Opus 5 30253f0e89 fix(streaming): vertical reach follows the strate, and the level below the sheet ring is filled
The owner marked missing tiles around him in a 64-chunk surface strate with RenderDistanceChunks=768. Cause: the desired-set vertical clamp used the player's small window (bClampViewToStrate on, ViewDistanceUp/Down=5, margin 3), so at chunk Z=0 only [-5..3] was ever requested; the rest of his strate was never asked for. Second cause: with MaxClipLevel=3 and a two-level sheet span the outer ring is level 5, while the marching-cubes loop stopped at 3, leaving level 4 empty.

The band now follows the player's whole strate plus margin ([-66..3] in the pinned case) and keeps the small window only when there are no strate bounds (inter-strate gap), so the hidden-rock column the clamp exists to avoid does not return. Bridge shells are emitted up to the level below the sheet ring, filling level 4.

Pinned case desired tiles per level, before -> after: l0 343->343, l1 218->218, l2 129->178, l3 80->178, l4 0->178, l5 4801->9586; total 5,571 -> 10,681 (+91.7%). New focused fixture VoxelForgeVerticalStrateReachTest fails before and passes after. Stress repro passes (140.089 m, 4 holds, 0 full stops); strate crossing passes (0 holds, 0 floor overlap).

⚠️ Cost not established: the post-fix perf sample was incomplete and showed a 55.7 s p50 queue wait, which may be the fixture's extreme settings (768 render distance -> 9,586 sheet tiles) or a real backlog. Measure in the owner's world before merging.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-23 01:49:57 +02:00
Fr0zkaandClaude Opus 5 d7af2cab97 fix(collision gate): hold the fall, not the player; add voxel.CollisionGate switch
The owner could not do speed runs: the gate stopped the whole pawn whenever streaming fell behind. Measured at 6000 cm/s on 0dd45ca: one 18.9 ms hold followed by terminal non-resume, 4.522 m of a 120 m route. The gate now cancels only downward motion into unresolved ground, keeps horizontal velocity and player control, ignores air and out-of-layout tiles, and requests a blocking support tile at high priority. voxel.CollisionGate (default 1) disables the safeguard for stress runs; it is snapshotted at start-up.

After: stress run PASS -- 4 holds, 1.881924 s total, full_stops=0, 139.947 m of 120 m. Strate crossing PASS with pending_floor_overlap=0 and the lower strate reached. Static perf sanity PASS. Verification budget kept small on purpose (no suite, no exports): the owner's complaint was that a one-line request triggered a thousand tests.

Known, NOT fixed here: the surface-fall repro fails as surface_ground_timeout -- the pawn now lands, but on top of the authored (0,0) entry at Z=799.5 cm instead of dropping to its floor at -150 cm. The gate has released by then; this looks like the entry opening being capped, i.e. the owner's 'surface generation is bugged'. Next round.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-22 16:23:20 +02:00
Fr0zkaandClaude Opus 5 88f3e44402 wip(collision gate): stop the fall instead of the player -- UNVALIDATED, after-change run never happened
The round was stopped when Windows began refusing to start processes for it (0xC0000142) under memory pressure, before any after-change test run. Treat as a draft: the baseline run happened, the verification did not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-22 15:19:12 +02:00
Fr0zkaandClaude Opus 5 0dd45ca410 fix(collision gate): stop freezing the player on a stale prediction
The owner fell through a surface-only world and hovered in mid-air: log showed 'ready=1 predicted_wait=1' for 50 s. Two causes, both fixed: (1) engaging the gate zeroes velocity, so the predicted-support probe never re-ran and the gate kept waiting on the tile remembered from when the pawn was still moving -- the prediction is now re-derived every tick from the pawn's current pose; (2) a tile the streamer never requests (out of layout, or outside the desired set) could never become ready, so the wait was unbounded -- such tiles are now logged and treated as nonblocking, and any wait is bounded to 2 s with a warning.

Repro added to the harness (surface-fall scenario): before, the pawn stops at Z=1315.9 with collision_gate_prediction_deadlock; after, it reaches the ground in 2.25 s at Z=-45.6. Strate crossing still protected: 145.49 s route, max gate wait 0.109 s (limit 10 s). Perf S/M/S/M 24.265/54.262 and 23.947/53.057 s, within repeat noise. Owner and canonical geometry unchanged (44E7C813 / 94B47A1C).

Known red, pre-existing and unrelated: VoxelForge.Composer.ParameterRoll still reports false box verdicts in the owner's current single-strate surface layout (5 violations, identical in the baseline). Next round.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-22 11:17:01 +02:00
Fr0zkaandClaude Opus 5 7af8d0e870 fix(correctness): false AllAir certificates and a stale shaft cache key; 44/44 tests green
The harness's first full suite run showed 16 of 44 tests failing. Two were real defects in the owner's world:

1. ParameterRoll: the tile classifier could certify a box AllAir while the final field is solid there (3 false proofs found, e.g. density -2.092319 at (-192,16,-224)). Cause: the box diagnostic narrowed 'tail reach' to 'air reach', so the swept tunnel FLOOR -- which writes solid -- did not count as reaching the box. A false AllAir skips meshing, i.e. missing floor in game. The classifier now uses the same reach predicate as the final writer (swept chain, floor band, relief, room-mouth envelope) and propagates it through Mixed refinement nodes. Re-run: 1,288,143 lattice samples, 0 violations.

2. VerticalShaftEquivalence: LedgeSpacing and LedgeDepth were missing from the production TLS cache key, so a shaft could reuse stale ledge parameters. Both are now in the key.

The other 14: broken fixtures (missing staged corpus assets, a sampler leaking the shaft TLS marker between queries, stale candidate IDs, a window entirely inside the common tail) or stale expectations after this week's owner decisions (no authored tunnel floor, slope in degrees, rooms owning their interior). No test was deleted or weakened into vacuity; retired legacy comparisons stay as telemetry and the current stack is asserted instead. PassageLandsInOpenSpace now requires a real player-capsule fit in the final density at each authored landing (14/14, worst gradient 0.115723).

Suite: 42 tests green in one run, the two long op-stack sweeps green in serialized filtered runs (the 1800 s launch guard); a harness timeout-recording bug was fixed. Bounded probe exports unchanged (canonical 94B47A1C, owner 44E7C813). ⚠️ Harness perf scenario moved 50.74 s -> 53.58 s worker (+5.6%): the wider reach predicate skips fewer boxes. Measure on the game path and decide before merging.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-18 13:13:21 +02:00
Fr0zkaandClaude Opus 5 a7b8d10de6 tooling(test): one command to test VoxelForge, with a commandlet-vs-game parity check
Tools/VoxelForgeTest.ps1 runs scenarios (canonical, owner, probe, perf, tests, parity) with the known-good flag sets, isolated user/config dirs, serialized launches, an owner-process guard, and a compact result.json + summary.txt instead of 300 MB logs that Codex has to stream. Answers the owner's question 'can the commandlet reflect in-game behaviour': -gameconfig=1 makes the commandlet use the authored settings, and the parity scenario compares per-tile geometry hashes between the commandlet export and the game's streaming path. Parity PASS: 64 commandlet tiles vs 810 game records, no differing tile.

First run of the tests scenario: 44 tests, 28 pass, 16 fail (8 composer, 6 op-stack equivalence, PassageLandsInOpenSpace, StrateParamBlendCoverage). Triage is the next round: stale expectations from the floor removal and the degrees change, versus real defects, especially the legacy-vs-op-stack equivalence ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-18 05:13:16 +02:00
Fr0zkaandClaude Opus 5 1c642fd182 perf(tunnels): memoise the room mouth -- room ownership now cheaper than before the change
The room-wins rule cost +55% static / +36% moving worker CPU because every sample re-answered the room-mouth question. A bounded exact memo around BuildRoomMouth (4096 sets x 4 ways, 7.49 MiB, keyed on every geometry-affecting room/tunnel input) removes that: hit rate 99.91% static / 99.89% moving, and VF_FindRoomFloorCrossingWorld (19.5%/17.1% inclusive), VF_FindRoomMouthBoundary (16.0%/14.0%) and VF_EvaluateRoomLandingDensity leave the sampler's top 30 entirely.

vs the tip DLL (bf9384a runtime), same staged owner map: worker sum 29.003 s vs 30.749 s static (-5.7%), 57.232 s vs 60.923 s moving (-6.1%), request p95 -3.3% / -8.4%. Geometry unchanged by construction: canonical hash 86A99075, 64/64 tile hashes, OBJ SHA 7E0D8109..C543, identical across a repeat run and with -voxel.CrtFma3=0. Walk Connected 42,870 / 17,413; tunnel-attributed solid inside rooms 0; mouth amplitude 1.4903 cm; TilePostReachProof and DetSinCos pass. The crossing test was not rerun (the memo returns the same values; its PASS stands at 248.43 m / 26.584 s).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-18 03:41:38 +02:00
Fr0zkaandClaude Opus 5 d01af4606d wip(tunnels): room wins over tunnels -- no shelf, smooth mouths; stopped before final checks
Stopped by the owner (he wanted his PC back to play) after most acceptance was in. Measured with the owner's assets: the shelf across rooms came from the tunnel core reasserting solid below its floor inside room volumes, plus the landing-floor post. Room ownership now suppresses the tunnel bottom in the generic tunnel SDF, the core evaluator and both native fallbacks: tunnel-attributed solid inside rooms 469 -> 0, and a matched-camera re-render shows the slab gone. Trimmed mouth endpoints now take their height from the room's own vertical zero crossing (join offsets -2.08/+0.40 -> -1.13/-0.71 voxels). Mouth surface periodic amplitude 1.49 cm floor / 0 cm wall (degree-six trend removal; raw local-median audit 6.19 cm kept). Canonical walk Connected, 42,870 fit / 17,413 walk-reachable (was 41,184 / 13,879); export 141,054 triangles, geometry hash 86A99075. Launches use an isolated engine saved/config directory so the owner's editor settings are not rewritten.

NOT DONE: the -voxel.CrtFma3=0 export comparison, TilePostReachProof / DetSinCos / strate crossing, the perf pair vs the experimental tip, the EditorSettings.ini diff. Unvalidated in game.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-17 14:56:06 +02:00
Fr0zkaandClaude Opus 5 4fa3931176 fix(tunnels): ribs removed at the cause; authored tunnel floor removed; slope acts on the route
The owner proved in game that the tunnel core caused the ribs (voxel.TunnelAblateTunnelCore=1 removes them). Root cause: the core's final reassertion replaced any sufficiently interior sample with a fixed +16 air density, destroying the distance gradient, so marching cubes snapped the surface to the voxel grid (all 385 centre samples saturated). The core now reasserts the continuous -Core.SDF, with bottom polarity from the swept shape. The Catmull-Rom sweep from 0231072 is reverted (it cost 18-30% and did not help); the linear tapered-capsule sweep is restored.

Owner decisions implemented: bTunnelFloorEnabled has no runtime effect (reflected only for asset compatibility, ignored on PostLoad), and the authored slab / graph support-floor paths are detached from generation, so the tunnel's own carved shape is the floor. Winding and dramatic ledges no longer depend on the floor switch. Degrees authoring, its migration, and the 3840-voxel route cap stay. Mouth trim stays behind voxel.TunnelMouthTrim (default off).

Evidence: along owner tunnel 15, 385 samples at 0.25 voxel, periodic surface amplitude floor 3.55 -> 0.50 cm, wall 16.9 -> 1.10 cm, ceiling 8.80 -> 0.69 cm (acceptance < 2 cm); 0/385 samples saturated. Matched-camera render: ribs absent. Canonical walk: Connected, player-fit 41,184 (was 20,830), walk-only 13,879 (was 10,909), mouth gap 0; mesh identical with -voxel.CrtFma3=0. TilePostReachProof, DetSinCos, strate connectivity 16/16, strate crossing: pass. Perf, one S/M/S/M pair vs a rebuilt bf9384a DLL: static 26.10 vs 25.41 s (+2.7%), moving 55.34 vs 53.08 s (+4.2%), request p95 about equal. Treat as a possible small cost to re-measure, not as noise.

Not merged into experimental: waiting for the owner's in-game look.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-17 02:48:20 +02:00
Fr0zkaandClaude Opus 5 0231072cc4 wip(tunnels): continuous world-core sweep, slope in degrees, bounded winding -- NOT MERGED: +18-30% worker CPU, visuals unconfirmed
Tunnel round from the owner's playtest. Luna's ablations traced the ribs to the world-space tunnel-core stage: disabling it removes them, while terracing, detail ops, roughness, the support floor, lateral warp, radius jitter, winding and LOD do not. The per-span capsule union is replaced by a deterministic Catmull-Rom sweep. Winding and graph expansion are now gated on bTunnelFloorEnabled (they ran even with the floor off). TunnelFloorGentleSlopeThreshold is authored in degrees (0-90, min 1 deg, DetSinCos conversion, legacy tangent values migrated: 0.3 -> 16.7 deg). The route length is capped at 6 waves x 640 = 3840 voxels, so 0.1 deg loads instead of freezing (2048 collapsed canonical reach; 24576 used ~2 GB). Room-wall trimming is behind voxel.TunnelMouthTrim (default OFF): with it on, a 36 m gap disconnects the walk.

Canonical walk: 20,907 / 10,981, connected (was 20,830 / 10,909); exports identical with -voxel.CrtFma3=0; TilePostReachProof, DetSinCos and strate crossing pass. NOT merged because: one S/M pair vs the tip DLL shows worker CPU +17.8% static / +30.3% moving and worse p95; and the orchestrator's look at the before/after renders is inconclusive (camera not matched; regular grooves still visible in the after interior). Next: owner checks the look in game; if smooth, make the sweep cheap before merging; if not, drop the sweep and keep the degrees/bound/wind-gate parts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-16 18:29:27 +02:00
Fr0zkaandClaude Opus 5 10fd0b6e77 fix(determinism): own sin/cos on the world path -- same world on every CPU, 3.5% faster
On Win64, FMath::Sin/Cos resolve to CRT sinf/cosf (GenericPlatformMath.h:521/530; no platform
override), and the UCRT picks FMA3 or non-FMA3 implementations at startup by CPU. /fp:precise
closed only the compiler half, so the same build and seed could build a slightly different world
on two Windows PCs, and on Linux.

- VoxelMath::DetSin/DetCos/DetSinCos (VoxelTypes.h): fixed double-precision period reduction to
  a quarter period, then fixed float minimax polynomials; no CRT, FMath or CPU-dependent
  intrinsic. Measured max error vs sinf 5.96e-8 over the used range (|arg| <= 6.19).
- Every world-deciding FMath::Sin/Cos replaced (generator, op stacks, cave morphology, strate
  manager, content placement, strate measure); none remain in runtime code.
- Pinned test VoxelForge.Determinism.DetSinCos (golden bit patterns + error bound).
- Diagnostic start-up switch -voxel.CrtFma3=0 (Windows) forces the CRT's non-FMA3 path.
- Parse-once console switches now initialise in StartupModule on the game thread; workers only
  read them (was an unsynchronised first parse inside GetDensityAt).
- Corrected the CRC comment that claimed a CRC32 cannot give a false positive.

Proof (Codex round; orchestrator re-checked):
- Before the fix, FMA3 on vs off was identical on this Ryzen 7 5800X3D: the hole exists but was
  not observed in this world. After the fix, the canonical exports are byte-identical with
  FMA3 on and off.
- The world changed once, as expected. New canonical export SHA-256 CBB8AE18...906F (geometry
  B9EF84AC; was B3E5F4C3...377); worms region 9242C093 (was 311CA42C), walk OBJ 20CCF7BD....
  Triangle counts unchanged in every tile. Order-independent nearest-vertex distance, worms
  region: max 8.7 micrometres (the reported "11.4 m" was a line-order artefact of a line-by-line
  OBJ diff); export region max 3e-5 m.
- Capability exact: player-fit 20,830, walk-reachable 10,909, connected. TilePostReachProof,
  sealed-proof, cross-platform and strate-crossing tests pass.
- Performance, headless game path, S/M/S/M vs the committed-tip DLL: static worker 27.41 s vs
  28.44 s (-3.6%), moving 59.59 s vs 61.74 s (-3.5%); LOD0 p50/p95 not worse.

From Sol's code review (DESIGN-SOL-2026-09-15-CODE.md, section 4.1), branch sol/deterministic-math.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-16 00:55:04 +02:00
Fr0zkaandClaude Opus 5 410788f7dd perf(generation): per-chunk routing cache -- ~4% less worker CPU, exact
GetDensityAt asked the strate manager on every sample whether the chunk is a gap chunk, which
generator type it uses, and whether it uses the operator stack. All three are constant per chunk.
voxel.ChunkRoutingCache (default 1) memoises them per worker thread (Codex; reviewed by me):
- thread_local storage only, so no cross-thread sharing;
- key = generator owner id + manager pointer + manager lifetime id + layout version + full chunk
  coord, the same identity pattern as the existing CP_* refetch;
- the editor composer override is still queried in the CP_* refetch, not cached here.

Attribution for the re-rank's ~28% shared cost (offline-symbolised sampler, exclusive): GetDensityWithParams
16.4%, GetDensityAt 6.2%, unknown 5.8%, FChunkSDFSpatialIndex::GetRange 3.8%, FScopedTimer::End 1.3%,
VF_ForEachSpatialCandidate 1.2%, UsesOperatorStackForChunk 1.0%. The tunnel-cache lookup was already
near-perfect (48 misses in 2,248,091 samples). A thread-local timer cache was tried and rejected as
slower (+0.4% static, +1.2% moving). Spatial-iteration and mesher-loop changes found no isolated
exact win.

Routing A/B: static worker 28.50 -> 27.78 s (-2.5%), moving 63.01 -> 59.81 s (-5.1%).
Final DEFAULT build vs the committed-tip DLL (two interleaved runs each, same command line):
  static  worker 28.79 -> 27.58 s (-4.2%)   LOD0 p50/p95 52.3/94.8 -> 50.1/92.6 ms
  moving  worker 62.25 -> 59.97 s (-3.7%)   LOD0 p50/p95 51.4/95.8 -> 48.8/92.3 ms
Field exact: OBJ SHA-256 b3e5f4c3..9b377 on both builds, 0 per-tile differences, capability
20,830 / 10,909; TilePostReachProof, sealed-proof and strate-crossing tests pass.
Ledger: every launch was serialized with 0 editors before it. Two attribution attempts were
rejected on harness bookkeeping/output-path bugs before a clean run; one crossing run was invalid
(profiling left on, exit -1) and its retry passed. No crash folders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 19:33:48 +02:00
Fr0zkaandClaude Opus 5 8ef1a3b579 test(reach): automation test guarding the per-tile reach proofs; merge closed as load-bearing
VoxelForge.Correctness.TilePostReachProof (VoxelForgeTilePostReachTest.cpp) runs the real mesher
twice in one process: at full reach it compares the skipped posts' FINAL field (356,946
comparisons, 0 differences), and at a deliberately shrunk reach it must find differences (774
found). Native/fused sample counts are identical (137,180). So a future tunnel or passage geometry
change that breaks a reach proof (ce4d878, a668833) fails loudly instead of silently making holes.
VoxelGenLOD::ResetTilePostReachDiagnostics resets every per-kind counter between the two runs, so
the proof cannot pass vacuously.

Test fixture fix: the strate connectivity test's density-sign/coarse-lie controls use boxes up to
256 voxels, which the DiffLayer budget added in 8879045 rejected. The fixture now uses an unlimited
budget; the test measures the field, not the gameplay brush.

The tunnel SDF/core merge was attempted and NOT shipped (Codex; reviewed by me):
- The world-space core is load-bearing. WorldControlPoints keep the authored room-floor anchors and
  walkable tunnel routes before cave warp (8e42d6d, against warped floors becoming vertical
  breaks), and cf2b2ad made the world chain authoritative for option B winding/ledge walkability.
  EvaluateTunnelCoreWorld also supplies the support/room-floor ownership the tail uses.
- Replacing the world shape: player-fit 20,830 -> 19,684, walk 10,909 -> 9,934, components
  261 -> 220. Raw world evaluation without floor handling also failed (20,348 / 10,713): a
  coordinate-frame / junction mismatch, not only floor projection.
- The exact shared-segment variant was SLOWER than the committed tip: static worker +5.6%, moving
  +12.6%, LOD0 p95 +6% / +13%.
- A 0.0001-0.01% world-chain blend that merely passed the gate was rejected as cosmetic.
All merge source was reverted. This build is bit-identical to the committed tip (311CA42C, 75,482
triangles, capability 20,830 / 10,909); connectivity 6/6 + 16/16, passage open-space 14/14 and
sealed proof 22/22 pass. Every launch was serialized.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 17:42:25 +02:00
Fr0zkaandClaude Opus 5 a668833ef5 perf(generation): exact reach gating for the tunnel core (per tile) and passage carving (per 8^3 block)
Applies ce4d878's decide-once-then-prove pattern to the two largest exact targets from the re-rank
(tunnel core 20.8%, passage carving 9.1%). Codex; reviewed by me.

Measured first, where the time is:
  core world:      far 223,860 vs near 35,515,979 cycles   -> tile gate only; the core is
                                                             near-dominated (~99%)
  core tail:       far 363,440 vs near 20,110,219
  passage, tile:   far 7,315,426 vs near 30,168,796        -> refined to an 8^3 block gate
  passage, 8^3:    far 11,233,384 vs near 26,250,838
The core already uses the tunnel spatial index. An exact skip cannot remove its cost: that cost is
in tiles near tunnels. The remaining lever there is the field-changing SDF/core merge (next).

- Core reach per tile, from lattice plus halo: swept capsule reach, floor band, floor-profile
  deviation, relief bound, room-mouth ownership. The GetDensityAt tail is identity unless
  bSupportFloor, bRoomFloor or CoreSDF < -inset.
- Passage carving per 8^3 block: capsule-chain and landing-room domains, the carve threshold and
  the SmoothMin K/6 margin. Malformed descriptors stay candidates (conservative).
- voxel.TileReachCostDiagnostics (default 0) for the far/near cost split.

Soundness: the final-field debug comparison ran 130,285,889 comparisons with 0 differences; shrunk
reach (0.5) produced 140,035 differences (60,098 in the final field) and failed as required.
Field exact: canonical 07C14005 / b3e5f4c3..9b377, 64/64 per-tile ledger identical, capability
20,830 / 10,909, strate-crossing and sealed-proof tests pass.

Final DEFAULT build vs the committed-tip DLL (same command line, two interleaved runs each):
  static  worker 28.84 -> 28.10 s (-2.6%)  LOD0 p50 53.3 -> 51.5 ms  p95 94.4 -> 93.1 ms
  moving  worker 61.39 -> 60.61 s (-1.3%)  LOD0 p50 50.9 -> 49.7 ms  p95 92.21 -> 92.27 ms (flat,
                                                                        +0.06%, within noise)
Maintenance note: these reach proofs must track tunnel/passage geometry. The next round adds an
automation test for the final-field comparison so a geometry change cannot silently break them.
Ledger: 15 builds (one C2065 fixed, one access-denied path retry); rejected TileOnly/BlockLookup
variants were slower; one hung diagnostic (a non-owner PID) was stopped; wrapper quoting failures
launched no editor. No crash folders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 11:30:37 +02:00
Fr0zkaandClaude Opus 5 1d824aaab2 perf(noise): batch the three cave-warp channels through the SIMD evaluator -- exact, ~4% faster
The remove-one ablation, re-confirmed on the current build, puts the cave warp at 11.0% static /
11.8% moving of worker time: three independent Perlin3D calls per sample. VoxelNoise::Perlin3D_x3
evaluates the three warp channels together through the existing SIMD evaluator (Codex; reviewed
by me). It is wired into the generator, op-stack, classifier and room/player-fit unwarp paths.

Exact: the field is byte-identical. 0/841 tiles changed, 0 triangle delta, 0 voxels of maximum
surface displacement; canonical export 07C14005 / b3e5f4c3..9b377; capability 20,830 / 10,909;
strate-crossing and sealed-proof tests pass. The seam test (VoxelForgeMazeSeamTest, extended) has
48 neighbouring-tile and 24 cross-LOD comparisons, 0 mismatches. Before/after renders from a
camera aimed at the real arrival tunnel are byte-identical.

Cross-machine determinism: Perlin3D_x3 adds no new intrinsics, no AVX, no FMA and no runtime CPU
dispatch. It reuses the evaluator behind the compile-time gate
`#if PLATFORM_ENABLE_VECTORINTRINSICS && PLATFORM_CPU_X86_FAMILY`, so every x64 machine runs the same
instruction path; its output matches the scalar path bit for bit.

Final DEFAULT build vs the committed-tip DLL (two interleaved runs each):
  static  worker 30.83 -> 29.47 s (-4.4%)   LOD0 generation p50/p95 56.3/99.3 -> 53.3/95.3 ms
  moving  worker 67.28 -> 64.56 s (-4.0%)   LOD0 generation p50/p95 55.9/99.0 -> 53.4/96.4 ms
Ledger: every launch passed; two harness false negatives (the seam test on the game-only
validator, and a trace ended by intentional forced termination) were rerun. No crash folders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 08:41:59 +02:00
Fr0zkaandClaude Opus 5 ce4d8783e9 perf(generation): per-tile reach test skips landing and structural posts -- ~12% less worker CPU
The remove-one ablation put landing posts at ~10% and passage structural posts at ~6% of worker
time, though landings exist only near passage ends and the origin. The parked exact-cull round's
per-SAMPLE post bounds had been a net loss. This round decides reach once per TILE (Codex;
reviewed by me).

- VoxelTilePostReach.h: one per-tile reach mask (thread-local byte), set when the tile is set
  up. The origin landing, passage landing SDF/floors, native floors, tunnel air and passage
  structural posts return their input unchanged on tiles no instance can reach. Per sample this
  costs an inline flag read.
- Reach proofs come from each post's geometry (origin rooms, landing SDF and floors, native floor
  band, structural support spheres), not from comments.
- voxel.TilePostReachDebug=1 evaluates the skipped posts anyway and compares the FINAL field;
  voxel.TilePostReachScale=0.5 deliberately shrinks the reach and must fail.

Default build: 694 tiles, landing reachable on 4 (skipped on 690), structural posts skipped on 169.
Soundness: 123,958,673 comparisons, 0 differences; shrunk reach: 13,929 differences, FAIL as
required.

Final DEFAULT build vs the committed-tip DLL (same command line, two interleaved runs each):
  static  worker 34.80 -> 30.52 s (-12%)   LOD0 generation p50/p95 67.0/107.6 -> 55.8/95.8 ms
  moving  worker 75.96 -> 66.66 s (-12%)   LOD0 generation p50/p95 66.3/106.0 -> 56.0/95.8 ms
Field exact: 841/841 keyed tiles vs a fresh committed-tip trace (669,902 triangles, which includes
9260775's gap floors), export b3e5f4c3..9b377, capability 20,830 / 10,909; strate-crossing and
sealed-proof tests pass.
Ledger: all launches passed or failed where designed to; early raw-SDF debug attempts failed
until the comparator was corrected to compare the final field. No crash folders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 07:42:13 +02:00
Fr0zkaandClaude Opus 5 92607751b1 fix(passages): D-floors now exist inside the inter-strate gap; two stale tests corrected
Investigation of the two tests that had been failing for days (Codex; the fix reviewed and its
field claims re-run by me).

Real bug, introduced by adb34df: gap chunks take the compact bedrock path in GetDensityAt, which
applied the passage modifier but never the build-authored native D-floor. So the walkable passage
floor vanished exactly at the gap/strate seam, in the passages the player uses to cross strates.
Now gap chunks set Result = -Density, then ApplyPassageNativeFloorMC, then VF_ApplyXYEdgeSealMC.
The seal runs on the MC value, in the same order as before.

Stale tests, not code bugs:
- PassageLandsInOpenSpace: since d97373c production passes the cached world/warp seed to
  VF_SuggestLandingPoint, while the test still queried with the default seed. Both queries are
  fixed; the test now allows only exact graph-owned support-floor samples (the production
  bProtectAuthoredTunnelFloor contract), and anything else still fails. 14/14 landing ends,
  5,774/5,774 floor and capsule samples.
- ClassifyTileSoundness: since d38d4ca ClassifyTile covers the cell vertices g=0..CPA, but the test
  still sampled the mesher's outer normal halo. Both "holes" were halo-only (g = CPA+1). The test now
  asserts the current contract. TryProveSealedSolidTile already uses the exact core lattice, so it
  is not affected.

Verified: crossing test PASS (max gate 0.129 s); sealed-proof test 22/22; both classifier variants
pass (600 tiles each). Re-run by the orchestrator against this build: export OBJ SHA-256
b3e5f4c3..9b377 unchanged; capability 20,830 / 10,909 / walk-only 10,909.
Ledger: one DDC startup fatal (crash folder 43101DC8, 04:38) and a set of expected diagnostic
failures while locating the gap-branch omission; one malformed ablation argument (not a valid
ablation).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 06:08:59 +02:00
Fr0zkaandClaude Opus 5 cf2b2add20 feat(tunnels): option B floors -- no staircases; steep tunnels wind or drop
Owner's playtest showed a staircase (dozens of <= 45 cm risers) on a steep tunnel into a room. He
chose option B: "smooth passage and 'dramatic ledge' on tunnels that crossed the slope limit, so we
both have serpenting tunnels and sometimes dramatic drops that needs to be escalated".

Implementation (Codex; the edge rule reviewed by me):
- VF_TunnelFloorNeedsTerrace and all tunnel-floor NumSteps quantisation are removed; new profiles
  always have NumSteps = 0. Gentle floors remain continuous ramps with bounded relief.
- The room graph's bridges and a deterministic reserved walk spanning forest decide each steep
  edge. bGraphLedgeEligible = leaf || (redundant && !walk-forest edge). Eligible edges may receive a
  few large drops at control-point boundaries, never repeated small risers. Every other steep edge
  WINDS (a sinusoidal wander lengthens the WorldControlPoints route until its floor is walkable).
  Several drops chosen together cannot disconnect walking, because the forest stays walkable.
- The collect margin includes the longer wound-route envelope (window invariance). Classifier
  interval bounds include both endpoint floor levels, covering a ledge riser.

Verified:
- default acceptance (slot 1, threshold 0.9657): player-fit 20,830, walk 10,909, arrival ->
  departure connected, TunnelFloorMultiStepEdges 0, mesh 311CA42C unchanged. NOTE: this config
  has 0 steep edges, so it does not exercise option B at all.
- stress (threshold 0.2): arrival -> departure connected; player-fit 21,305 (winding adds
  volume), walk-only 10,787. Fewer walk-only cells are expected behind climbable ledges. Ledges
  4-8 voxels: 52,826 observations; 8+: 215,880; below 4: 0.
- determinism: two runs identical (164,126 triangles, 70F987AA, 0 per-tile differences).
- performance vs baseline DLL: moving worker 93.6 -> 85.2 s; LOD0 request p95 static 196 ->
  181 ms, moving 188 -> 164 ms.
- the two already-failing tests fail identically before and after (ClassifyTileSoundness: 2
  expected-uniform holes; PassageLandsInOpenSpace: upper landing endpoint mismatch).

NOT verified visually: every render reused one fixed camera, never aimed at a changed tunnel. The
arrival case was rendered at the default threshold (8/8 views identical); in the steep/wind cases
the changed views did not show a tunnel clearly. The owner's in-game check is the real acceptance.
Ledger: several intermediate capability gates failed before the final configuration passed; two
malformed launches were aborted; no crash folders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 04:23:31 +02:00
Fr0zkaandClaude Opus 5 8879045627 fix(correctness): cold-audit fixes -- tile-cache determinism bug, cache identities, lifetimes
Correctness round on AUDIT-COLD-2026-09-14.md (Codex; key fixes reviewed by me).

#7 (determinism, introduced by the tile-cache work): the refetch guard in GetDensityAt now
rebuilds on `bTileCacheContext != CP_UsesTileCacheWindow` or when the worker-local stack/core were
moved into a cache entry (`CP_ActiveTunnelDensityCache != nullptr`). A cache miss can no longer
evaluate a moved-from, empty operator stack. Test (VoxelForgeCorrectnessRoundTest): the pre-fix
binary CRASHED in the context-switch case (negative control); the fixed binary passes with 0/5
coarse and fine mismatches, and warm vs fresh origin-tile meshes are identical.

Also fixed, each with a focused test that passes:
- #3/#8: worker diff/surface/biome caches carry owner/layer identity (no cross-world reuse);
  #9: the shaft key gains LedgeSpacing/LedgeDepth and the island key BoundarySealThickness;
  #14: clearing passages bumps PassagesVersion; #21: HasAnyModInChunkRange is proportional to
  the queried range (the sealed-rock proof calls it per candidate).
- #1: the density-volume fill thread is paused/drained with generation; #2: each content manager
  joins its own decoration tasks before destruction; #15: non-finite/invalid modification
  geometry is rejected; #20: zero-width transition blend is guarded at runtime.
- Also #4-#6, #10-#13, #16-#18, #22. #19 is valid C++20 and unchanged.

Verified: canonical export b3e5f4c3..9b377; capability 20,830 / 10,909; fresh trace 841/841
identical, 0 origin-column mismatches; static mesher calls 18,078,130. Performance: two pairs
disagree in sign, and the means are final -2.5% static / -0.8% moving; no repeatable regression.

Known and NOT caused by this round: ClassifyTileSoundness and PassageLandsInOpenSpace were already
failing (15/17 in the grouped suite). Queued for investigation.

Ledger: first determinism launch hit the DDC fatal (crash folder 57DB6ADC, 23:21); the retry
exited 1 on the two known failures; one malformed multi-test launch was inconclusive; one
malformed trace launch was stopped; one launch with an unquoted project path found no test and its
retry passed. Everything else passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 00:29:46 +02:00
Fr0zkaandClaude Opus 5 39c306b524 perf(streaming): prove sealed rock solid instead of sampling it -- crossing fix's cost recovered
4c8d06f stopped content-cutting LOD0 so the player can cross strates, at a cost of +29% static
worker CPU: LOD0 tiles in the sealed rock between strates, which the old cut skipped by accident,
were sampled in full and found solid. This proves those tiles solid instead (Codex; reviewed by me).

- UVoxelGenerator::TryProveSealedSolidTile: AllSolid only for a tile whose whole lattice lies in a
  gap/seal region. It rejects player mods (HasAnyModInChunkRange), passages plus carve pads, the
  origin spine/landing, disturbances, recipes and overrides. It is narrower than the old
  ClassifyTile, which stays off in streaming. CollisionSolidTiles / CollisionNotRequiredTiles
  bookkeeping is preserved, so the collision gate's support search still resolves.
- TryProveOutOfLayoutAirTile: a separate fail-closed constant-air proof for the zero-triangle tiles
  outside the strate layout. It never supplies collision support.
- voxel.SealedSolidProof (default 1). Mining a proven tile invalidates and re-meshes it.
- A soundness test that can fail (VoxelForgeSealedSolidProofTest): 22 candidates, 22 skips, 0
  non-solid lattice samples; a deliberately weakened diff guard produces 437 non-solid samples
  and fails.

Verified: mining tile (14,-18,-8) went from proof=1 / 0 triangles to proof=0 / 8 triangles and
re-meshed; strate-crossing test PASS (max gate 0.098 s); off/on trace 899/899 identical; export
b3e5f4c3..9b377; capability 20,830 / 10,909.

Two interleaved rounds, serialized (owner-editor count 0 before each launch):
                        baseline   crossing-fixed   + proof
  static  worker        35.5 s     41.9 s           36.0 s   (+1.4% vs baseline)
  moving  worker        78.1 s     93.2 s           78.2 s   (+0.1%)
  mesher calls static   18.078M    22.280M          18.078M
Applied triangles identical: 669,834 static / 1,331,482 moving.
Runs: one early automation probe exited 3 with no log (the corrected harness passed); one
exploratory moving run was interrupted and excluded.

Note: HasAnyModInChunkRange scans every modified chunk per candidate (cold audit #21). That is cheap
today (~20 candidates), but it grows with mining; it is queued in the correctness round.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 22:08:24 +02:00
Fr0zkaandClaude Opus 5 4c8d06fd8f fix(streaming): the player can walk from one strate into the next
Owner playtest: he could not walk from strate A into B. Forcing the pawn through in the editor
showed the junction tunnel (and his edits to the lower strate) were there.

Cause: UVoxelSettings::StrateContentCutMinLevel defaulted to 0, and DA_Settings does not override
it, so the strate content band (the PAWN's strate) also cut LOD0, the collision tiles. While the
player stood in A, the passage below A's seal, the gap and strate B had no LOD0 geometry, and the
collision gate correctly refused to step onto missing collision. The band only moves once the pawn
is inside B: a deadlock. bClampViewToStrate is a different setting, which is why toggling it did
not help. Some historical fall-throughs were likely this, before the gate turned "fall" into
"blocked".

Fix (Codex; reviewed by me):
- StrateContentCutMinLevel default 0 -> 1, ClampMin 1, and
  UVoxelSettings::GetEffectiveStrateContentCutMinLevel() clamps to [1,9] at every runtime
  consumer, so a legacy asset serialising 0 cannot bring the deadlock back. LOD1+ stay banded
  inside strates and go full in the gap.
- A regression test, -voxel.TestStrateCrossing=1 (off unless passed): it walks the real manager
  passage control points with AddMovementInput and normal swept character movement, with limits
  of 10 s max gate, 120 s total and a 2-voxel floor tolerance. Old code: FAIL
  collision_gate_timeout at route 5/9 (10.0 s gated). Fixed: PASS reached_lower_strate, strate
  0 -> 1, max gate 0.092 s. The trace shows band [-7..0] -> full in the gap -> [-17..-10].

Field unchanged: canonical export b3e5f4c3..9b377, capability 20,830 / 10,909. In game, the LOD0
change in the crossing scene is 11 transition-gap tiles going from 0 to 22,390 triangles; no LOD0
interior tile changed.

COST, stated plainly (two interleaved runs, baseline vs fixed DLL):
  static  generation CPU 34.1 -> 44.1 s (+29%), density calls +23%, LOD0 request p95 +8.9%
  moving  generation CPU 78.8 -> 91.1 s (+16%), density calls +26%, LOD0 request p95 +3.1%
  applied triangles identical (669,834 / 1,331,482): the extra work is LOD0 tiles in sealed rock
  that the old cut skipped. The next round proves them solid instead of sampling them.

Visual caveat: the old default of 0 was a deliberate July choice, because LOD0/1 tiles straddling
a strate boundary mixed both strates' materials. With LOD0 never cut, that mixing may reappear
near seals. The headless runs are -nullrhi, so this still needs checking in the editor.

Launches: all serialized and waited by PID. Failures: one malformed first baseline attempt, one
over-strict early harness assertion, and one run against stale staging (it logged CutMinLevel=0),
all superseded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 19:46:35 +02:00
Fr0zkaandClaude Opus 5 7a972f513d fix(ablation): tunnel-core ablation no longer disables the fused evaluator
Salvaged from the parked exact-culling round (wip/exact-cull-20260914, 49369ac). This is the only
change ported. `if (!VoxelDensityAblation::IsTunnelCoreOff() && CP_UseOpStack)` becomes
`if (CP_UseOpStack)`: with the tunnel-core ablation on, the old condition also sent coarse tiles
to the scalar per-chunk cache path. That was the 2.9x "tunnel core off" anomaly in 6ccf371. In
normal runs (ablation off) the condition is unchanged.

Salvage round (Codex; checked by me):
- Baseline confirmed on this machine: static worker 33.98 s. So the parked round really was a
  regression (~41 s with all its switches off), not machine drift.
- The parked round's always-on cost: EvaluateSDFCached did a tile-window/candidate lookup
  unconditionally; tunnel-chain signatures added candidate range checks and per-segment AABB/Sqrt
  lower bounds; CanSkipSmoothMin read exported globals + TLS per primitive even when disabled;
  passage candidate setup ran before the disabled check; the passage/landing/post paths copied
  density and built debug closures with debug off. No FORCEINLINE was lost; the added work was
  inlined into the hot sample path.
- Not ported: tile candidates (48.26 s on vs 46.19 s off even inside the WIP), the SmoothMin cutoff,
  the post early-outs (a measured loss) and the debug hooks.

Final vs baseline DLL, two interleaved runs each: static worker 33.98 -> 33.56 s, moving
73.89 -> 74.70 s; LOD0 generation p50 66.1 -> 65.5 ms static, 64.7 -> 65.3 ms moving. Equal
within noise, as expected from a change that is inert in normal runs.
Field exact: 841/841 keyed tiles, export b3e5f4c3..9b377, capability 20,830 / 10,909.
Every launch was serialized (prelaunch UnrealEditor count 0); no crash folders; two harness-only
trace wrapper failures preceded the passing trace.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 17:26:09 +02:00
Fr0zkaandClaude Opus 5 6ccf3718ff measure(perf): per-component ablation of the TunnelNetwork path; exact worms restored
Round (Codex; reviewed by me against PERF-SAMPLED.md).

Revert: voxel.WormNoiseMode / voxel.WormLatticeStep, their storage and their switches are
REMOVED. VoxelWormField has one path again, exact Perlin with the N1 short circuit. Kept:
bEnableWorms (asset), WormsForceOff (measurement only), the exact block skip (default 0).

Ablation instrument: 14 development-only, world-changing force-off bits (voxel.TunnelAblate*),
applied on the fused, op-stack/classifier, cached SDF/core and modifier/floor paths. They resolve
once per process, are hashed into the generation fingerprint, and are compiled to zero in
UE_BUILD_SHIPPING (VoxelDensityAblation.cpp). They are a measurement instrument, never a gameplay
or multiplayer switch.

Defaults exact: canonical export OBJ b3e5f4c3..9b377, worms-region 311CA42C / 75,482 triangles,
capability 20,830 / 10,909, and 841/841 keyed tiles identical to NextHeadStaticTrace_20260913.

Static worker seconds saved by removing ONE component (two off runs vs the interleaved all-on
33.06 s; baseline noise 0.27%):
  tunnel SDF 11.4%, cave warp 10.3%, passage carving 10.1%, landing posts 9.9%,
  passage structural posts 5.6%, worms 2.4%, native floor 2.2%, origin spine 1.6%,
  room SDF 1.6%, everything else <= 1.1%
Moving (2+2 runs each) confirms the top five at 6.0-11.6%. Tunnel SDF off cuts LOD0 generation
p95 104 -> 83 ms: it owns the slow tail. The positive savings sum to 58% of the static total; the
other 42% is shared. The rows are not additive.

Unexplained: tunnel-core OFF made the static total 2.9x SLOWER (95.5 s) while LOD0 generation got
faster (p50 64.7 -> 60.6 ms) and triangles fell. The report blames the changed field, which does
not fit; more likely a slow fallback path in coarse tiles. That row is not a cost measurement.

Ledger: every Unreal process exited 0 and verified the loaded DLL; two PowerShell parser errors
happened before any process started. No crashes or retries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 09:39:57 +02:00
Fr0zkaandClaude Opus 5 c132243b30 feat(worms): asset-owned worm off switch; offline stack symboliser -- cheap-worm default to be reverted
Worms round (Codex; reviewed by me against its report and PERF-SAMPLED.md).

Part 1, a real off switch: UVoxelStrateDefinition::bEnableWorms (default true, so existing assets
are unchanged). It is deliberately not in FStrateGenerationParams, so the 83-field memo-key guard
is untouched. It resolves after composer candidates, season vectors and boundary blending, is
included in the generation fingerprint, and clamps composer overrides, so a roll cannot re-enable
it. With strength 0 the fused path skips the worm block, the op-stack builder omits the worm
source, and the interval/classifier paths do no worm work. Proof: disabled == hand-zeroed control
byte for byte (70D310A4, 74,672 triangles), capability 20,856 / 10,917, connected, zero worm rows
in the symbolised sampler. voxel.WormsForceOff is a dev-only, world-changing measurement
override, included in the fingerprint.

Part 0: the sampler writes a module map (733 modules, identity checked), and a post-exit
offline symboliser (VoxelStackOfflineSymbolizer, editor module) rebuilds the all-LOD / LOD0 /
LOD1+ exclusive / inclusive / source-line tables. A planted cost was found (5 named rows) and then
removed. DbgHelp still never runs in the measured process.

Part 2, "cheap worms": FAILED to deliver, and the default is wrong. No candidate was faster:
lattice-2/4 gave no gain, and value noise was within noise static and ~2.1% WORSE in moving worker
seconds. Yet this commit carries voxel.WormNoiseMode=1 (value noise) as the default, a field change
(29/64 test tiles) with no benefit, selected after value-noise variants were tried until one
passed capability (three were rejected at 20,821-20,828). The next round reverts the default to
exact Perlin (mode 0) and removes the no-gain world-changing experiment modes.

The finding that matters: disabling worms entirely (WormsForceOff A/B, two interleaved runs each)
saves only ~2.5% static and ~3.4% moving worker time. The sampler's "worm line
VoxelGenerator.cpp:3985 = 19.4% of LOD0" was a misattribution of inlined noise code to one PDB
line. Line-level attribution of inlined code must be confirmed by ablation before a round targets
it. That is why the worm block skip (26b3476) found nothing.

Runs: the full ledger is in PERF-SAMPLED.md. Failures: one moving wrapper hit the graceful-close
timeout and was rerun; the first offline-symboliser commandlet hit the DDC no-writable-nodes fatal
(crash folder UECC-...CE75ACD5, 08:01) and was retried with the writable Saved\DDC.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 08:19:50 +02:00
Fr0zkaandClaude Opus 5 11f0c1335b fix(diagnostics): sampler can no longer crash the run it measures; worm skip default off
Harness round. Every launched run is in Saved/HarnessFixAcceptance_20260914.ledger.txt.

- FVoxelStackSampler: two sampler-on runs had died in EndPlay (logs stop after "workers drained",
  before the latency summary). The lifecycle teardown is now serialised, registration TLS is
  tagged by session, stale worker registrations are cleared safely, and in-process DbgHelp
  initialisation/symbolisation is REMOVED. The measured process now writes raw PCs only; symbol
  resolution is deferred to after process exit. (An offline symboliser is still needed for
  readable tables; it is queued first in the next round.)
- voxel.WormBlockSkip default 1 -> 0: it measured neutral (26b3476). The code, the proven bound
  and the soundness test are kept for a tighter skip later.
- Harness (Saved/*.ps1, not tracked): a writable DDC under the plugin's Saved\DDC with
  -ddc=NoZenLocalFallback, fixing the DerivedDataBackends.cpp:208 startup fatal. UE 5.7 Windows
  was verified NOT to honour -nocrashreports for its startup monitor, so the harness runs
  unattended with dialogs forced off plus a PID-scoped CrashReportClient watchdog. Failures
  still exit non-zero, and a run without its final summary line is rejected.

Acceptance: 10 consecutive mixed runs, 7 of them sampler-on, all PASS; no new crash folders, dumps,
dialogs or lingering processes. Other runs: one smoke run failed on a PowerShell
HashSet.ToArray() compatibility bug in the harness (Unreal itself exited cleanly); its retries and
the export passed. Export OBJ unchanged (b3e5f4c3..9b377).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 05:17:27 +02:00
Fr0zkaandClaude Opus 5 26b34765d5 perf(generation): exact worm block skip with a proven 3D Perlin bound -- measured NEUTRAL
The sampler put the worm carve's first noise call (VoxelGenerator.cpp:3985, FusedWorm) at 19.4% of
LOD0. The worm only carves where N1 + N2 < T with N2 >= 0, so where N1 >= T it contributes nothing,
bit for bit. A block proven to have N1 >= T everywhere can skip both noise calls exactly.

- The Lipschitz bound for this Perlin3D implementation: L = (15/4) * sqrt(3) * 1.25 = 8.118988160,
  in scaled-N1 units, with a 1e-3 float-rounding margin. Adversarial sampling found a worst ratio
  of 3.187 (0.39 L). The existing 2D floor-relief bound (8.5) is a component bound of a different
  field and was confirmed not to transfer.
- A soundness test that can fail (VoxelForgeWormBlockSkipTest): 6,476 proved blocks, 355,945 exact
  lattice samples, 0 violations. A deliberately halved L proves a bad block; the full L rejects it.
- voxel.WormBlockSkip, default 1.

Field exact: export OBJ byte-identical (b3e5f4c3..9b377), 841/841 keyed tiles identical to
NextHeadStaticTrace_20260913, capability 20,830 / 10,909.

RESULT, stated plainly: no measurable speed gain. The skip rate is 4.07% of worm-eligible LOD0
samples and 0% at LOD1-4, because the bound is ~2.5x looser than the worst observed ratio at the
block size used. Paired on-vs-off, two interleaved runs each: static worker +2.15%, generation p50
+1.8%; moving worker -1.42%, request p50 -2.5%. Within noise. The next round turns the default OFF.
The proven bound and its test are kept for a tighter skip later (smaller or hierarchical blocks,
or a per-cell bound).

Run ledger (from Codex's report): the first build failed on an ambiguous test overload and was
fixed; two timing runs are void because the orchestrator stopped the session so the owner could
use the PC; the eight valid timing runs, the sampler run and the diagnostics runs completed. No
new crash folders. PERF-SAMPLED.md now lists all eight earlier-round UECC crash records.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 04:29:11 +02:00
Fr0zkaandClaude Opus 5 5489b5e311 perf(generation): inline exact IsFinite -- LOD0 ~11-12% faster, field-exact
The sampler's last "unknown" LOD0 bucket (~13.8%) was ucrtbase.dll!_finite: FMath::IsFinite is an
out-of-line CRT call on the MSVC game path, hit from the innermost tunnel, passage, density,
spatial-index and meshing loops.

- VoxelMath::IsFiniteFast(float/double) in VoxelTypes.h: memcpy to uint32/uint64,
  (Bits & ExpMask) != ExpMask. It is the same predicate for every bit pattern, with no FP
  comparison for fast-math to fold. The disassembly shows it inlined (movd/movq, mask, compare).
- VoxelMath::IsFinite routes through a plain global, voxel.FastIsFinite (default 1), for the A/B.
  The CRT call remains only on the switch-off branch. Hot generation-path uses are replaced; no
  _finite / std::isfinite remains in Source/VoxelForge. Tool/editor/test code keeps FMath.
- A test that can fail: VoxelForge.Math.IsFiniteExact compares against FMath::IsFinite over 22
  explicit float/double patterns (+-0, denormals, min/max normal, +-Inf, quiet and signalling NaN
  payloads) plus 2,000,000 hashed patterns each: 0 mismatches. The explicit NaNs make a NaN-wrong
  helper fail.

Field exact: 841/841 keyed tiles identical to NextHeadStaticTrace_20260913 (verdict/empty/
triangles), export OBJ unchanged (b3e5f4c3..9b377), capability 20,830 / 10,909.

Within-round A/B, game path, two interleaved runs each, sampler off (off -> on):
  static  LOD0 request p50  95.5 -> 84.8 ms   generation p50/p95  70.3/114.1 -> 62.3/100.6 ms
          worker            35.4 -> 31.6 s (-10.5%)
  moving  LOD0 request p50/p95  70.8/144.2 -> 62.0/126.1 ms
          generation p50/p95    62.6/113.4 -> 55.1/101.0 ms
          worker            102.6 -> 91.3 s (-11.0%)
Sampler-on moving: the _finite/CRT cluster is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-13 23:50:30 +02:00
Fr0zkaandClaude Opus 5 22186473f9 perf(generation): spatial index default on; the 10 s question and the unknown bucket, answered
Measurement round (Codex; checked against its report):
- Per-tile identity for ab4bbe3's always-on LOD0 changes: a fresh 841-tile HEAD trace matches
  traces recorded before them (MemoStaticOn/Off), keyed by (x,y,z,level) with
  verdict/empty/triangles compared. 0 differences, 0 missing; LOD totals identical, 669,834 in all.
- The "missing 10 s" is not the spatial index: all six TileCacheWindow x SpatialIndex
  combinations are within ~1% for a given window setting. TileCacheWindow=0 at HEAD is not
  2990009's path. It still thrashes at L3/L4 (p95 ~250 ms). The default beats 2990009 on every
  metric: static worker 48 -> 34.4 s, moving 125 -> 101 s, LOD0 generation p50 73 -> 66 ms
  static and 70 -> 62 ms moving.
- voxel.SpatialIndex default -1 -> 1: forced-on was marginally best in every pair (within noise).
- The sampler's "unknown" ~13.8% LOD0 leaf is ucrtbase.dll!_finite (RVA 0x3C080), the non-inlined
  CRT call behind FMath::IsFinite, not Core.dll. Named by parsing Core's export table (no export at
  or below 0xC080) and aggregating callers: the tunnel chain, passage landing/floor checks,
  GetDensityWithParams, GetDensityAt, the spatial index range and EvaluateSDFCached.

Gates: export OBJ unchanged (b3e5f4c3..9b377), capability 20,830 / 10,909, determinism assertion
passed.

Next: an inline bit-test IsFinite (the same predicate for every input, so field-exact).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-13 23:05:33 +02:00
Fr0zkaandClaude Opus 5 ab4bbe3680 perf(generation): field-exact LOD0 overhead removal; the unknown bucket is Core.dll
Field-preserving floor round (Codex; reviewed by me against the code):
- exact per-(sample, passage) caching of native/generic floor projections and walkable-air
  checks, behind voxel.FloorRound1PassageProjectionCache
- a zero-work disabled path for VoxelDensityProfile::FScopedTimer, unchanged when enabled
- pointer / range-for iteration in the proven-hot tunnel, passage, spatial and candidate loops
  (TArray RangeCheck was ~6.5% of LOD0 samples on the Development binaries the owner plays on)
- FVector2D temporaries replaced with scalar doubles, same operations in the same order
- FSurfaceColumn made trivial, so thread-local construction does no work. Verified safe:
  ComputeSurfaceColumn writes all five outputs unconditionally before any branch, and every read
  of Box.Cols is gated by Box.Computed

Verified: export OBJ byte-identical (b3e5f4c3..9b377); capability 20,830 / 10,909; all 8
interleaved game runs gave identical totals (841 tiles / 669,834 triangles static, 2,348 /
1,985,216 moving, the same as previous rounds). Per-tile identity for the always-on changes still
needs a check against a stored trace; that is queued first in the next round.

Game path (sampler off, two interleaved runs each):
  projection-cache switch alone: neutral (static worker +0.41%, moving -0.05%)
  vs the previous round (cfad3d1, same harness; cross-round numbers reproduced within ~1% here):
    LOD0 generation p50  static 76.6 -> 65.8 ms (-14%), moving 72.4 -> 59.5 ms (-18%)
    LOD0 request p50/p95 moving 79.4/158.5 -> 67.6/137.6 ms
    worker seconds       static 39.2 -> 33.4, moving 113.7 -> 97.9
  The always-on changes carry the gain; they have no switch, so this is a cross-round comparison.

The sampler's ~12.9% "unknown" LOD0 leaf is engine code: UnrealEditor-Core.dll + RVA 0xC080,
without a Core PDB. The old FSurfaceColumn label is gone. Next round names it by its callers.

PERF-SAMPLED.md: corrected a sentence that repeated my false "daytime use" explanation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-13 21:00:34 +02:00
Fr0zkaandClaude Opus 5 cfad3d1c18 perf(generation): gate the tile cache window by LOD; LOD0 back to switch-off speed
2de603c's tile-sized room cache cut worker CPU, but LOD0 generation was ~9% slower. The per-tile
split shows the loss was evaluation (more candidates per sample in a bigger window), not cache
building. Policy, measured per path and LOD:
- fused path: tile window at LOD >= voxel.TileCacheWindowMinLOD (default 3)
- op-stack path: tile window at LOD >= voxel.TileCacheWindowOpStackMinLOD (default 4)
- lower LODs keep the 4-chunk worker window
- voxel.SpatialIndex: -1 auto (off at fused LOD0, on elsewhere), 0 or 1

Proof caveat closed: VF_ComputeTerrainOpEnvelope adds authored terrain-op extents (pits, chimneys,
detail overrides, ...) to the invariance envelope. Ops beyond the proven 256-voxel extent, or
malformed/unloaded ones, force the legacy window, so window invariance holds for every asset.

Sampler fix: the leaf symbol kept pointers into a TMap that FindOrAdd can rehash while later
frames resolve; the leaf is now re-found by program counter after insertion.

Game path, two runs each, sampler off (Codex; checked against its report):
                       LOD0 request p50/p95    generation p50/p95    worker s
  static  optimized    106.3 / 175.9 ms        76.6 / 124.3 ms       39.2
  static  switch-off   106.3 / 173.0           75.9 / 124.1          58.5   (-33%)
  moving  optimized     79.4 / 158.5           72.4 / 123.7         113.7
  moving  switch-off    82.8 / 160.4           73.2 / 125.4         141.9   (-20%)
LOD0 is flat within noise static and slightly better moving; worker CPU keeps most of the gain.
Field exact: 841/841 per-tile triangle identity, export OBJ SHA-256 unchanged (b3e5f4c3..9b377),
capability 20,830 / 10,909.

Caution: compare these numbers only within this round. The machine is in daytime use, so
absolute figures drift between rounds.

Current LOD0 map (sampler, moving, inclusive): tunnel SDF 12.5% + tunnel core 12.3% (the same
tunnel in two coordinate spaces); passage carving 11.1%, structural posts 11.4%, native floor
4.1%, with floor projection at 8.9% inside them; cave warp ~8% exclusive. Development-build-only
overheads: TArray RangeCheck ~6.5%, per-sample FScopedTimer ~2.6%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-13 18:17:28 +02:00
Fr0zkaandClaude Opus 5 2de603cd77 perf(generation): size the room cache to the tile; spatial index; memo-key guard
Coarse tiles thrashed the per-worker 4-chunk room-cache window: 633 BuildChunkCache calls per
LOD3 tile and 1,062 per LOD4 tile. This round was started by one Codex session and finished by a
second, after the orchestrating session died at 06:06 and corrupted the first session's record.
The second session reviewed and built its predecessor's half-finished tree.

- voxel.TileCacheWindow (default 1): the room cache covers the requesting tile's footprint plus a
  fixed collect margin. It is set with TGuardValue around GenerateMesh and classify, so it never
  leaks to other work on the thread. Tile windows are capped at 64 chunks per side;
  unsupported/non-finite params fall back to the legacy window.
- Window invariance, argued by Codex: graph decisions depend on at most two bounded
  MaxTunnelLength hops, and primitives are culled only once their complete bounds are known.
  So the collect margin 2*MaxTunnelLength + max(room reach, pair/chain/tube reach) does not grow
  with the window. Caveat: oversized authored terrain-op radii are not yet in the envelope check.
- PrepareTunnelCoreCache reuses the graph, rebuilding only the exact support-column table for a
  crossed chunk.
- FChunkSDFSpatialIndex: ordered XY buckets over room/join/tunnel bounds; rejection stays exact
  and within-bucket order is preserved. Only 9-13% of candidates reach exact evaluation.
- static_asserts in VoxelStrateTypes.h: VF_STRATE_PARAM_FIELDS lists 83 fields, and their byte sum
  + 8 B padding == sizeof(FStrateGenerationParams). This closes the memo key's known gap (it
  catches omissions and size changes, not same-size replacements or padding-hidden fields).

Verified (game path, two runs each, sampler off):
- field exact: 841/841 per-tile triangle identity, 669,834 triangles; export OBJ SHA-256
  unchanged (b3e5f4c3..9b377); capability 20,830 / 10,909, connected
- cache builds: fused 139,187 -> 1,019 (-99.3%), op-stack 60,598 -> 879
- worker CPU: static 58.4 -> 40.8 s (-30%), moving 138.4 -> 121.2 s (-12%)

REGRESSION, stated plainly: with the switch on, LOD0 gets slower. Generation p50 is ~9% slower,
static 75 -> 82 ms and moving 71 -> 77 ms; request-to-ready p50/p95 is ~5-6% slower. This is
consistent across both runs of both cases. The thrash only lived at LOD2+ (LOD0 had 1.66 builds
per tile), so the next round gates the tile window by LOD and must bring LOD0 back to switch-off
speed while keeping the CPU gain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-13 15:06:24 +02:00
Fr0zkaandClaude Opus 5 2990009f3b perf(generation): memoise room player-fit points -- 3.8x less worker CPU in game
The sampler (8e40bdd) showed ~3/4 of in-game worker CPU inside BuildChunkCache ->
VF_FindPlayerFitPointForRoom, recomputed for the same rooms on every per-worker cache rebuild.
The search reads only the strate params, the room site, the strate Z bounds and the warp seed,
so its answer is pure. It is now memoised process-wide.

- The key is exact, 97 words: every FStrateGenerationParams field via VF_STRATE_PARAM_FIELDS
  (83 scalar fields, audited: no pointers or TArrays), the room centre/radii/hash/bOrigin,
  both Z bounds, the warp seed and a key version. The hash only picks a shard; lookup compares
  every word. A miss is the worst case, and eviction just recomputes the same value.
- 4096 sets x 4 ways, a per-slot reader/writer state, a per-set lock only on miss (single
  flight), 7.1 MB fixed. The search algorithm is unchanged.
- voxel.PlayerFitMemo (CVar + command line), default on. The logs confirm =0 reaches the path.
- Diagnostics: the SdfCacheBuild count moves to BuildChunkCache entry, per-tile cache_builds in
  the tile profile, tile coordinates in the startup trace, and memo stats logged at EndPlay.

Game path, two runs each, sampler off (Codex; checked against its report):
                      worker CPU     LOD0 ready p50 / p95      tiles applied
  static  off -> on   183.7 -> 48.2 s  (3.81x)   109 / 178 -> 103 / 172 ms   841 = 841
  moving  off -> on   256.9 -> 124.7 s (2.06x)   104 / 192 ->  78 / 157 ms   2226 -> 2348
Hit rate 99.95%, 463 real searches in the moving run, 0 evictions.

Field and capability: per-tile triangle counts are identical on/off for 841/841 tiles
(669,834 triangles), the export OBJ SHA-256 is unchanged (b3e5f4c3..9b377), and player-fit
20,830 / walk-reachable 10,909 are unchanged.

Next, measured by this round: coarse tiles thrash the 4-chunk room-cache window. BuildChunkCache
calls per tile are 1.7 at LOD0, 18 at LOD2, 633 at LOD3 and 1,062 at LOD4.

Known gap, closed in the next round: nothing yet asserts that VF_STRATE_PARAM_FIELDS covers every
FStrateGenerationParams field. A field added to the struct but not to the list would be missing
from the memo key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZS94UH8bSsBeg5sZaxe2Y
2026-09-13 04:48:44 +02:00
Fr0zkaandClaude Opus 5 8e40bdd92c measure(perf): in-process stack sampler; the game spends ~3/4 of worker CPU on room player-fit
A diagnostic stack-sampling profiler, off by default. A sampler thread captures the stacks of
registered generation workers with FPlatformStackWalk::CaptureThreadStackBackTrace every N us and
tags each sample with the tile's LOD. Symbolisation (inline frames included) happens after the
run, into a bounded CSV plus a summary. Registration is per tile task, never per sample; when the
sampler is off it costs one relaxed atomic load. Game: -voxel.SampleStacks=<us>. Commandlet:
-samplestacks[=us]. The streaming latency line now also prints applied tile, visible tile and
triangle counts.

Instrument checks (Codex, PERF-SAMPLED.md):
- a planted 20 ms busy loop was found at 10.5% inclusive, the expected share, then removed
- closure: 193k static / 251k moving samples, 0 capture failures, <0.75% wait frames,
  leaf resolved for 82-84%
- static game output identical with the sampler on or off: 841 tiles, 447 visible, 669,834
  triangles
- overhead at 1 ms: +4.7% static worker CPU; moving worker CPU within noise, moving timing +4-5%.
  So trust large shares, not single-digit deltas.

The finding, on the game path (the owner's instruction: profile the game, not the export):

                                all-LOD inclusive   static   moving
  BuildChunkCache                                   79.5%    80.5%
  VF_FindPlayerFitPointForRoom                      73.4%    74.0%

About three quarters of in-game worker CPU goes to player-fit searches for rooms. They are
recomputed on every per-worker room-cache rebuild, and coarse tiles span many cache windows. At
LOD0 the costs are the cave warp (3 Perlin per sample), the swept tunnel evaluated both in the
SDF and in EvaluateTunnelCoreWorld (~12% inclusive each, duplication confirmed), and the passage
structural tail (~11-14%). Candidate AABB rejection is cheap (1.5-2.2%). The 74-field params copy
does not show up as a separate cost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZS94UH8bSsBeg5sZaxe2Y
2026-09-13 03:32:29 +02:00
Fr0zkaandClaude Opus 5 c65b015f01 perf(streaming): outer tile classifier off by default; delete mode 2
Owner-approved, on the A/B measured in 83a0e4d: the classifier plus exact validation cost more
than it saved on the game path. With it off, ready p95 is 182 vs 190-195 ms, generation p95 is
125-127 vs 139-141 ms, and density calls drop 18%.

- GVoxelForgeOuterClassifierMode defaults to 0. Mode 1 stays reachable via
  voxel.OuterClassifierMode=1 as an A/B instrument; the clamp is now 0..1.
- Mode 2 (validation samples reused as the mesher core grid) is deleted: the reuse grid,
  FVoxelMesherCoreDensityGrid, GenerateMesh's ReusableCoreGrid parameter, bValidationCoreReused,
  TotalValidationCoreReuseTiles and its log field. It could not fire: it reused the grid only
  when validation disproved a uniform verdict, which never happened, and Mixed tiles never
  validate. The mode-1 validation loop exits early again on the first disagreeing sample.
- All three GenerateMesh call sites checked; the density-count pointer binds after BandVoxHi.

Verified by Codex, checked by me against the logs:
- build ok; the measured process loaded the staged DLL, SHA-256 1024D8CF..04D
- export OBJ SHA-256 unchanged: b3e5f4c3..9b377
- a run WITHOUT the override prints outer_classifier_mode=0 validation_density_calls=0,
  proving the new default took effect
- 841/841 tiles have identical triangle counts between mode 0 and mode 1. The 394 tiles mode 1
  skipped as uniform produce 0 triangles in mode 0, so the strate band cut (applied by the
  mesher, not by validation) was not hiding geometry.

Docs: ARCHITECTURE 8.10, CODEMAP (GenerateTileResult, ClassifyTile) and fable-idea T1.d record
that streaming no longer classifies by default. WORK-NEXT item 1 records the owner's playtest:
he can no longer fall through the world.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZS94UH8bSsBeg5sZaxe2Y
2026-09-13 02:06:24 +02:00
Fr0zkaandClaude Opus 5 f2044135ea fix(mesher): stop forcing the nested block classifier on -- but I cannot reproduce the 3.5x
The code contradicted its own documentation. VoxelMarchingCubesMesher.h:150 says to leave
the optional 8^3-block refinement DISABLED in streaming, "repeating a proof on ..." --
and VoxelWorld.cpp forced `Mesher->bUseBlockEarlyOut = true` regardless.

It is now off by default, behind `voxel.UseBlockEarlyOut` (CVar + command line), so the
capability stays available for the focused diagnostics where it was measured to help.

⚠️ THE HEADLINE NUMBER DOES NOT REPRODUCE, AND I AM NOT CLAIMING IT
Sol's second pass read a 3.5x from the attribution table:
    classifier disabled    587.991 ms
    classifier enabled   2,056.596 ms
My own clean A/B on this flag, quiet machine, 3 runs each:
    nested OFF   0.597 / 0.613 / 0.607 s   mean 0.606
    nested ON    0.637 / 0.599 / 0.605 s   mean 0.614
Identical within noise.

So either the 2,056 ms control exercised more than this flag does, or the 3.5x lives on
a path the 128^3 export does not take. PERF-ATTRIBUTION.md describes that figure as "the
separate classifier control", which is not obviously the same lever. Until the game-path
A/B is run, treat the 3.5x as UNCONFIRMED.

What this commit is worth on its own: the code now does what its documentation says, the
behaviour is switchable rather than hardcoded, and geometry is unchanged --
SHA-256 b3e5f4c3..9b377 identical with the flag on, off, and against bb8ec51.

⚠️ An earlier measurement pass in this round was contaminated: a local llama-server was
using 5 GB and competing for cores. Those runs came out 0.67-0.78 s with the on/off
ranges overlapping. Discarded and re-measured on a quiet machine; only the clean numbers
above are reported. Worth recording as a reminder that the machine is part of the
instrument.

NOT DONE -- the round was OOM-killed partway (same llama-server):
- the three-way outer-classifier A/B (no outer / current+discarded validation / ⭐ outer
  with validation samples REUSED as the mesher core grid). That third mode is the
  interesting one: exact validation currently costs 33^3 = 35,937 density calls whose
  results are then DISCARDED, while the mesher then fills 35^3 = 42,875. A successful
  uniform verdict saves at most 16.18%, and a Mixed verdict saves nothing.
- game-path measurement of any of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-12 13:19:42 +02:00
Fr0zkaandClaude Opus 5 bb8ec51939 measure(perf): the remaining 4.3x is ALL density evaluation -- and the classifier costs 3.5x in game
Measurement only; no density or meshing decision changed. Full table in
PERF-ATTRIBUTION.md. The profiler self-check finally passes honestly: profiled 606.128
vs unprofiled 602.341 ms, **+0.63%** (it used to inflate 11.5x).

THE ATTRIBUTION, and it is almost embarrassingly one-sided

  mesh ledger, additive, sums to the clock exactly (sum_matches_mesh = true)
    shared density-grid fill    590.923 ms   97.4913%
    tile parallel wall           10.972 ms    1.8102%   cells, interp, normals, streams
    tile merge                    4.232 ms    0.6982%
    pre-tile setup / prep         0.002 ms    ~0%
    remainder                     0.0001 ms   ~0%
    ------------------------------------------------
    total                       606.128 ms

  GetDensityAt parent: 580.184 ms worker-wall, nested inside that 590.923 ms.

⇒ **The mesher is exonerated. Marching cubes, vertex interpolation, normals and stream
building are 2.5% combined.** The whole 4.3x is density evaluation, which is where three
of us have been looking and none of us had proven it.

WHAT IS ACTUALLY BEING DONE, per 2,248,091 samples
  rooms        11,099,891 candidates ->  1,730,920 evaluated
  tunnels      28,951,420 candidates ->  3,462,565 evaluated
  tunnel-core  28,951,420 candidates ->  1,881,812 evaluated
  ~31 candidates SCANNED per sample, ~3.1 evaluated
  fused detail ran on 1,079,777 and skipped 1,168,314 samples (52.0%)
  software tunnel cache: 2,248,043 hits / 48 misses -- not the problem

The post-stack structural tail, which I have suspected twice, is small: StructuralTail
20.9 ms CPU / 1.9 ms wall, DensityStructuralPosts 21.9 / 2.0, PassageStructuralPosts
10.5 / 1.0 -- all nested samples against a 590.9 ms wall.

⭐⭐ AND THE FINDING THAT MATTERS MOST FOR THE GAME
  clean export, classifier absent       587.991 ms
  same export with the classifier       2,056.596 ms
The classifier makes generation **3.5x slower**, it is ABSENT from the export path we
have benchmarked for five days, and the GAME PATH USES IT. Its control route runs
211,289 support-column builds, 2,171,763 candidates, 1,370,314 floor queries and
2,296,243 checks -- none of which the fused export route executes at all.

⇒ Every optimisation this week was measured on a path that skips the single largest
cost the player actually pays. That is a measurement-design failure and it is mine.

Also newly visible: the full export wall is 1,004.557 ms, of which mesh is 606.128 ms
(60.3%) and OBJ WRITING is 394.273 ms (39.2%). My "0.556 s" figures have always been
mesh_seconds alone.

One real hot-path improvement landed with the instrumentation: AreCountersEnabled()
returned an unconditional `true`, so counter hooks were live in shipping builds. It is
now a relaxed load gated on diagnostic mode.

VERIFIED BY ME: build 0 errors; export 0.552 / 0.555 / 0.566 s (cf0319d was
0.563-0.566, unchanged); geometry SHA-256 b3e5f4c3..9b377 UNCHANGED.

Honest gaps, as reported: exact per-operation shares inside the density stage, physical
cache-miss counters, and a rebuilt 34f06df binary for a true A/B were all unavailable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-12 08:45:26 +02:00
Fr0zkaandClaude Opus 5 cf0319d480 perf(streaming): item 3 bought nothing, item 5 removed dead weight -- both worth knowing
Two WORK-NEXT items, one null result and one confirmed removal. Item 4 untouched.

ITEM 3 -- CANCEL OBSOLETE WORK AND PRIORITISE THE FLOOR: NULL RESULT
Clean -nullrhi, diagnostics off, 800 cm/s, 200 m of travel:

                          before            after
  request->ready p95      0.369117 s        0.367612 s
  queue wait p95          0.066949 s        0.064483 s
  generation p95          0.287926 s        0.283642 s
  p95 travel distance     2.952938 m        2.940894 m
  obsolete aborts                 0                12

Flat. Aborting obsolete tiles, sorting from the true pawn position and prioritising the
occupied/next tile changed request-to-ready by 0.4%.

⭐ AND THE NULL RESULT IS ITSELF THE FINDING. Look at the split: queue wait is 65 ms of a
368 ms p95; generation is 284 ms. **The queue was never the problem, so reordering it
cannot help.** Only 12 tiles in a 200 m run were obsolete enough to abort. The floor is
late because generation is slow, not because work is badly ordered -- item 3 establishes
that by exclusion, which is worth more than the 0.4%.

The machinery is kept: it is correct, it costs nothing, and it will matter once
generation is fast enough for ordering to be the binding constraint.

ITEM 5 -- VERDICT CACHE REMOVED. It was pure overhead.
Clean moving-session audit:

  outer  calls/probes/hits    2,109 / 2,109 / 0
  nested calls/probes/hits   19,904 / 19,904 / 0
  enclosing hits                              0
  stores 15,877, evictions 15,365

⇒ It stored 15,877 proofs, evicted 15,365 of them, and hit ZERO times. A moving player
visits unique regions; the cache was filling and thrashing a 512-entry table under a
global lock for no benefit. Same-binary A/B: request p95 0.367645 vs 0.365978,
generation p95 0.285559 vs 0.285992 -- neutral within noise, so its cost was the lock and
the linear scans rather than time we can see.

Removed: the global cache, its lock, the entry table, the invalidation path, the
cache-only profiler fields and the synthetic probe. The proof classifier stays.

⚠️ I asked for this cache two days ago on the strength of a commandlet measurement that
re-visited the same region repeatedly. That access pattern is nothing like a moving
player, and I should have said so when specifying it.

VERIFIED BY ME: build 0 errors; export 0.563 / 0.565 / 0.566 s (35de59b was 0.545-0.563,
flat as expected); geometry SHA-256 b3e5f4c3..9b377 UNCHANGED; capability 20,830 /
10,909, connected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-12 06:49:20 +02:00
Fr0zkaandClaude Opus 5 35de59bac5 perf(opstack): fused evaluator -- 1.05s to 0.56s, byte-identical, and the ~70% early-out is back
WORK-NEXT item 2. The authored operator graph is lowered at chunk-cache build time into a
fused scalar evaluator for canonical TunnelNetwork/Underwater stacks. Operators are
untouched; this is a lowering, not a de-composition. Custom recipes, composer/lateral
stacks and other archetypes stay on the interpreted block path, selected by a build-time
EVoxelOpFusedEvaluator marker with `-voxel.UseFusedEvaluator=0` to A/B it.

⭐ MY OWN A/B, same build, 3 runs each:
    fused         0.545 / 0.563 / 0.561 s    mean 0.556
    interpreted   1.066 / 1.036 / 1.033 s    mean 1.045
                                             -46.8%,  1.88x

  geometry SHA-256 b3e5f4c3..9b377 IDENTICAL across fused, interpreted, and 081cc5e.

⚠️ Codex reported this as 5.88% (2.121 vs 2.253 s). Its figures come from its staged
harness with diagnostics enabled, which inflates both sides and compresses the ratio.
Measured clean it is 1.88x. This is the fourth time a profiled number has misled us --
the instrument is the instrument, not the thing.

THE MECHANISM IS RESTORED, which is the part that matters
The audit's finding was that the old path short-circuited the cave detail suite per
sample when bNearCaveSurface was false -- historically ~70% of deep-solid samples -- and
that we had traded that for per-block pruning.

  fused export skipped detail on  1,130,230 / 2,490,664 samples = 45.38%
  capability walk skipped                                        69.26%

69.26% matches the historical ~70% almost exactly. The early-out is genuinely back.

Capability exact: player-fit 20,830, walk-reachable 10,909, Connected.

WHERE THIS LEAVES THE 8x
  pre-op-stack 34f06df   0.130 s
  now                    0.556 s      4.3x, down from ~8x

So roughly half the regression is recovered, and the remaining 4.3x is NOT operator
interpretation -- that hypothesis is now tested and spent. Codex's own read: the rest sits
in "common structural/passage work and meshing". Three of us predicted the interpretation
overhead was the whole story; it was about half.

Build 40 actions, 0 errors, loaded-module verified. Six plugin source files.

Cumulative on this region: 342.2 s -> 0.556 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-12 04:30:46 +02:00
Fr0zkaandClaude Opus 5 081cc5e459 fix(streaming): collision-ready gate -- the pawn waits for a COOKED floor, not a submitted one
WORK-NEXT item 1, plus all three of item 6. Item 2 (the fused evaluator) untouched as
instructed.

⭐ RMC DOES EXPOSE WHAT WE NEEDED, so the gate rests on a real signal:
  OnCollisionBodyUpdated                              (delegate -- diagnostic)
  SetCollisionConfig() -> TFuture<ERealtimeMeshCollisionUpdateResult>  (AUTHORITATIVE)
RealtimeMeshComponent was READ ONLY, per the owner's grant. Nothing there was modified.

THE INVARIANT
Until now nothing guaranteed a tile the player occupies has collision. apply_s ended at
mesh SUBMISSION; RMC cooks asynchronously and we recorded no completion, so no
measurement this project has ever taken said when physics could actually collide.

  - per-tile CollisionReadyTiles state, with stale-callback protection
  - HandleTileCollisionCookComplete driven by RMC's cook future
  - pawn gated at BeginPlay and pre-physics on the FEET-SUPPORT tile -- deliberately not
    the actor-centre tile, because the floor that catches a falling pawn is what must be
    cooked -- plus the predicted movement path and solid/air traversal
  - movement-component state saved/restored, so a temporary stream wait cannot
    permanently change a character's movement mode
  - new trace fields: mesh_submit_to_cook_s, collision_request_to_cook_s,
    body_event_to_future_s -- three boundaries where we had one blurred number

ITEM 6, all three:
  - LoadedTiles is now recorded only after SUCCESSFUL mesh submission, and a failed
    mesh init retries. Previously a failed init left a tile marked loaded forever -- a
    permanent hole.
  - origin spawning fixed (the zero-position guard made a pawn at exactly
    FVector::ZeroVector indistinguishable from "no pawn" -- and the owner spawns at 0,0)
  - exact core-lattice validation for uniform whole-tile verdicts, which previously had
    no safety net while block verdicts did

⚠️ I FIXED TWO COMPILE ERRORS MYSELF rather than spend another Codex round on them:
`GetMovementMode()` / `GetCustomMovementMode()` do not exist on
UCharacterMovementComponent -- MovementMode and CustomMovementMode are public fields.
Two lines. Codex did not build this round, so it could not have caught them.

VERIFIED BY ME: build 0 errors after the patch; export 1.022 / 1.055 s (aa62971 was
1.034 / 1.089); geometry SHA-256 b3e5f4c3..9b377 UNCHANGED -- generation is untouched,
as it should be for a streaming-lifecycle change.

⚠️ UNVERIFIED AND IMPORTANT: the submission-to-cook gap is still unmeasured -- Codex had
no live process to measure it in. The instrumentation exists and will report it on the
next play session. And the GATE ITSELF IS UNTESTED IN MOTION: if a support tile never
becomes collision-ready, the pawn stays frozen rather than falling. That trade is
deliberate, but the owner should watch for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-12 03:38:58 +02:00
Fr0zkaandClaude Opus 5 aa62971047 tools(trace): Play-to-steady-state timeline -- startup is 83ms, the other 8.2s is tiles
Measurement only, no optimisation, per the instruction. First time anyone has looked at
the path BEFORE LoadTile, and first time any measurement has used the owner's REAL
assets instead of the commandlet's synthetic strates.

Trace: Saved/StartupTraceRealFinalTrace.startup.jsonl -- 873 JSONL records, 268 KB,
841 tile records, bounded at 4096 with zero suppressed. Structured, own file,
stream-parseable; the 13 GB log pile taught that lesson twice.

TIMELINE
  BeginPlay                    45.585 ms
  StrateManager.Initialize     29.018 ms
  GeneratePassages              6.628 ms   (5 passages)
  first BuildDesiredTiles       2.710 ms
  ------------------------------------------
  all startup stages           ~83 ms
  BeginPlay -> steady state     8.216 s
  process launch -> stop       20.164 s

⚠️ My hypothesis was wrong: I expected layout resolution or GeneratePassages to be a
hidden one-off cost. They are 83 ms combined. **The 8.2 seconds is tiles, all of it.**
A further ~2.05 s sits between BeginPlay finishing and the first desired-set build, and
the trace attributes that to engine/map/player startup, not to us.

REAL ASSET RESOLUTION -- two things worth the owner's attention
  settings                /Game/VoxelForge/DA_Settings, seed 0
  ⭐ five slots, ALL resolving to /Game/VoxelForge/DA_Strate3
  InterStrateGapChunks    2
  MaxClipLevel 4, ClipRadius 3, RenderDistanceChunks 0
  bFarSheetRing true, but NO sheet tiles exist because render distance is 0
  ⭐ bEnableDensityVolume FALSE -- already off, no volume object, no GPU upload

DA_Strate1 (the 10.7 KB surface strate) and DA_Strate2 (rooms + water) are not in play
at all. Every strate in the world is Strate3.

THE DESIRED SET -- 841 tiles, and the shape is the story
  LOD0  343 tiles    8 m each     56 x 56 x 56 m
  LOD1  218         16 m         112 x 112 x 80 m
  LOD2  120         32 m         224 x 224 x 96 m
  LOD3   80         64 m         448 x 448 x 128 m
  LOD4   80        128 m         896 x 896 x 256 m

⭐ LOD4 covers 896 x 896 x 256 m -- about 205 MILLION cubic metres of cave -- and its
verdicts are 0 AllAir / 0 AllSolid / 80 Mixed. Not one LOD4 tile can be skipped, and
each costs 127.5 ms p50 / 291.6 ms max to generate. That is roughly 12 s of CPU spent on
geometry nobody can see from inside a cave.

TILE DISTRIBUTIONS (p50 / p95 / max ms)
  LOD  tiles  verdicts A/S/M   req->ready        generation        mesh
   0    343   98/123/122    44.9/420.8/819.5    9.6/358.2/767.6   0/341.6/726.1
   1    218   49/83/86      31.1/148.9/188.6    8.7/134.3/164.2   0/117.8/151.1
   2    120   0/39/81       34.0/61.6/76.9     10.9/43.6/63.7   9.9/40.4/60.5
   3     80   0/2/78        46.4/85.9/96.3     38.5/80.7/87.8  31.0/67.5/70.7
   4     80   0/0/80       148.2/272.8/304.2  127.5/253.8/291.6 127.4/253.8/291.5
  totals: 147 AllAir, 247 AllSolid, 447 Mixed, 669,834 triangles
  apply never exceeds 0.43 ms at any level
  zero classifier-cache and region-cache hits on a fresh process, as expected

⇒ The two costs are LOD0's 122 meshed tiles (~360 ms p95 each) and LOD4's 80
unskippable tiles (~150 ms each). Apply, queue and startup are all noise beside them.

VERIFIED BY ME: build 0 errors; export 1.034 / 1.089 s (5ddd966 was 1.013-1.075, so the
instrumentation does not distort); geometry SHA-256 b3e5f4c3..9b377 UNCHANGED. Game peak
RSS 2.07 GB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-11 20:03:24 +02:00
Fr0zkaandClaude Opus 5 5ddd96664d perf(opstack): invert the loop + prune per block -- 1.45s to 1.02s, byte-identical
The architecture round. Loop inverted from per-sample-per-op to per-op-per-block,
operators pruned per block by interval bounds, profiler moved out of the hot loop.

  my build, 3 runs:  1.013 / 1.036 / 1.075 s   (adb34df was 1.418 / 1.445 / 1.458)
                     mean 1.041 vs 1.440  =  -28.5%, 1.39x
  geometry SHA-256   b3e5f4c3..9b377  IDENTICAL to adb34df

  pruning removed 20,999 / 31,320 operator-block visits = 67.1%
  surviving ops per block:  LOD0 6.63  LOD1 6.30  LOD2 5.23  LOD3 4.41  LOD4 3.89
                            (out of 22)

⚠️ I PREDICTED THE DISPATCH OVERHEAD WAS "THE 10x". IT WAS NOT.
864,688 virtual dispatches per chunk sounded decisive and is worth ~20-28%. Real, and
worth having, but the order of magnitude is elsewhere. That is my fourth wrong
performance prediction in this project, and the pattern is identical each time: I reason
from a call site's shape instead of measuring what the data actually costs.

⭐⭐ AND THE "5-20x GAME/EXPORT GAP" WAS MY ARITHMETIC ERROR. IT DOES NOT EXIST.
I told the owner the largest unexplained number in the project was that export does
22 ms/tile while the game does 115-500 ms. That 22 ms came from dividing 1.4 s of WALL
CLOCK by 64 tiles -- ignoring that 15 workers ran them in parallel. Per-job means on the
same build:

  nonshared block export   447 ms/job
  shared-grid export       261 ms/job
  game LOD0 meshing        253 ms/tile p50

They are the same. The game is not anomalously slow; generation simply costs ~250 ms per
LOD0 tile. The shared grid's benefit is real but modest, and a neighbourhood cache is NOT
justified by these numbers -- I would have had Codex build one on the strength of a
division error.

GAME PATH, final build (841 profiles, 447 applies)
  meshed p50/p95/max   LOD0 253/521/697 ms   LOD1 79/130/135   LOD2 19/42/51
                       LOD3 36/64/81         LOD4 93/238/271
  LOD0 request-to-ready  p50 47.7 ms  p95 389 ms  max 798 ms
                         worker queue p95 84 ms, generation p95 344 ms
  at 6 m/s: p95 = 2.3 m of travel, max = 4.8 m
  empty LOD0 tiles never enter the mesher; classifier p50 8.7 ms, p95 17.2 ms
  apply is not the bottleneck: p50 0.107 ms, p95 0.161 ms, max 0.296 ms

⭐ THE BINDING CONSTRAINT IS COMPUTE, NOT POLICY:
  logical cores 16, automatic core cap 14, effective generation cap 14, CAP HIT.
Apply limits never bind. We are saturating the machine, which means the remaining wins
must come from doing less work, not from scheduling it better.

NOT DONE, with reasons rather than excuses:
- Coarse-first LOD refinement: coarse tiles carry NO COLLISION, so streaming them first
  would let the player fall through. Needs a conservative collision proxy or an L0
  handoff first. Correct refusal.
- Heading prefetch: desired tiles are sorted by distance only (VoxelWorld.cpp:1748).
  Velocity-weighted prediction with a bounded budget and expiry is the shape, not built.

A failed experiment, reported rather than buried: eagerly filling every classifier leaf
with EvalBlock regressed export to 3.2-3.3 s because it defeated the classifier's early
exit. Removed.

Build 43 actions, 0 errors. Peak RSS under 2.1 GB. Sources: Epic's Tasks System and
Level Streaming docs, Lorensen-Cline.

Cumulative on this region: 342.2 s -> 1.02 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-11 18:09:12 +02:00
Fr0zkaandClaude Opus 5 adb34dffa9 feat(passages): passages are D-tunnels now -- legacy support writer fires ZERO
The owner was right and said so plainly: "the tunnels should be a flat D with then a
bit of noise added, as it's most basic shape -- this alone should make all tunnels not
over X steepness to be walkable." Codex had reached the same conclusion from the other
direction ("the native fix is to author the inter-strate passage floor profile during
passage construction"); it had measured the current position while he described the
destination. This is that refactor.

Walkable inter-strate passages now bake their floor profile and bounded relief during
GeneratePassages. Evaluation composes a D-shaped SmoothMax floor, with final MC
ownership after the generic writers -- the same ordering fix that made the tunnel floor
native in 2260c4d.

  native compositions      7,629
  legacy support fires         0
  floor-profile fallbacks      0

  player-fit cells    20,804 -> 20,830   (+26)
  walk-reachable      10,884 -> 10,909   (+25)
  climb-reachable              10,911
  connectivity        arrival -> departure, Connected
  largest component           10,909

⭐ Support-ON and support-OFF now produce IDENTICAL export geometry (hash 07C14005,
82,273 v / 153,346 t, mesh 2.981 s vs 2.960 s). The writer that previously cost
3,359 reachable cells when disabled is now inert -- the shape provides what it used to
patch. That is the ~17.43% of banded pixels from 4f8b4a9 addressed at the source.

The legacy writer stays only as a fail-closed fallback for a malformed/non-native
passage. It is no longer load-bearing.

PUSHBACK, and it is the right restraint: only passages carrying
`bWalkableTunnelContract` -- currently the authored `Straight` style -- receive the
guaranteed D-floor. Spiral, Cascading and Worm styles do NOT silently gain a
walkability promise without a separate gradient proof. Correct: a guarantee nobody
proved is worse than no guarantee.

ROUGHNESS -- the owner's report, measured properly this time and CLOSED
He confirmed twice that changing roughness type/frequency changed generation time from
startup. The earlier sweep was a single 128^3 export at LOD0 and could not have seen
it, so this one used the game path, fixed streaming centre, identical 850-tile workload:

  skip rate   LOD0 70.8%  LOD1 57.3%  LOD2 32.6% (Cellular 31.8%)  LOD3 2.5%  LOD4 0%
  gen p95 s   FBM .472/.369/.083/.087/.145   Ridged .473/.365/.084/.084/.143
              Mixed .474/.358/.084/.090/.149  Cellular .479/.360/.083/.089/.161
  FBM frequency 0.025-0.2: flat across every LOD

No classifier-bound collapse by type or frequency. The startup difference was
workload/streaming behaviour, not a type-specific bound defect. No unsafe bounds change
was made to chase it.

⚠️ Worth noting for later: the skip rate collapses with coarseness -- 70.8% at LOD0 to
0% at LOD4. That is expected (a LOD4 tile spans a huge volume and almost certainly
contains something) but it means the classifier does nothing for exactly the tiles that
cover the most ground.

VERIFIED BY ME: build 0 errors; export 1.418 / 1.445 / 1.458 s (4f8b4a9 was
1.353 / 1.430 / 1.432); 153,346 triangles. Peak game RSS ~2.05 GB.

Sources consulted: Epic's UE task-systems documentation for the worker/game-path
distinction, and Lorensen-Cline for the discrete isosurface context.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-11 16:29:49 +02:00
Fr0zkaandClaude Opus 5 4f8b4a9a27 fix(gen): the phantom terracing is the PASSAGE support writer -- plus a region verdict cache
The owner played the build and reported four things. Three are answered with evidence,
one is disproven, and my own leading hypothesis was wrong.

⭐ 1. THE PHANTOM TERRACING IS NOT MARCHING-CUBES ALIASING
He reported stairs surviving with bTunnelFloorTerracingEnabled = false. I suspected MC
lattice aliasing on a near-planar floor; I was wrong. An A/B over the same mesher,
counted in pixels:

  graph-tunnel floor removed     2,813 / 147,456 px changed   1.91%
  passage support removed       25,695 / 147,456 px changed  17.43%

⇒ The bands are the INTER-STRATE PASSAGE SUPPORT WRITER, which is independent of the
tunnel terracing flag. That is exactly why turning terracing off did nothing: he was
disabling the wrong system. FCaveTerraceMod is inactive (TerraceStepHeight=0) and was
never involved.

⚠️ It cannot simply be switched off. With it disabled:
  fit 20,804 -> 20,511, walk-reachable 10,884 -> 7,525, components 260 -> 272.
Route still connected, but a third of reachable space gone. It stays as a correctness
backstop. The native fix is the same pattern we just applied to tunnels: author the
inter-strate passage floor profile during passage CONSTRUCTION. Codex judged that
refactor too large to risk this round and said so rather than attempting it.

MC aliasing remains theoretically possible at shallow angles (Lorensen & Cline 1987),
but the same-mesher A/B proves our writer dominates. Dual Contouring (Ju et al.) was
considered and rejected as a disproportionate mesher/topology change.

2. THE ROUGHNESS COST DEFECT DOES NOT EXIST -- my hypothesis, cleanly disproven
  FBM 2.91s / 66.92% skips · Ridged 2.85s / 66.53% · Mixed 2.92s / 66.82%
  Cellular 2.99s / 66.43% · FBM frequency 0.025-0.5: 2.88-2.99s, 66.80-67.04%
I predicted a shared bound across noise types collapsing the classifier. The code
already carries separate conservative bounds per type. No roughness code was changed.
⇒ Whatever the owner saw is not steady-state export cost. Most likely the in-editor
regeneration a parameter change triggers, which is the full-rebuild cost, not the
per-sample cost. Worth confirming with him.

3. ROOM MOUTH RISE -- his idea, implemented, off by default
  RoomMouthRiseBlendVoxels, default 8.0
  blend 8   fit 20,804  reach 10,884  40,429 v / 75,416 t   capability-NEUTRAL
  blend 24  fit 20,792  reach 10,874  40,337 v / 75,232 t   REJECTED, loses capability
Relief preserved at blend 8 (dihedral p50 2.5584, p90 33.7191). The chosen camera did
not see a mouth bench, so no visual claim is made.

4. ⭐ VERDICT CACHE WITH REGION REUSE -- answers "why re-prove what we proved?"
  130 classification calls -> 1 store, 129 hits, of which 128 ENCLOSING-REGION hits.
The region level is the one that matters: deep rock is empty thousands of chunks at a
time, and one proof over a large box now serves all of them.

Soundness, which is the whole risk (a stale "empty" silently deletes geometry):
512-entry bounded LRU; stores ONLY AllAir/AllSolid, never Mixed; key includes seed,
layout/lifetime id, params fingerprint, diff version, density/seal settings, support
mode, lattice step, origin and extent; dynamic season/composer/terrain-op/biome
configs FAIL CLOSED and bypass reuse. Deliberately session-resident, not on disk --
cross-process persistence cannot serialise all asset/layout/edit state safely yet.

VERIFIED BY ME: build 0 errors; export 1.353 / 1.430 / 1.432 s (2260c4d was
1.320 / 1.424 / 1.439); geometry SHA-256 b3e5f4c3..9b377 IDENTICAL to 2260c4d.
Peak RSS ~1.75 GB. Determinism repeat equal.

⚠️ My composer hypothesis was also wrong: I suspected
`bTunnelFloorTerracingEnabled = CategoryRng.FRand() < 0.75f` in the composer was
overriding his setting. It is not the cause here. The design question it raises is
still open and worth the owner's view: should a composer roll be allowed to overwrite
an explicitly authored value? Otherwise "I turned it off" never reliably means anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-11 13:13:28 +02:00