85a803b8739a815058c367147c587fd5b078c3eb
30
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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 |
||
|
|
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
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
39c306b524 |
perf(streaming): prove sealed rock solid instead of sampling it -- crossing fix's cost recovered
|
||
|
|
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 |
||
|
|
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
|
||
|
|
c65b015f01 |
perf(streaming): outer tile classifier off by default; delete mode 2
Owner-approved, on the A/B measured in
|
||
|
|
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
|
||
|
|
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 (
|
||
|
|
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 (
|
||
|
|
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 (
|
||
|
|
5156084cac |
audit(gen): which post-hoc ops are tunnel-floors in disguise -- and the X+2A budget is unsound
The owner's framing for the night: "if it sees performance opportunity by unifying
some process that should be, or like the tunnels, have it be native more than patching
it after generation". This is the audit. No generator behaviour changed; the only code
here is request/generation timing instrumentation in VoxelWorld.
STRUCTURAL-POST AUDIT -- verdict per operator
FBoundarySealOp 0.000011 us/sample KEEP. A boundary condition, not a workaround.
FXYEdgeSealOp 0.000054 us/sample KEEP. Domain constraint; identity today with
WorldRadiusVoxels=0.
FOriginSpineOp 0.000588 us/sample PARTLY family, but origin anchoring and
ordering are special.
FPassageCarveOp 0.019977 us/sample Could share the volume/floor representation,
but its boundary-crossing order and
current-density carve are load-bearing.
floor protection 0.061298 us/sample ⭐ THIS IS STILL THE REAL PATCH.
TunnelAuthoredFloorProtectionClamps fired 29,634 / 29,634 -- every authored floor
sample -- while the named support/room fallbacks fired ZERO. So the backstop is not
a rare safety net; it is doing the work, and the tunnel floor is not yet native.
⭐ AND IT NAMED THE PATH OUT, which I had not seen: the floor is authored at build time
but LATER WRITERS can still refill or erase the clearance, so a clamp re-asserts it.
Native means either composing the floor AFTER all writers, or making every later
writer floor-aware with a proof it cannot violate the invariant. Removing the clamp
today would be unsafe. It did not remove it.
PRIMITIVE FAMILY -- valid, but my proposal was too crude
Algebraically sound: solid + floor via SmoothMax + bounded relief + smooth union.
Rooms, swept tunnels, landing rooms and some passages can share a cached
walkable-volume DESCRIPTOR (shape/profile, baked floor, relief params, mouth/room
ownership, capability metadata).
⛔ But they should NOT become one undifferentiated operator: tunnels are swept
multi-segment chains, passages cross structural boundaries, origin landings have
special finite geometry, seals are domain conditions. Flattening those would WEAKEN
interval proofs and operation ordering -- the opposite of what I hoped unification
would buy. Renaming the passage carve would not help either; it Lerps against
incoming density, so its global bound stays unbounded regardless.
X + 2A CLEARANCE BUDGET -- SETTLED, AND IT IS UNSOUND AS SPECIFIED
A surface-displacement budget needs BOTH an upper bound on field perturbation AND a
lower bound on the gradient near the zero surface. The second fails at flat or
degenerate crossings, and the current-density Lerp/Min operators make a global bound
unavailable. Perlin derivative bounds alone do not fix it.
⇒ Usable only inside separately proven primitive regions; elsewhere the validator and
backstop must stay. The owner and I both assumed interval propagation already gave
us this. It does not, and now we know why rather than just that.
ITEM ZERO -- the suspected in-game regression: NARROWED, NOT CONFIRMED
Queue wait is real, but meshed latency is primarily GENERATION time, not queue. One
EMPTY LOD0 generation took 551.7 ms, so it is not pure queue saturation either.
RoomGraphSource measures 0.278939 us/sample, which cannot explain a 400-1000 ms mesh.
Codex declined to call it a confirmed code regression on this data. Still open, and
it now points at the known-unsolved LOD0 mixed-classifier fallback rather than at the
floor work.
REFERENCE ARCHETYPE -- costs, and a correction to my arithmetic
CorridorRadius 12.5 voxels is a 6.25 m DIAMETER (I said 3.1 m), and validation caps
it at 16 voxels. The 15-20 m reference hall needs a separate gallery archetype or a
larger cell-size budget; raising the value violates current lattice/wall assumptions.
FCaveTerraceMod is a true no-op disabled (0.000531 us/sample); enabled it adds two
cached SDF probes, estimated lower bound ~0.56 us for a qualifying sample -- estimated,
not measured. Columns belong to slab generation; TunnelNetwork needs a baked column
op. Water is presentation, not rock density, and null-RHI cannot price it.
Build 0 errors. No RMC files, links, historical checkouts or logs touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
|
||
|
|
383ec86c37 |
perf(apply): stutter is NOT the game thread -- and the flat floor cannot be relaxed
Two things the owner reported from playing |
||
|
|
d38d4cabef |
perf(gen): interval propagation -- blocks can finally be PROVEN empty. 1.55s -> 1.21s
Task 4 ran 11 hours and was killed by system memory pressure before it could file a report. The work survived, builds clean (0 errors), and is verified faster with byte-identical output. This commit is my verification, not its summary -- it never got to write one. THE STRUCTURAL FIX |
||
|
|
0576e800c4 |
perf(gen): the landing work was per-voxel -- 342s to 2.6s, same world
The owner played |
||
|
|
faa62d419e |
feat(runtime): the composed world can BE the game world
Jahni: "can you not implement those settings? as in, have them be live in
game?" This is that. The composer stops being a lab tool.
THE ARCHITECTURAL LINE, which is the whole design (COMPOSER-NOTES §3.1):
COMPOSITION stays editor-only, EVALUATION becomes runtime. Rolling,
measuring, preview, selection, promotion, the corpus -- none of it reaches
a player's machine. What crosses into the runtime is exactly two things:
reading a season manifest, and materialising a recipe into an op stack.
UVoxelSeasonAsset a cookable UPrimaryDataAsset holding the manifest,
so a season ships in a packaged game. The JSON
stays the reviewable, git-diffable artefact.
UVoxelSettings::Season optional. Unset => behaviour is exactly as today.
GetEffective*() season seed / spine radius / world radius / season
number override the authored values when assigned.
The test deliberately sets DIFFERENT authored
values, so "the season is authoritative" is proved
rather than coincidental.
⭐ MULTIPLAYER DETERMINISM, PROVED: two independent runtime managers built
the same season and compared 393,216 density samples BIT-IDENTICALLY. This
is the property the whole multiplayer model rests on -- replicate seed +
layout + diff, never geometry.
⭐ AND A STALE SEASON IS DETECTABLE, NOT SILENT. Initialize() now returns
bool and an assigned-but-invalid season NEVER falls back to the authored
pool. Falling back would hand a client a world that looks fine and is not
the season -- silent divergence, the worst failure mode in a shared world.
GetCurrentSeasonContentHash() is exposed BlueprintPure to compare at join.
HasComposerRecipeOverride() is gone. It force-classified every custom recipe
as Mixed, which was correct while box verdicts were only proved for
hand-written stacks -- but
|
||
|
|
d717bc74ff |
feat(editor): walk a composed candidate in PIE + why rooms read angular
PART B -- the thing Jahni asked for: "any way to test those in game?" A candidate existed only as measurements and PNGs. Now he can stand in one. Select the VoxelWorld actor in PIE -> Live Edit|Composer -> ComposerSeed / ComposerCandidateIndex / ComposerTargetStrateIndex / bComposerRollStructure -> click Apply Composer Candidate. The button and the automation tests call the SAME roll wrapper, which was the load-bearing requirement: a second roll path could drift, and then he would be judging a different world than the contact sheet describes. A determinism sample is logged on apply (seed, index, recipe, archetype, metrics, epoch, eight-point density verdict) so drift is caught the day it happens rather than in a month. Hazards handled, since this is the first composer code to touch the live runtime path: preflight materialisation before any live mutation, generation and decoration tasks drained, PassagesVersion bumped to invalidate thread_local caches, reuse of the existing RegenerateAllChunks / Epoch invalidation rather than a new path, no Initialize() or GeneratePassages() during apply (the re-entrancy trap), DiffLayer locks untouched, and custom recipes conservatively force ClassifyTile to Mixed. Scope is deliberate: a DENSITY-ONLY overlay. Layout height, authored passages, content, water and biome metadata stay from the existing slot; RebuildStrates() clears it. A full composed layout replacement needs a safe rebuild path that does not exist yet. Editor-only, verified: a Shipping build succeeds with the bridge excluded (96/96 actions), so §3.1's "offline only, never on a player's machine" still holds. ⚠️ UNVERIFIED BY DESIGN: a button cannot be tested headlessly and Codex was told not to claim it works. Jahni presses it and confirms the visual plus the "determinism=PASS" log line. PART A -- why rooms read angular, measured not guessed. One of three room shapes is VoxelSDF::RoundedBox: ShapeA = (.8 Rxy, .8 Rxy, .8 Rz) ShapeR = .25 Rxy so rounding is 31% of the horizontal half-extent but 78% of the vertical. Corners are rounded; the broad planar FACES survive. SmoothMin (blend 4) softens room/tunnel junctions not isolated corners; the cave warp bends geometry but preserves flat regions; roughness is surface-scale only. The name is accurate -- "rounded box" is not "blob". NOT ACTED ON. Jahni corrected his own read: the angular archetype is Maze (lattice source, rectilinear BY DESIGN and correct), and TunnelNetwork is "alright-ish". Codex's recommendation is recorded for if he ever wants it: ~5-10% boxes, 10-20% capsules, rest ellipsoids, or a larger ShapeR. Also closed REVIEW_FINDINGS EVoxelPassageType/Style as WONTFIX, enacting a 2026-08-16 judgement that recommended closure and was left open. Build clean (Editor + Shipping), 25 succeeded / 0 failed, run by me. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
302cf16681 |
fix(world): the voxel field is in ACTOR space -- make every entry point agree
The city's hole is not at world (0,0), so the AVoxelWorld actor has to move.
It couldn't: the plugin was only HALF actor-relative, and had been for a long
time without symptoms because the actor always sat at the origin.
Already correct: GetBiomeAtWorldLocation, GetVoxelSurfaceHeightAt,
UpdateDecorations, UpdateLandmarks, UpdateWater, UVoxelDensityVolume::Update,
the passage debug draw, the clipmap material.
Not correct: the streaming centre, the streaming anchors, all six carve/fill
entry points, GetStrateAtPosition, and UVoxelAtmosphereManager::UpdateForPlayer
-- whose own comment documented the assumption out loud ("actor at
origin/identity"). Carving would have landed offset; the strate lookup would
have been quietly wrong by the actor's Z.
The trap worth recording: because the content managers already convert
internally, converting PlayerLastPos once at the source would have DOUBLE-
converted four call sites. The fix has to be per-boundary. That was only
visible after reading each consumer -- the estimate before that read was wrong
in both directions.
WorldToLocalCm / WorldToLocalVoxel / LocalVoxelToWorld are now declared as the
only sanctioned boundary between Unreal coordinates and voxel coordinates, and
the real deliverable is the invariant next to them: no `/ VOXEL_SIZE` applied
to a parameter named `World*` outside those three functions. A grep can check
that. The risk was never a missed site among the ones found -- it is the next
one nobody notices.
Also adds a translation-only guard in BeginPlay. Tiles, ClassifyBox verdicts,
the clipmap window, culling and streaming distance all assume world-axis-
aligned boxes; a rotation or non-unit scale breaks that structurally rather
than gracefully, so it logs an Error naming what was set.
With the actor at the origin every conversion is identity, so this is a no-op
until the actor actually moves. Built and confirmed working.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPUuCtn5zTLui6WybLbkNb
|
||
|
|
49a9959aed |
fix(world): drain workers before mutating strate layout/passages (VF-01)
Initialize does StrateLayout.Empty() and Passages.Empty()/Add() -- freeing and
reallocating both -- with no guard, while mesher workers read them through
AnyPassageNearBox, EvaluateModifierSDF and FindSlotIndexForChunkZ. The epoch is
bumped AFTER, so previous-epoch workers are live during the mutation; the epoch
rejects a finished result, it cannot make a read of a freed allocation safe.
Same class as the DiffLayer.ChunkMods carve-vs-stream AV that ModsLock fixed.
Chose the drain over the two alternatives:
- FRWLock on the arrays: correct, exact in-repo precedent, but a read lock on
the per-voxel hot path (~43k/tile) would contaminate the perf measurement
CODEX-TASK-001/002 are queued to take. Worst possible timing.
- Immutable generation snapshot (Sol's proposal): right long-term, refactors
the whole StrateManager API surface. A design conversation, not an agent's
unprompted call.
- Drain: zero hot-path cost, reuses the machinery EndPlay already proved, and
its stall lands only on human-initiated editor actions, never during play.
FScopedGenerationPause raises a new bGenerationPaused (deliberately NOT
bShuttingDown, which means teardown) and drains both reader populations: chunk
tasks via ActiveTaskCount and decoration tasks via a new
WaitForDecorationTasks, whose refactor also removed NotifyShutdown's duplicate
spin loop. On timeout it does NOT mutate -- logs an Error and leaves the world
consistent. A timeout that proceeds is what the bug already does.
RebuildStrates, OnObjectModifiedInEditor and ChangeSeed cannot reach Initialize
without the pause. BeginPlay untouched (no tasks yet). EndPlay untouched --
VF-02's 3s timeout is a separate deliberate decision.
Verified: four files, no density/mesher file, so terrain cannot move.
ShouldAbortWork replaced three existing checks, adding none. No worker path
waits on the game thread, so the drain is bounded.
Not built -- Jahni builds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
69fa73e07e | tmp | ||
|
|
cb61c8b2e4 | potential perf regression | ||
|
|
6875614002 | Fix Decoration Placement | ||
|
|
e6cd852129 | Another pass | ||
|
|
db558d9e14 | j | ||
|
|
f030eec08a | Upload of all files, starting point |