Commit Graph
250 Commits
Author SHA1 Message Date
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 cc04c0bd4e chore(tools): untrack a stray __pycache__ artifact
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-18 05:13:24 +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 76bc8bce97 docs(work-next): room-wins result, checks passed, and the perf regression it introduced
Crossing PASS (248 m, 26.6 s), TilePostReachProof PASS, DetSinCos PASS, canonical export identical with -voxel.CrtFma3=0 (86A99075), walk Connected 42,870 / 17,413. FAILED: one S/M/S/M pair on the owner's staged map shows worker generation 47.7 s vs 30.7 s static (+55%) and 83.1 s vs 60.9 s moving (+36%), request p95 +19-28%. The room-ownership containment test now runs per sample in the generic tunnel SDF, the core evaluator and both native fallbacks. Next round: same geometry (export hash must stay 86A99075), cost back to the tip's level.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-18 01:04:02 +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 01cb9d47f1 docs(work-next): tunnel playtest night -- rib cause, removed floor, what's queued
Records the owner-playtest findings (stale editor build, owner assets vs harness defaults, the 0.1 slope freeze, ribs with every terrace setting at 0), the proven rib cause and its fix on fix/tunnel-shape, the reverted Catmull-Rom attempt and why its evidence was wrong, the in-editor ablation procedure (cvar read once at first Play), current canonical numbers, and the queue awaiting the owner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-09-17 02:49:18 +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 bf9384a1d5 docs(work-next): bet A killed, bet B cleared rung 1, rung 2 interrupted by the usage limit
Bet A: the per-sample lookup it would remove is 5.1-6.1% exclusive, and feature-major visiting would touch 5.2-6.6x more lattice points than the current code evaluates; the swept kernel is branch-divergent. Killed on its own numbers. Bet B: 20.04% of static LOD0 density calls are exact duplicates with 80.6% donors already finished (~2.96 s of 18.3 s), moving 20.70% / 63.3%; re-derived independently from the raw trace. Its rung-2 prototype is committed unvalidated on sol/tile-sample-reuse after the Codex usage limit ended the round mid-rebuild; resume brief in the scratchpad.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-16 04:09:40 +02:00
Fr0zkaandClaude Opus 5 8722293f6b docs: deterministic maths recorded (new canonical hashes); stale landing-connector paragraph corrected
- WORK-NEXT: state after 2026-09-16 -- Sol's review, the sol/deterministic-math result, new
  canonical export/worms/field hashes, and the next branches in order.
- CODEMAP 3.2: VoxelMath::DetSinCos / IsFiniteFast rows.
- ARCHITECTURE: passage landings have had no connector to the (0,0) room since the owner's
  d97373c playtest correction; the doc still claimed one. Records the open gap Sol found (the
  random-reach fallback when the footing query declines is unproven) and the proposed fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-16 00:56:04 +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 cda082fa10 docs(design): Sol reads the code -- three changes worth testing, one determinism hole
Round two of the owner's "build it every better way" question: Sol's round-one session was forked
read-only and given the repository, to say what could reasonably change and how each change would
prove its worth. Advice only; nothing was changed, built or run.

Sol's verdict: don't rebuild. The representation, mesher, clipmap and operator stack are already
close to its clean-slate design. Three things are worth testing, each with a cheap
instrumentation-only first step and explicit kill criteria:
- tile-wide, feature-major tunnel evaluation (inferred 8-18% of worker CPU);
- exact density reuse across adjacent gameplay tiles (theoretical ceiling ~23%);
- a canonical numeric contract for world-deciding maths (correctness, ~neutral on speed).

The orchestrator verified against the code and the UE 5.7 engine source:
- FMath::Sin/Cos resolve to the CRT sinf/cosf on Win64 (FMA3 variant chosen at runtime);
- per-sample lookup is ~5% exclusive, so round one's "the loop direction is the largest gap"
  overstated it;
- worker-thread parse-once flags, raw-memory CRC cache keys and a docs-vs-code contradiction on
  landing connectors are real.
Round one's note gets a pointer to the corrections.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 21:37:24 +02:00
Fr0zkaandClaude Opus 5 cf9904e8b1 docs(design): Sol's clean-slate architecture answer, with a determinism risk it surfaced
The owner's reframing idea: after local "find and fix the hot spot" rounds hit diminishing
returns, we asked Sol High, with no code and no file access, how it would build this project in
every better way. It recommends feature-rasterised sparse bricks (each feature stamps into the
bricks it touches instead of every sample searching for features), noise only in surface bands,
coarse LOD from finer data, a fixed-point determinism contract and deadline-driven collision-first
streaming.

The orchestrator's check against our code: 34 FMath::Sin/Cos calls sit on the generation path
(per-sample strata line/rib detail and passage/tunnel shape building). The MSVC C runtime switches
those to FMA3 implementations at runtime, so cross-machine bit-identity is not guaranteed. Not
changed yet; recorded for a deterministic replacement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 19:45:37 +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 c7b832db4c docs(work-next): the tunnel SDF/core merge is closed -- the world core is load-bearing
Records why the merge must not be re-proposed without a new idea. WorldControlPoints keep the
authored room-floor anchors and walkable routes before cave warp; replacing them lost 1,146 fit /
975 walk-reachable cells, and the exact shared-segment variant was slower. The remaining large
costs are the geometry itself, so the exact-skip perf push has reached diminishing returns; the
reach proofs are now guarded by an automation test (8ef1a3b).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 17:42:54 +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 cfd81b5ce9 docs(work-next): state after 2026-09-15 -- gameplay fixes, correctness, perf, current cost map
Records the playtest-driven fixes (strate crossing, sealed-rock proof, option B floors, gap
D-floors), the cold-audit correctness round (including the tile-cache determinism bug), the perf
gains measured against committed-tip DLLs (static worker ~184 s -> ~28 s over the whole push, same
world), the current remove-one cost map, and the parked exact-cull branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 11:31:09 +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 0208e93606 measure(perf): re-rank component costs on the current build -- the tunnel core is now the largest
Measurement only (Codex): the same remove-one ablation method and command line as 6ccf371, all 14
voxel.TunnelAblate* bits static x2 and the top five moving x2, on the committed tip after the
landing-post, cave-warp, gap-floor and option B changes.

Static leaders now: tunnel core 20.84%, tunnel SDF 12.64%, passage carving 9.08%, cave warp 6.68%;
27.62% remains unattributed/shared. The tunnel core could not be measured at 6ccf371 because of the
ablation gate bug fixed in 7a972f5. The two tunnel evaluations together are now about a third of
worker time. Next targets as proposed: tunnel SDF/core merge (field-changing), passage carving
reuse (exact), cave warp result reuse (exact). Full tables and the launch ledger are in
PERF-SAMPLED.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-15 09:30:22 +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 481e7bbaee docs(audit): cold read-only examination with verification notes -- one determinism bug confirmed
A read-only Codex examination (no project background in the brief; it did read the root docs) with
22 code-level findings, kept verbatim, plus the orchestrator's verification notes:
- #7 CONFIRMED: after VoxelGenerator.cpp:2968 moves CP_OpStack into the tunnel cache, a miss under
  the other tile-cache context skips the refetch (:2723-2725) and evaluates an EMPTY stack. The hit
  path makes the spawn column X=Y=0 reach this after an ordinary hit. Introduced by the tile-cache
  work; queued first in the next correctness round.
- #3/#8 are real but narrower (multiplayer peers share seed and layout by design).
- #19 is not a defect under C++20.
The rest are to be verified individually before fixing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 20:14:35 +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 7ece3ceafc docs(work-next): state after 2026-09-14 -- perf ledger, measured costs, what failed and why
Records in the repo what two days of perf rounds established: the in-game worker CPU trajectory
(~184 s -> ~34 s static), the remove-one ablation costs, the three approaches that were tried and
not kept (with where the parked work lives), the measurement lessons that each cost a round, and
the next candidates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 17:26:44 +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 b839edd3a3 docs(work-next): correct cfad3d1's false "machine in daytime use" explanation
cfad3d1's message blamed cross-round drift on the owner using his PC. That was my unverified
guess, and it is false: he had not used the PC all day. The gap is systematic. The switch-off
baseline measured 58 s static worker CPU in two rounds hours apart, against 48 s at 2990009, and
LOD0 generation p50 is ~4% slower than at 2990009. Something left on with
voxel.TileCacheWindow=0 costs ~10 s. Queued: attribute it with in-tree switches (first suspect:
voxel.SpatialIndex auto-on at fused LOD1-2), never by running old commits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-13 18:51:47 +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 83a0e4d85c measure(classifier): outer classifier is net-negative in game; nested 3.5x IS real
Three-mode outer-classifier A/B on the headless game path, nested refinement off in all
modes, clean and diagnostics off. Runs made by the forked Codex session; the table was
assembled and checked by me from its logs (Saved/OuterABMode*_20260912.log,
OuterABMove*Headless_20260912.log).

  static  ready p95   mode0 182.4-182.7   mode1 189.7-194.8   mode2 190.0-194.7 ms
          gen p95     mode0 124.9-127.0   mode1 139.1-140.6   mode2 140.3-141.3 ms
          worker CPU  mode0 184.9-193.5   mode1 201.2-202.3   mode2 201.2-201.3 s
          density     mode0 18.05M        mode1 21.89M        mode2 21.89M
  moving  ready p95   mode0 198.9         mode1 208.0         mode2 210.7 ms
          density     mode0 57.7M         mode1 69.6M         mode2 69.6M

The outer classifier costs more than it saves: with it removed (mode 0), every latency
figure improves and density calls drop by about 18%. Mode 0 also cannot remove geometry,
because it skips nothing.

Mode 2 (reuse the validation samples as the mesher's core grid) recorded 0 reuse tiles in
every run. That is by design, not a bug: the grid is only reused when validation DISPROVES
a uniform verdict, and it disproved none. Mixed tiles, which are the ones that need a
grid, never run validation at all.

Recommendation recorded in WORK-NEXT.md, pending the owner: default the outer classifier
mode to 0. The code default is NOT changed in this commit.

CORRECTION to f204413, whose message says the nested classifier's 3.5x could not be
reproduced. That was my error. I passed the streaming console variable
-voxel.UseBlockEarlyOut=1 to the export commandlet, which reads its own -blockearlyout=
argument (VoxelForgeExploreCommandlet.cpp:381, :1093), so both of my runs had nested
refinement off. Re-run with the right flag:
  -blockearlyout=0   0.553 / 0.572 s
  -blockearlyout=1   2.036 / 2.060 s     3.6x, geometry SHA-256 b3e5f4c3..9b377 identical
The 3.5x was real. Turning nested refinement off in streaming (f204413) was worth it,
and Sol's reading of the table was right where mine was wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZS94UH8bSsBeg5sZaxe2Y
2026-09-12 22:02:53 +02:00
Fr0zkaandClaude Opus 5 667da8df14 docs(work-next): promote item 4 to performance architecture
Written by the classifier-fix Codex round before it was closed. The classifier is a
second evaluator -- interval proofs, exact certificates and final-field validation --
beside the canonical lowered one, and the runtime can validate the same tile again
before meshing. Any classifier kept long-term must consume the lowered representation
and reusable density data.

That round's other uncommitted edit was NOT kept: it flipped
GVoxelForgeOuterClassifierMode 1 -> 0, turning the outer classifier off by default.
That was A/B setup for an unfinished experiment, not a measured decision, so the
committed default (1, classifier on) is restored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9DLqyXrJxG4TojzDrSmVw
2026-09-12 13:34:28 +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