Author SHA1 Message Date
Fr0zkaandClaude Opus 5.5 54ba1b49ab wip(l59): open helix shaft (ledge open to the core) -- PARKED: jagged inner ramp edge, audit ROI missed it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-25 20:27:04 +01:00
Fr0zkaandClaude Opus 5.5 6c6cc37e1d feat(core): native lattice generator, slice 1 -- helix-ramp spine + small maze (L58); research R27
Core.Lattice.PrimitiveField v1, opt-in per strate (legacy Maze/VerticalShaft stay legacy): resolves the real
arrival and departure mouths first, plans a mandatory spine, then maze cells and two dead ends (9 rooms,
9 edges, stable IDs). The spine descends 20 m through four helix turns (6 m radius, 3 m clear width, 5 m
pitch, 2.6 m clearance; max grade 10.0 deg); each feature is one filleted void (0.57 m measured). Final-field
reachability passes at 883 stations; an orphaned shaft control is rejected; exact intervals (0 misses) and
a whole-surface audit with 0 flags. Whole view 1.69 s (above the 1 s target). fast/medium/full PASS.
Research R27 (self-checked): static stream-to-pool water design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-25 15:45:32 +01:00
Fr0zkaandClaude Opus 5.5 1e3e04bbac perf(core): FI LOD0 attribution, exact Z-inner traversal; fix a mesh gap at the passage shaft foot (L57); R26
Sampled cycle attribution of FI LOD0 field work: grid fill 83%, boundary 13%, skirts 4%; island roots,
columns and helix dominate, the column cache path is real but secondary (no thrashing). FI-only Z-inner
traversal at LOD0 (same lattice samples, exact) cuts FI whole view 5.35 -> 5.03 s and moving p50 by 12%;
global Z-inner and <=4-island culling were measured and rejected. The meshing band now includes the planned
passage support column's conservative bounds, closing a real gap where the arrival shaft meets the floor
(WorldAssemblyEvaluatorVersion 208 -> 209). fast/medium/full PASS, Clang and GCC.
Research R26 (self-checked): native lattice (maze/shaft) generator design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-25 10:47:44 +01:00
Fr0zkaandClaude Opus 5.5 4e64b75044 feat(core): slab + sparse motifs, slice 1 -- flat field, monolith, platform at 0.52 s (L56); research R25
Motif vocabulary v3: version-1 records keyed by (seed, strate, stable ID) with route keepouts, band
clearance and pairwise spacing. A rounded capped-cylinder monolith (r 1.4 m, 24 m tall) and a rounded-box
plinth, smooth-unioned into a flatField floor with a 0.55 m geometric rounding (measured fillets 0.73-1.17 m).
Exact radial/box interval bounds, a BVH, isTileShapeFree aware (0 brute-force misses, false-proof controls
rejected). Final-field route passes; whole-surface audit covers foot, cap and platform edges. Cold whole
view 0.524 s. Owner world unchanged. fast/medium/full PASS (Clang and GCC).
Research R25 (self-checked): FloatingIslands LOD0 column cache conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-25 07:47:15 +01:00
Fr0zkaandClaude Opus 5.5 aee1487410 perf(core): tile-drain instrumentation; parallel FI sign-edge repair, FI first tile 1.28 -> 0.85 s (L55); R24
Opt-in --instrument-tile-drain records per-tile queue/dispatch/finish timelines, stage spans and counters
(proof skips, X8 lanes, GradAt/midpoint memo, skirts, FI cache stats); <2% when off. Measured: SW 13.3 and
FI 50.2 worker-s across LOD0-8, tails are work-saturated. FI sign-edge repair now computes in parallel and
publishes in sorted serial order (exact output): sign-edge 535 -> 47 ms, FI first tile 1.28 -> 0.85 s, near
ring 1.46 -> 1.04 s, whole view 6.09 -> 5.65 s. Proof block size, lane batching, SW midpoint memo and helper
cap 12 measured and left off. fast/medium/full PASS, Clang and GCC. Research R24: slab + motif generator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-25 06:16:07 +01:00
Fr0zkaandClaude Opus 5.5 e35a77f676 chore(L54): drop duplicate control SVG maps (already in Linux48)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-25 04:10:37 +01:00
Fr0zkaandClaude Opus 5.5 9458217d34 feat(core): native Core caves generator, slice 1 -- spine + room, whole view 0.73 s (L54); research R23
Core.Caves.PrimitiveField v1, opt-in per strate (legacy TunnelNetwork assets stay legacy): solid host rock
and one smooth union of two elliptical spine tubes and an ellipsoid room, each intersected with its rising
floor by cubic smooth-max (W 2.4 m, measured fillet 0.70 m, no trailing floor cap). Bound in the L40 field
table and L42 capabilities, BVH feature candidates, isTileShapeFree aware, exact interval boxes (0 misses).
Capsule flood fill arrival -> departure passes (headroom >= 0.3 m, grade <= 15 deg); the generalized
whole-surface audit passes with its controls failing. Cold whole view 0.727 s, near ring 0.19 s. Graph
ordering/serialization changes are scoped to native caves (the flagged L47 demo is unchanged); verify
tiers write captures under --out only. fast/medium/full PASS, Clang and GCC.
Research R23 (deepened): per-tile LOD0 kernel speed plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-25 04:10:19 +01:00
Fr0zkaandClaude Opus 5.5 20f6c98ec5 feat(core): profiled heightfields, slice 1: periodicHills, mirroredRelief, flatField (L53); research R22
WorldAssemblySurfaceProfileV1 on the native SurfaceWorld recipe and plan; version 0 'natural' is omitted
from serialized identity, so the owner world is unchanged. periodicHills (seeded cosine hills, plan-time
grade <= 15 deg), mirroredRelief (cap mirrors the floor by lambda, clearance checked) and flatField share
one profile function for points, lattice classification and exact cosine-extrema intervals (0 interval
misses in brute-force audits). Style-override fixtures with planned passages. Cold whole view about 1.33 s
for each profile vs 1.74 s natural SW (Clang). Owner hashes identical to L52; fast/medium/full PASS.
Research R22: SurfaceWorld critical path to about 1 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-25 01:49:11 +02:00
Fr0zkaandClaude Opus 5.5 408c14baff perf(mesher): shared proof-driven sampling skips certified-uniform blocks (L52); FI 7.1 -> 5.6 s; R21
The shared marching-cubes mesher applies its 8-cell block proofs at step 2 for standalone shape contexts
and skips density-grid fills outside certified-uniform blocks (plus a one-sample normal halo); mixed and
unknown blocks still sample exactly. FI certified-air boxes 1,959 -> 14,119; FI LOD1 samples -63%. FI whole
view 7.08 -> 5.61 s (-21%), moving p50 about -33%; SW unchanged. Geometry hashes identical (no version
bump); proof audit 36.9 M samples, 0 violations, Clang and GCC. Source stamp now covers the standalone
mesher copy. Research R21 (deepened): native Core cave generator design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 23:53:43 +02:00
Fr0zkaandClaude Opus 5.5 c42c785e2c docs(L51): complete the truncated orchestrator review
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 20:37:10 +02:00
Fr0zkaandClaude Opus 5.5 75550256ef perf(core): FloatingIslands whole view 16.7 s -> 7.4 s (L51); research R20
Measured first: 45% of FI tiles are all-air, yet FloatingIslandFieldV1 returned unknown for any box near
an island AABB (25,536 unknown boxes, 0 tile skips). Conservative ranges for island bodies, root lobes,
buttresses, connectors, surface detail and smooth-min budgets now let the shared box classifier prove FI
air/solid (216 tile skips; 28.6 M audited samples, 0 violations). The X8 path reuses its FI preflight
samples. LOD7-8 far-shell and column work is skipped outside per-tile island/connector influence bounds
(farHorizonVersion 4 -> 5). FI 16.74 -> 7.36 s (-56%), FI moving entering-tile p50 halved; SW unchanged.
Peer determinism (Clang/GCC W1/W8/W12, random cameras) PASS; fast/medium/full PASS.
Research R20: world-type catalogue and dreamcore/liminal concepts (revised after a deepening pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 20:36:54 +02:00
Fr0zkaandClaude Opus 5.5 ca85e876ad feat(core): ore vein records and a materialAt query, off by default (L50); research R18, R19
Versioned capsule vein records (stable ID, resource ID, endpoints, radius, seeded irregularity) resolved
into the snapshot only when requested, placed beside the route and away from passages, landings and
landmarks (forced-passage control rejected). The bound field table gains materialAt (host + two resource
IDs + Q16 weights) behind a per-tile BVH; mesh export v2 carries per-vertex materials; a measurement-only
yield computes removed resource volume (0 on already-air space). With veins on, every density, mesh,
payload and topology hash is unchanged; demo off: owner hashes identical to L47. fast/medium/full PASS.
Research R18 (cavern-graph planner) and R19 (why FloatingIslands costs ~10x SurfaceWorld).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 18:25:03 +02:00
Fr0zkaandClaude Opus 5.5 b9ccff4ad6 research(R17): authoring and adapter wiring to bring Core stamps and cavern graphs into Unreal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 16:38:55 +02:00
Fr0zkaandClaude Opus 5.5 6110f118e3 research(R16): why hard SDF intersections alias under marching cubes; fillet sizing per LOD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 15:44:49 +02:00
Fr0zkaandClaude Opus 5.5 3073d1b2fd tools(audit): whole-surface mesh artifact audit with historical controls (L48, tool only); research R15
AuditWholeSurfaceL48.py scans every LOD0 triangle in the route corridor + 3 m and the chamber box + 3 m:
welded-edge normal variation, high-angle edge density, max jumps and sliver area in 1 m cells, clustered
and located, with thresholds from native patches in the same export, plus a detrended full-width floor
profile. L45 ramp, L46 entrance and L47 wall-foot meshes fail it; plain native terrain passes. Red-triangle
SVG maps included. Not wired into the tiers yet; the attempted Core fix is parked on wip/l48-surface-audit.
Research R15: mesh artifact metrics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 14:53:11 +02:00
Fr0zkaandClaude Opus 5.5 c091e4434f fix(core): smooth tunnel entrance floor; verifier keeps baselines out of /tmp (L47); research R14
The demo tunnel's hard floor cap used a vertical Z - floorZ on a sloped Bezier floor, which is not a signed
distance, so marching cubes produced a staircase through the first 7 m (40-51 mm ripple, 82 mm steps). The
cap now uses the grade-normalised signed distance with a floor-adjacent bore re-application fading over the
entrance; mesh tile Z bounds include cavern bounds. New three-rail entrance audit: L46 fails, L47 passes.
verify: the cost baseline exports only build inputs into Saved/Linux/<out>/baseline-<commit> and removes it;
/tmp unchanged after a full tier. Demo off: owner hashes identical to L46. fast/medium/full PASS.
Known: sawtooth at the tunnel wall foot (next round). Research R14: Windows/MSVC readiness review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 12:54:39 +02:00
Fr0zkaandClaude Opus 5.5 e016f6a20f feat(core): cavern graph v1, one continuous floor-capped tunnel into the landmark chamber (L46); R13
The flagged SW demo's ramp (57 carves + 107 support pillars, visibly ribbed) becomes one cubic-Bezier
sweep: closest point by 17-point seed plus 5 Newton steps, a cubic floor profile, a rounded 2.6 m bore,
and a hard floor cap applied after unions and relief that only adds support where the field is air.
Optional resolved cavern-graph records (one room, one edge) enter canonical identity only when present;
their bounds extend World::isTileShapeFree. Reachability passes (L43 control fails), headroom 2.8 m,
floor ripple 1.8 mm (L45 ramp control fails at 17.9 mm), shape limits met; demo off: owner hashes
identical to L45. fast/medium/full PASS. Research R13 (read-only): continuous SDF sweeps along curves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 09:53:06 +02:00
Fr0zkaandClaude Opus 5.5 f13b145f23 feat(core): reachable SW landmark chamber demo with a reachability audit (L45); research R12
Stamp vocabulary v2; the flagged SW demo opens through a side-facing portal and a bent ramp (max 14 deg,
two landings) into a 17 x 15 m chamber with a rock dais. New reachability audit: a capsule surface search
from walkable ground near the arrival mouth to the chamber floor (66 samples, 0 blocked/unsupported); the
buried L43 layout fails it. Demo off: owner hashes identical to L44. fast/medium/full PASS (Clang+GCC).
Research R12 (read-only): composite cavern-graph generator design and slices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 07:42:30 +02:00
Fr0zkaandClaude Opus 5.5 036bd530fc fix(core): worker-count-dependent tiles near passage support columns (L44); random-camera determinism sweep
L27 added passage support columns, but World::isTileShapeFree only bounded the route and bridge, so tiles
touched by a column could be approved for early native-only generation; depending on scheduling they used
that incomplete field or the compiled one (six LOD0 tiles differed between W1 and W12 at an SW camera near
the arrival mouth). The shape-free proof now includes a conservative envelope for every possible column.
The compiled world is unchanged (the old W1 variant), so no version bump. verify tiers gain a seeded
(SplitMix64 0x4c3434) random-camera W1/W8/W12 sweep across both recipes and several clip settings; it fails
before the fix and passes after. fast/medium/full PASS; cost within noise. Research R11: suspect review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 23:00:21 +02:00
Fr0zkaandClaude Opus 5.5 422dd7761a feat(core): deterministic bounded SDF landmark stamps, off by default (L43); research R10
Stamp vocabulary v1 (carve ellipsoid/arch, fill pillar/dais) with smooth CSG and seeded relief; the
director resolves records (stable ID, strate, micrometre position, conservative bounds) from
hash(seed, strate, ID) with up to 512 candidates and rejects any that touch a passage mouth, walk corridor,
content face or ceiling/bedrock band; a stamp BVH skips tiles outside all bounds. No requests: canonical
JSON and owner hashes unchanged. --landmark-chamber-demo adds a SW chamber (buried; see review).
fast/medium/full PASS on clean builds. Research R10 (read-only): ore/resource channel design.
Review also records a pre-existing W1/W12 determinism mismatch at a non-spawn camera (L37+, not S14).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 21:50:56 +02:00
Fr0zkaandClaude Opus 5.5 497e974178 research(R9): drop the chat preamble from the saved report
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 20:23:04 +02:00
Fr0zkaandClaude Opus 5.5 2566f53734 refactor(core): boundary policy as resolved capabilities, not generator-name tests (L42, step 6); R9
ResolvedLayoutSnapshotV3::finalize maps each strate's generator and interface once into capability
records (FI gradient air-gap, surface/FI landing support, passage-anchor columns, SW-to-TN floor);
ApplyResolvedStrateBoundaryBand and passage composition read those instead of generator kinds. Derived
records stay out of canonical JSON, so layout identity is unchanged. Owner hashes identical to L41;
fast/medium/full PASS on clean Clang and GCC builds; cost within noise.
Research R9 (read-only): player-visible roadmap refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 20:22:49 +02:00
Fr0zkaandClaude Opus 5.5 1abee91721 refactor: resolved metre layout is the runtime authority (L41, migration step 3); stamp covers plugin sources; R8
UVoxelStrateManager::Initialize resolves the layout width to integer micrometres before passages or
scheduling; each slot stores physical [bottom, top) bounds, transition mode/width and passage settings;
chunk indices remain a floor-based compatibility projection. The Core adapter derives interiors and L29
bands from those resolved values. verify: full-tier cost baseline is HEAD (or --baseline), tier timeout
7200 s with 1800 s per process; the source stamp now includes the plugin Source tree compiled on Linux
(it previously missed VoxelStrateManager.cpp and friends). Owner hashes identical to L37; fast, medium
and full PASS on a clean rebuild. Unreal manager/adapter edits are uncompiled on Windows.
Research R8: inventory of unit-bearing UPROPERTYs and a custom-version asset migration plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 19:36:51 +02:00
Fr0zkaandClaude Opus 5.5 efa9e89468 refactor(core): bind generator fields once per resolved strate (L40, migration step 2); research R7
SurfaceWorld, FloatingIslands and legacy evaluators sit behind one immutable densityAt adapter held in
a variant (no virtual call per sample); BindResolvedStratumFields builds a sorted strate table at world
setup, so DensityAt, native SW/FI sampling and ClassifyBoxDetailed no longer ask the strate manager per
sample. No arithmetic change: owner hashes identical to L37/L39 at W1/W12. Cost vs L39: FI -0.66%,
SW -1.31% (noise). verify fast/medium/full PASS (full re-run alone: 407 gates).
Research R7: disk cache design for UE5/Windows/multiplayer and a zlib probe (about 1/3 size, 0.1 ms/tile).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 17:15:46 +02:00
Fr0zkaandClaude Opus 5.5 753cc9c259 tools(verify): fast/medium/full verification tiers with a compiled-in source stamp (L39); research R6
BuildLinux.sh hashes a sorted manifest of compiled inputs into VOXELFORGE_SOURCE_STAMP, printed first by
every executable; Standalone/Tools/verify/verify.py checks it before and after each tier and records
binary/input hashes and the resolved recipe, style map and layout. Fast (163 s) runs on the canonical owner
world only and asserts FI Style 2 / SW Style 1; medium (8 min) and full (26 min) add the style matrix,
GCC, determinism and cost. Controls: stale source, mislabelled style-0 owner, all-black capture, L30/L31,
L35/L36 and the L38 step are all rejected. No geometry change; owner hashes identical to L37.
Research R6: leak table, generator contract and a 10-step metre-native migration plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 15:27:52 +02:00
Fr0zkaandClaude Opus 5.5 88c54eab44 research(R5): tiered verification (fast/medium/full) and a final-source stamp
Inventory of every audit gate with controls and wall times; a fast tier (~3-4 min, real owner styles)
per trial, medium (~8-10 min) per candidate, full (~32-34 min) once. A compiled-in source manifest stamp
that every runner checks, so evidence provably comes from the committed tree. Lists duplicate runners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 11:24:57 +02:00
Fr0zkaandClaude Opus 5.5 cf57c87daf research(R4): L38 wall "ripple" is a coil-ownership step, not a wave
Offline replay of the L37 and L38 tube fields on the FI Style 2 helix shows no 7.8 m ripple from the
tube distance itself (< 0.3 cm). The real signal is a step: the upper coil's 1 m fallback floor strip,
composed independently, puts a support root 0.44 m from the lower bore's centreline at head height;
the 7.8 m "period" is a sinusoid fitted to that step. Proposes pairing the floor cap with its bore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 08:30:59 +02:00
Fr0zkaandClaude Opus 5.5 945225d942 research(R3): floor ownership on an overlapping helical passage
Read-only study for L38: a synthetic FI Style 2 helix (3 m radius, 3.31 m pitch) shows Euclidean
closest-centerline ownership is wrong when bore radius > pitch/2; a floor-near (+-0.125 m, within bore
radius) nearest-floor-point owner gives 2.9 cm p-p on helix and style-like paths. The L38 0.94 m ripple
period equals the 20-segment chord. L38's continuation applied it: floor ripple 0; wall ripple open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 07:35:16 +02:00
Fr0zkaandClaude Opus 5.5 7f2d84d2c7 fix: L37 Core compile error (stray paren) and Unreal-side issues from review R2
ShapeWorldInternal.h kept an extra ')' after a diagnostic edit was restored post-build, so the L37
commit did not compile; the fixed build reproduces L37's verified owner-world hashes exactly.
Unreal (uncompiled here): SetHardTransitionFallbackForAssembly is const over a mutable runtime flag
(the adapter calls it through a const manager); the legacy fallback clears the flag; VoxelStrateManager
includes <cmath> for std::llround. Adds research R2 (read-only Unreal compile/behaviour review).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 06:55:53 +02:00
Fr0zkaandClaude Opus 5.5 3e443bf81c wip(assembler): round L37 -- FI mouth carve-fade fix, no raised bridge (style 0); research R1
The passage mouth fade multiplied the signed carve distance by zero, which turned native rock into an
open surface (the reason earlier rounds stamped walk pads at mouths); it now blends between the
uncarved field and the full signed carve. The FI style-0 route followed a lifted grade envelope over
the crater (the "bridge"); it now runs on the rim and follows the native surface. New built-shape and
crease audits with thresholds frozen before the fix (L36 and box controls fail). 24/24 passage rows,
30/30 determinism, 16/16 invariants, 18/18 interface/ring. Unreal: one recipe setter (uncompiled).
Research R1 (read-only, web): ranked proposals for natural walkable mouths, floor-limited carve first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 05:51:50 +02:00
Fr0zkaandClaude Opus 5.5 8be1c4d9ef wip(assembler): round L36 -- tunnel floor without washboard; FI mouth without shards (evaluator v23)
The walk tread was built per segment as a one-sided density clamp, so its crossing with the native
field moved at segment joins (L35: 2.25-2.48 cm washboard). Treads now overlap up to 2 m as bounded
signed-distance slabs (0.06 cm residual). The FI mouth's crater detail and tread met without a smooth
join; v23 filters the local field, drops thin isolated solids and smooth-unions the tread with the
terrain. New floor ripple and mouth fragment/crease audits with L35 as failing controls. 24/24
passage rows, 30/30 determinism, 16/16 invariants, LOD0-8 rings; cost within noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-22 13:01:54 +02:00
Fr0zkaandClaude Opus 5.5 ba017077d1 wip(assembler): round L35 -- smooth passage walls without regular ribs (evaluator v21)
The shared passage carve took the minimum of piecewise-linear capsules (scallops at joins) and added
wall relief on regular 8 m / 4 m octaves, which read as evenly spaced ribs. Interior sweep joins are
now Catmull-Rom smoothed and the relief uses irregular rotated 43/29/19 m scales; the natural wobble
remains. New axial ripple audit (0.125 m radius samples, 0.5-8 m periods): beaded control and L34
geometry fail, all 24 passage rows pass. FI mouth tread apron tapered. 30/30 determinism, 16/16
invariants, ring audits LOD0-8 pass; cost within noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-22 07:28:50 +02:00
Fr0zkaandClaude Opus 5.5 49e29fcd36 wip(assembler): round L34 -- passage mouth profile audit and standard capture set
Adds a density-field floor/wall profile audit along passages near mouths (0.125 m spacing; L23
terraced negative control detected) and a reusable 12-view passage capture set (80 deg FOV, eye
height, LOD0, skirts off, capped clip planes). The old blurry mouth shot was camera framing; the
L33 entry ledges were tile skirts. Owner world hashes unchanged; 24/24 passage rows, 30/30
determinism, 16/16 invariants; cost within noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-22 01:12:40 +02:00
Fr0zkaandClaude Opus 5.5 b7a47bef95 wip(assembler): round L33 -- SurfaceWorld can be an upper strate; FloorProfileBlend measures seams
Route planning for an upper SurfaceWorld failed on long candidate chords over steep relief; chords
are now capped to the arrival/departure radii on an 8 m centre grid with an origin-proximity cost
(21.5 m walk, about 4 deg). The departure passage starts at the SW ground and tunnels down through rock.
FI -> SW -> TN fixture: all 4 styles pass every passage audit on both compilers. FloorProfileBlend
now checks endpoint height/tangent seams (step and tangent-step negative controls detected). Owner
world hashes unchanged; 30/30 determinism; cost within noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-21 22:30:36 +02:00
Fr0zkaandClaude Opus 5.5 d0158c6019 wip(assembler): round L32 -- smooth Gradient interface at full amplitude (no rings, not flat)
Band faces now use signed distance to the displaced solid intervals, continuous across bands, so
MC interpolates the interface instead of terracing at density levels. The Gradient amplitude is
restored to width/8 (about 4 m peak-to-peak at 16 m). Ring/terrace audit at LOD0-8 on the FI floor and SW
ceiling passes; negative controls: L30 (rings) and L31 (flat) both fail. Hole/interface audits,
16 passage rows and 24/24 determinism pass. Evaluator 20. Cost vs L31 SW +1.9%, FI +1.5%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-21 20:27:29 +02:00
Fr0zkaandClaude Opus 5.5 28069f0d98 wip(assembler): round L31 -- FI recipe column audit corrected; recipe-stamped audit runner
On the real FloatingIslands recipe, column caps sit inside anchor-island rock and native bedrock;
the old check sampled the route foot. The audit now samples each planned cap against the native
field (negative control: a cap moved into air is detected). The audit runner records requested vs
actual recipe and exits on mismatch. All 16 passage rows (4 styles x 2 recipes x 2 compilers) pass.
The readable shader uses a smooth tint. Interim: FI Gradient displacement capped at width/256
(flat floor, no contour rings); restoring real undulation is L32. Evaluator 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-21 14:13:18 +02:00
Fr0zkaandClaude Opus 5.5 103d8b471e wip(assembler): round L30 -- FI floor ring defect fixed; transition cost recovered
The Gradient interface left native solid density in the air-side gap and snapped the face to
voxel levels (concentric ring steps on the FI floor). The owner Gradient path now writes signed
distance both sides; match error RMS 0.36 -> 0.11 m. Band and fade-tile culling cut transition
cost: whole view vs L24 SW +2.7%, FI +0.2%. Evaluator 16. Deterministic across compilers and
workers; interface and passage audits pass. Found: on the real FloatingIslands recipe, the
rock-column connected-ends audit fails (all styles); earlier matrices ran SurfaceWorld twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-21 11:40:27 +02:00
Fr0zkaandClaude Opus 5.5 63c7e563d0 wip(assembler): round L29 -- strate transitions honoured at the bedrock/ceiling interface
EVoxelStrateTransition from the upper strate shapes the bedrock(N)/ceiling(N+1) interface in the
shared composition layer: Hard flat; Gradient = smooth 2D hash surface (16 m owner width, about 4 m
measured undulation); Interleaved = 3D domain-warped fingered overlap within the width. Each band
keeps >= 2 Core voxels solid; faces return to the seam near passage mouths. Interface audit over
512 m: 0 hole columns for all modes; wider-than-declared and too-thin controls rejected. All passage,
route, proof and validator audits pass; deterministic Clang = GCC. Evaluator 15. Cost +4% SW,
+1.3% FI vs L28. Open: ring-like artifact on the FI floor capture; transition look unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-21 09:16:12 +02:00
341 changed files with 941831 additions and 668 deletions

No files matched your search

+247 -2
View File
@@ -1883,8 +1883,9 @@ Each record separates the nominal ceiling, content, and bedrock bands. A strict
sequence for every strate and requires bedrock(N) to touch the next strate's ceiling(N+1). The two sequence for every strate and requires bedrock(N) to touch the next strate's ceiling(N+1). The two
bands remain separately owned. Gradient and Interleaved interfaces may declare interpenetration only bands remain separately owned. Gradient and Interleaved interfaces may declare interpenetration only
inside their explicit transition zone; Hard has zero effective width. The manager's `TransitionBlendChunks` inside their explicit transition zone; Hard has zero effective width. The manager's `TransitionBlendChunks`
is expressed in owner-chunk metres. Interleaved's existing two-chunk domain warp extends its possible is expressed in owner-chunk metres. L21 recorded the older Interleaved allowance as blend width plus
fingers beyond the blend width and is recorded separately. Compatibility validation reports current warp amplitude; L29 now treats the declared blend width as the complete physical crossing allowance.
Domain warp changes finger placement, but cannot extend overlap beyond that width. Compatibility validation reports current
gaps, overlaps, ambiguities, inactive/missing bands, and unhonoured transitions while accepting them; gaps, overlaps, ambiguities, inactive/missing bands, and unhonoured transitions while accepting them;
strict validation rejects them. Core dispatches per-strate recipe fields without applying that strict validation rejects them. Core dispatches per-strate recipe fields without applying that
parameter blend, even though the legacy manager helper implements it. Both band types default diggable, parameter blend, even though the legacy manager helper implements it. Both band types default diggable,
@@ -1943,6 +1944,7 @@ opens a band; the support strip stays below the player capsule. The lower mouth
monotone descent from the short route-bound landing tail, whose height may vary with the route. monotone descent from the short route-bound landing tail, whose height may vary with the route.
Both route and passage audits sample the 0.34 m by 1.76 m capsule, support probes, and grade; the Both route and passage audits sample the 0.34 m by 1.76 m capsule, support probes, and grade; the
negative control compiles the same world without passage carving and must detect a blocked capsule. negative control compiles the same world without passage carving and must detect a blocked capsule.
The L23 shared-field change is `WorldAssemblyEvaluatorVersion=9`. It does not change the L22 bands The L23 shared-field change is `WorldAssemblyEvaluatorVersion=9`. It does not change the L22 bands
or any generator's field away from a passage. The L23 report includes exact-query/hash comparisons, or any generator's field away from a passage. The L23 report includes exact-query/hash comparisons,
the blocked control, captures, and the Windows adapter checklist. §8.10's cache, proof, streaming, the blocked control, captures, and the Windows adapter checklist. §8.10's cache, proof, streaming,
@@ -2051,6 +2053,249 @@ proof bounds preserve the L27 output.
[Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md) [Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md)
records counts, parity, costs and audit caveats. records counts, parity, costs and audit caveats.
### 8.46 Honored bedrock/ceiling transitions — World Assembler L29
The upper strate's `EVoxelStrateTransition` controls the shared interface between its bedrock band
and the next strate's ceiling band in the Core composition path. `TransitionBlendChunks` is converted
using the resolved layout chunk width (8 m by default). Hard preserves the flat interface. Gradient
moves the shared surface with a smooth 2D hash field whose peak-to-peak range is at most one quarter
of the declared width. Interleaved uses a seed/interface/coordinate-keyed, domain-warped 3D hash field
to set actual overlap between the facing bands from zero to the declared width. The two faces move
inward by half that overlap; the warp changes finger placement without increasing the crossing
allowance. Both bands keep authored thickness, and their solid intervals touch or overlap in every
XY column. Core does not blend unrelated generator fields.
Each boundary band keeps at least **two Core voxels** of solid thickness (0.5 m at the default 25 cm
voxel size). Strict validation rejects thinner bands and declared interpenetration beyond the
transition width. The world audit samples a 512 m square XY area, measures actual crossing depth and
minimum band thickness, and sweeps every non-passage column through both bands at half-voxel spacing.
Passage columns are excluded from the hole sweep. Near an interface mouth, the shared face returns to
its nominal height inside the mouth radius plus a 1 m support margin, then fades back to the selected
transition over 2 m so the authored landing floor remains supported. Negative controls reject a
transition one micrometre wider than declared and a band one micrometre below the minimum. See
[`Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md)
for audit measurements, hashes, timings, captures and run history.
### 8.47 Ground-bound SurfaceWorld departure routes — World Assembler M3 Linux L33
An intra-strate SurfaceWorld route follows the resolved terrain ground profile. Its route-point Z
is the ground height plus the player's half-height; the route does not descend to the strate's
bedrock boundary. Bedrock belongs to the separate inter-strate passage, which begins at the
SurfaceWorld departure mouth on the ground and tunnels outward and down through native rock and
the boundary into the next strate.
The old fixed candidate envelope used a 64 m chord and 16 m centre spacing. On the L33 upper-SW
fixture, replaying that envelope sampled ground from −507.767 to −464.857 m; 366/648 candidate paths
exceeded the 15° grade limit. The worst failures clustered near the outer west/northwest terrain
shoulder (up to 79.971°) and the steep approach slopes around the origin. Candidate diagnostics
separate grade failures from endpoints outside the bounded arrival/departure placement disks.
The bounded search now limits its longest chord to the two mouth radii minus a 3 m clearance
margin, checks centres every 8 m, and ranks endpoints near the route anchors. It finds a 21.5 m
route from (−2.75, 0) to (18.75, 0), sampled on ground and accepted by the full capsule/support/
grade audit.
The SurfaceWorld departure passage starts at the ground mouth with a 1.5 m portal, then smoothsteps
to a 3.5 m rock tunnel over 9 m. Its ring is sized and oriented outward from the mouth, so the
approach does not cut back under the walk route. The `FloatingIslands → SurfaceWorld → TunnelNetwork`
fixture passes all four passage styles, including both interfaces, capsule clearance, support,
grade, walkability, naturalness, open-air, column ends, long-bridge and route-binding audits.
`FloorProfileBlend` previously compared each curved floor profile with its straight chord and
applied a 2-voxel limit. That is a curvature measurement, not a seam measurement. It now checks
endpoint height and tangent continuity at 0.01 voxel and 0.001 voxel/m; the 3-voxel step and
0.25 voxel/m tangent-step controls are both detected. SurfaceWorld owner hashes remain identical
to L32 because the route-mouth, expanded column sizing and short-span support changes apply only
when SurfaceWorld is the upper strate. No evaluator or recipe version changed.
[`Standalone/Research/WorldAssemblerM3/Linux33/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux33/REPORT.md)
records the four-style audits, captures, determinism, owner hashes, costs and retries.
### 8.48 Irregular passage-wall relief and grounded tread shoulders — World Assembler M3 L35
L34's owner-SW profile had a 7.438 m radius repeat with 0.0361 m (3.61 cm) amplitude; an independent
mesh-normal scan also found a 4.750 m period at 0.02682 (1.537°). The geometry came from piecewise
capsule joins, while broad value-noise bands added a second regular pattern. The shared carve now
uses Catmull–Rom centerline interpolation on eligible interior segments, excluding short spans and
terminal controls. Passage walls use deterministic, rotated 43/29/19 m relief with reduced terrain
band contribution; other `VF_FieldSurfaceRelief` users, including grounded bridge columns, are
unchanged. The shared evaluator is version 21.
The residual radius scan on owner-SW style 0 measures a 7.062 m period at 0.0109 m (1.09 cm),
with 0.0044 m (0.44 cm) coherent amplitude. The independent L35 mesh-normal scan passes at 8 m
and 0.02496 axial amplitude, below its 0.0255 threshold. The ripple audit samples both side-wall
radii every 0.125 m in 32 m windows, scans 0.5–8 m periods, and rejects amplitudes of 0.035 m or
more. Its synthetic control uses 2 m radius spheres spaced every 1.5 m; it measures 0.0599 m and
must fail.
At the FI mouth, the flat apron is the shared passage support tread, not `LandingExtensionQ`, a tile
edge or a separate support layer. The tread is required by capsule and support probes. L35 gives its
outer entry shoulder a shallow taper and seeded grain while preserving the supported walk core and
lower-mouth overlap width. The shallow quarter-weight support-height/radius spline keeps the tread
continuous without moving its XY footprint.
Cascade routes also replace identical full-depth cosine lobes with a seeded shallow profile on each
turn. Passage, capsule, support, open-air, bridge, binding, strict layout, capture, determinism, ring,
and cost evidence are recorded in
[`Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md).
### 8.49 Walk-floor washboard and FI mouth naturalness — World Assembler M3 L36
L35's exported owner-SW mid-tunnel floor measured a 7.250 m repeat at 0.02252 m amplitude; its
upper-SW fixture arrival measured a 1.438 m repeat at 0.02482 m. Both came from segment-built,
one-sided support treads meeting the native terrain zero crossing at per-segment joins. The floor
probe samples the exported walk mesh along the resolved route, separately from the wall-radius scan.
Eligible tread sections overlap by up to 2 m and all sparse route segments now use a bounded
signed-distance slab, including SurfaceWorld-to-TunnelNetwork routes. This keeps the tread top stable
across segment joins while capsule-clearance and native-solid gates retain walkability and open air.
Regular tread tops remain seated 5 cm lower and ease back to 0.20 m within 20 m of a lower landing;
seeded shoulder grain is removed, and gravity-facing relief is reduced while side-wall and ceiling
relief remain irregular.
On v23, the same mesh probes measure 7.188 m / 0.00067 m (0.067 cm) on owner-SW and 6.500 m /
0.00057 m (0.057 cm) on the upper fixture. The density audit checks five 32 m walk-floor windows at
0.125 m spacing, rejects amplitudes of 0.015 m or more, and detects its beaded control. The FI mouth
uses a local 2 m symmetric signed-field filter with the route protected and joins route-mouth support
to native terrain with a 1.5 m smooth signed-distance union. Its mesh audit reduces the L35
42.27° fade-ring jump to 27.13° and finds no detached component above the 0.0001 m² area floor; one
8-triangle subthreshold component at 0.0000266 m² is recorded. The evaluator is version 23. Full
recipe/style audits, route support, open-air, strict layout, legacy validators, determinism, capture and
paired-cost results are in
[`Standalone/Research/WorldAssemblerM3/Linux36/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux36/REPORT.md).
### 8.50 Immutable field binding — World Assembler M3 L40
`FStandaloneShapeWorld` now keeps a sorted `FBoundStratumField` table built from the resolved layout
at world construction. Each entry owns a concrete SurfaceWorld, FloatingIslands, or legacy adapter
in `FGeneratorFieldAdapterV1`, plus the resolved bounds and native plan pointer. The adapters share
the immutable `densityAt(x,y,z,out)` shape, while `std::variant` keeps dispatch concrete instead of
adding a virtual call to every density sample. FloatingIslands keeps its original fixed-point entry
point for sample evaluation and its boundary parameter table is captured when the adapter is bound.
`DensityAt`, native SurfaceWorld/FloatingIslands evaluation, legacy SurfaceWorld bridging and
`ClassifyBoxDetailed` select from the bound table. The native adapters no longer ask the manager
which generator or plan to use per sample; box proof reuses the bound field objects. The existing
float-to-fixed conversion order, density arithmetic and shared boundary composition remain intact.
The manager's layout-width value is captured once for the legacy-compatible chunk-index projection.
The legacy adapter is still a compatibility bridge: `UVoxelGenerator::GetDensityAt` retains its
own manager routing when invoked for legacy SurfaceWorld fields or out-of-plan fallback queries.
Other shared-composition policy branches and passage/boundary planning remain outside this slice.
No Unreal adapter or `VoxelWorld.cpp` change was needed, and the evaluator version is unchanged.
L37/L39 owner hashes are the parity baseline; the L40 report records fast, medium, full and paired
cost evidence.
### 8.51 Resolved metre layout authority — World Assembler M3 L41
`UVoxelStrateManager::Initialize` now resolves the layout in integer micrometres before passages or
Core work are scheduled. The configured layout width is rounded once to micrometres; legacy height
and inter-strate gap counts are multiplied by that width. Each slot stores its half-open physical
interval `[bottom, top)`, height, transition mode/width, copied passage settings and their physical
dimensions. The effective world seed is captured in the manager before passage generation.
The historical 8 m layout keeps its old placement: the first upper edge is +8 m, and each authored
chunk of height or gap is exactly 8 m. `TopChunkZ` and `BottomChunkZ` are floor-based projections of
the resolved interval, including mathematical floor for negative Z. Season schema v2 still validates
its aligned top/bottom chunk values and stacked order as before, then maps those accepted projections
to the same physical intervals. L29's facing ceiling/bedrock bands are derived from these physical
intervals and the copied transition width, so their metre bounds retain the prior chunk-based values.
The Core adapter builds SurfaceWorld and FloatingIslands bounds, shared-layout bands, and resolved
passage dimensions from the micrometre fields. FI and SW both receive the same
`LayoutChunkWidthSubvoxels = round(layoutWidthMicrometres * 256 / coreVoxelMicrometres)`; layout
conversion does not use the owner/Core scale. Owner/Core ratios remain in the legacy FloatingIslands
parameter adapter for generator dimensions that are still authored in owner voxels. The manager's
legacy passage builder converts resolved passage micrometres back to owner voxels only at its legacy
geometry API boundary.
Chunk coordinates remain in `GetStrateForChunk`, `GetGeneratorTypeForChunk`, chunk-parameter queries,
season-v2 validation, and legacy generator dispatch because those interfaces accept layout-chunk
indices. Their values are cached projections from the physical slot bounds. World-Z queries and the
Core snapshot use the half-open metre intervals. L40's bound Core field table remains the native
per-sample dispatch path; this migration adds no manager lookup to it. The Unreal manager and adapter
cannot be compiled in this Linux workspace, so their small source changes still need the Windows
Unreal build before integration.
### 8.52 Resolved boundary and landing capabilities — World Assembler M3 L42
`ResolvedLayoutSnapshotV3::finalize` materializes runtime capability records from the immutable
strate/interface fields before sampling begins. The upper interface records whether its Gradient FI
bedrock air gap remains open and whether a SurfaceWorld-to-TunnelNetwork passage gets the special
landing support profile. Per-strate passage records carry the existing SurfaceWorld landing profile,
FloatingIslands route-support behavior, and passage-anchor column center policy. Shared boundary and
passage composition reads these records; it no longer compares generator-kind strings.
The records are deterministic derivatives of `generator_kind`, `transition_mode`, and the resolved
adjacent strate, and are omitted from canonical JSON. Those source fields remain in the hashed layout,
so owner layout IDs and density hashes retain their L41 identity. The upper strate still controls
Hard/Gradient/Interleaved evaluation; each strate still composes ceiling, content, then breakable
bedrock. L29 transition samples and the passage-mouth fade arithmetic are unchanged.
`BindResolvedStratumFields` still tests the resolved generator kind once to bind the concrete
SurfaceWorld, FloatingIslands, or legacy evaluator. Passage planning in `ResolvedLayoutInternal.h`
also retains generator-specific anchor and default-mouth placement rules; those are outside the
shared composition boundary policy. No Unreal source changed. See the L42 report for hashes, tier
results and paired cost measurements.
### 8.53 Shared Core landmark SDF stamps — World Assembler M3 L43
`ResolvedLayoutStampV1` adds a small, version-1 SDF vocabulary to the shared resolved layout:
carve ellipsoid, carve arch, fill pillar and fill dais. Requests and resolved records use integer
micrometres and carry a stable ID, strate, position, conservative bounds, half extents, smooth-CSG
width, bounded irregularity amplitude/wavelength and quarter-turn orientation. A stamp's seeded
noise and bounded placement offsets derive from `(world seed, strate, stable ID)`; records are
sorted by stable ID before the layout identity is finalized. If no request is made, the stamp fields
are omitted from canonical JSON, preserving the no-stamp layout identity and density hashes.
Resolution retries a finite, deterministic set of candidates and rejects a record if its complete
bound intersects a passage mouth or walk corridor, reaches a strate content face, or overlaps the
ceiling or bedrock band. The director reports failure if no safe candidate exists. The strict-layout
negative control forces a candidate onto a route and into a ceiling band and expects both rejections.
`FStandaloneShapeWorld` applies stamps after the shared resolved-strate boundary and passage
composition. Carves use smooth maximum with the negated stamp SDF; fills use smooth minimum. Seeded
trilinear value noise adds low-frequency relief without changing the conservative bound. The tile
query scope builds a BVH candidate list from each tile's sample bounds. Samples in a tile outside
every stamp box do no stamp SDF evaluation. Box classification marks stamp-intersecting boxes mixed,
and native-only readiness refuses to skip them.
The `--landmark-chamber-demo` recipe flag adds an irregular ellipsoid chamber, a separate arched
entrance and a low fill dais in the owner SurfaceWorld strate, about 31 m from the passage arrival
mouth and off the walk route. It stays disabled by default. Active records carry vocabulary version
1 in the layout identity, making the demo an explicit world change; no Unreal adapter was added.
L43's report records the off/on hashes, walk and interface audits, outside-stamp timing, tier
results, captures and any failed runs.
### 8.54 Opt-in native lattice field — World Assembler M3 R26 slices 1–2 (Linux L58–L59)
`Core.Lattice.PrimitiveField` is an opt-in recipe selected only for one Maze or VerticalShafts
strate by `--recipe=lattice --lattice-strate=N`. The legacy generator assets and their default
dispatch stay unchanged. Version 2 binds the actual incoming lower mouth and outgoing upper mouth,
then creates arrival/exit overlap corridors, an open vertical shaft and the maze spine; two
deterministic dead ends are added after that route. Resolved room and edge seeds use
`(world seed, strate, recipe version, stable ID)`. The orphan control translates the shaft 20 m
away from both route junctions and must fail reachability.
The version-2 shaft core is a vertical cylinder of 4.5 m radius. Its air continues 8 m above the
top tread and 2 m below the bottom tread. The ramp winds around the core at a 6 m centreline radius,
with 3 m clear width, 5 m pitch and four turns. The open annular shaft air joins the core air, while
one-metre support slabs follow the helix under each tread. This leaves a 4 m vertical gap between
one tread and the underside of the next. The ledge's inner edge is open to the well; the route
centreline stays 1.5 m from the drop and leaves 1.16 m beyond the 0.34 m capsule radius. Its inner
edge grade is about 10.0 degrees. The maze retains 2.6 m room headroom. The shaft's padded outer
design envelope is 21 m. The 1.9 m floor-wall smooth-max gives an isolated ideal fillet radius of
0.6718 m; the separate 0.05 m feature-union blend keeps route junctions connected.
The layout reuses the resolved cavern graph vocabulary. A per-strate opt-in selects the native
negative-solid/positive-air field; canonical feature order is stable-ID order. The ring-shaped shaft
air and its support layers use an analytic helix lookup instead of testing every polyline segment
per density sample. The core cylinder and annular wall have conservative interval bounds; false
all-air and all-solid controls remain rejected, and `isTileShapeFree` refuses occupied tiles. The
L59 pipeline checks the composed field from actual mouth to mouth with a 0.34 m by 1.76 m capsule,
support at every station, 2.2 m clearance and a 15 degree grade limit. It also checks the orphan
control, whole-surface ramp lip/wall-foot/underside, brute-force interval samples and the composed
wall-foot fillet. See the Linux58 and Linux59 reports for evidence. This code path is not promoted
to legacy Maze/VerticalShafts generation.
## 9. Multiplayer model (listen-server first, dedicated-friendly) ## 9. Multiplayer model (listen-server first, dedicated-friendly)
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a > **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
+124 -17
View File
@@ -30,7 +30,7 @@ A **density-field voxel terrain** plugin for Unreal Engine, built around undergr
| Term | Meaning | | Term | Meaning |
|------|---------| |------|---------|
| Legacy chunk | 32×32×32 voxels (`CHUNK_SIZE`). At the legacy 12.5 cm voxel size it spans 4 m; it is not the strate-layout unit or the Core tile. | | Legacy chunk | 32×32×32 voxels (`CHUNK_SIZE`). At the legacy 12.5 cm voxel size it spans 4 m; it is not the strate-layout unit or the Core tile. |
| Strate-layout chunk | World-level `StrateLayoutChunkSizeMeters`, default 8 m, shared by all generators and independent of voxel size. The director resolves it once and Core reads the resolved snapshot. At 25 cm it is 32 Core voxels; at 20 cm it is 40 Core voxels. | | Strate-layout chunk | World-level `StrateLayoutChunkSizeMeters`, default 8 m, shared by all generators and independent of voxel size. `UVoxelStrateManager::Initialize` resolves it to integer micrometres; slot bounds, heights, gap and transition widths use those physical values. Core derives the common `LayoutChunkWidthSubvoxels` from that same width. At 25 cm it is 32 Core voxels; at 20 cm it is 40 Core voxels. |
| Core tile | Internal 32×32×32 Core voxel tile. Its metre size follows the Core voxel size and stays separate from the configurable layout chunk. | | Core tile | Internal 32×32×32 Core voxel tile. Its metre size follows the Core voxel size and stays separate from the configurable layout chunk. |
| Density | Scalar field. `< 0` solid, `> 0` air, `0` = surface (`IsoLevel`). | | Density | Scalar field. `< 0` solid, `> 0` air, `0` = surface (`IsoLevel`). |
| Strate | Vertical layer of the world, stacked downward. Has its own generator + params. | | Strate | Vertical layer of the world, stacked downward. Has its own generator + params. |
@@ -70,8 +70,10 @@ A **density-field voxel terrain** plugin for Unreal Engine, built around undergr
(RealtimeMeshComponent) (RealtimeMeshComponent)
``` ```
`UVoxelStrateManager` is the side oracle: "what strate is at this Z, what params, `UVoxelStrateManager` owns resolved half-open metre intervals (`[bottom, top)`) and is the side
what generator type, and is there a passage/elevator SDF near here?" oracle: "what strate is at this Z, what params, what generator type, and is there a passage/elevator
SDF near here?" Integer chunk bounds on each slot are compatibility projections for existing chunk
dispatch APIs.
--- ---
@@ -167,14 +169,16 @@ remains `0`, and lateral regions remain gated off.
| `../../Standalone/Core/src/CoreNamespacePreamble.h` | First include of every Core TU. Forces FP contraction off per compiler (Clang/MSVC/GCC pragmas; unknown compiler = `#error`) and defines `vfcore::fp_contract::selfTest`, which `World::compile` runs before building: a fused multiply-add (e.g. Clang `-ffp-contract=fast`) refuses world generation. The world depends on contraction (Linux2/Linux3 reports), so never remove either. | | `../../Standalone/Core/src/CoreNamespacePreamble.h` | First include of every Core TU. Forces FP contraction off per compiler (Clang/MSVC/GCC pragmas; unknown compiler = `#error`) and defines `vfcore::fp_contract::selfTest`, which `World::compile` runs before building: a fused multiply-add (e.g. Clang `-ffp-contract=fast`) refuses world generation. The world depends on contraction (Linux2/Linux3 reports), so never remove either. |
| `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. | | `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. | | `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, transition and dispatch provenance, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.41. | | `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, honored transition settings, deterministic per-column transition bands, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.42, §8.46. |
| `NativeLatticeFieldRecipeVersion`, `IsNativeLatticeFieldStratum`, `LayoutLatticeFeatureSeed` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `Standalone/Core/src/ResolvedLayoutInternal.h` | Explicit Maze/VerticalShafts opt-in at lattice recipe version 2; stable feature seeds include world seed, strate, recipe version and feature ID. §8.54. |
| `FCavernRuntimeFeature::openShaftRamp`, `ApplyResolvedNativeCavesField`, `ClassifyNativeCaveFieldBox` | `Standalone/Core/src/ShapeWorldInternal.h` | Analytic open shaft ring, helical support lookup, exact cylinder core bounds and conservative interval proofs; the outer graph BVH supplies tile candidates and protects `isTileShapeFree`. §8.54. |
| `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. | | `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval. | `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. | | Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. |
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). | | `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). |
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; schema-v1 owner-asset export check and BuildHost Core actor tests. `-coreworldassembly=PATH` supplies a reviewed manifest transiently to `-coreworldtest=1`; it does not save a data asset. `FStandaloneCoreAdapterScheduler` runs Core work at 1/8/16 workers; `VerifyStandaloneCoreAdapter` checks hashes and walkability. Existing bounded render/`VF_` walk/OBJ orchestration and research options remain editor/commandlet-only and are not linked by Game/Shipping. | | `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; schema-v1 owner-asset export check and BuildHost Core actor tests. `-coreworldassembly=PATH` supplies a reviewed manifest transiently to `-coreworldtest=1`; it does not save a data asset. `FStandaloneCoreAdapterScheduler` runs Core work at 1/8/16 workers; `VerifyStandaloneCoreAdapter` checks hashes and walkability. Existing bounded render/`VF_` walk/OBJ orchestration and research options remain editor/commandlet-only and are not linked by Game/Shipping. |
| `Source/VoxelForge/Public/VoxelSettings.h` / `Private/VoxelSettings.cpp` | `UVoxelSettings` owns optional canonical world-assembly JSON and its Core digest. The editor file picker plus `ImportWorldAssemblyManifestJson` imports the reviewed manifest into the cooked settings asset and validates the recipe seed against the effective settings seed. | | `Source/VoxelForge/Public/VoxelSettings.h` / `Private/VoxelSettings.cpp` | `UVoxelSettings` owns optional canonical world-assembly JSON and its Core digest. The editor file picker plus `ImportWorldAssemblyManifestJson` imports the reviewed manifest into the cooked settings asset and validates the recipe seed against the effective settings seed. |
| `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` | Validates the optional manifest against each resolved `SurfaceWorld` slot (unique strate index and exact top/bottom chunks), then exports canonical `resolved_layout` separately from `world_assembly` and its hash. The layout digest does not alter recipe hashes. Invalid plans fail Core initialization. | | `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` | Resolves Core's common `LayoutChunkWidthSubvoxels` from the manager's physical layout width; builds SurfaceWorld/FloatingIslands bounds, L29 bands and passage settings from resolved micrometre slot fields; keeps owner/Core ratios in the legacy FloatingIslands parameter adapter. Chunk fields validate imported plans as compatibility projections. Exports canonical `resolved_layout` separately from `world_assembly` and its hash. Invalid plans fail Core initialization. |
| `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c through step 2f. The original path still compares live `GetDensityAt` with the editor chunk program. Research modes assign contiguous batches dynamically so surface-heavy work balances across workers; steps 2e and 2f use 4³ proof bricks and stage each evaluated brick before copying contiguous X rows, while step 2d retains 8³. Each skip requires a macro Lipschitz bound plus the complete final-detail supremum; uncertain bricks retain every 12.5 cm detail sample. Timings split classification, point evaluation, and uniform fill; step 2f compares all profiles and hashes workers 1 and 6. | | `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c through step 2f. The original path still compares live `GetDensityAt` with the editor chunk program. Research modes assign contiguous batches dynamically so surface-heavy work balances across workers; steps 2e and 2f use 4³ proof bricks and stage each evaluated brick before copying contiguous X rows, while step 2d retains 8³. Each skip requires a macro Lipschitz bound plus the complete final-detail supremum; uncertain bricks retain every 12.5 cm detail sample. Timings split classification, point evaluation, and uniform fill; step 2f compares all profiles and hashes workers 1 and 6. |
| `Public/VoxelRedefinedDensityField.h` / `Private/VoxelRedefinedDensityField.cpp` | Two default-off `WITH_EDITOR` research fields, selected by `voxel.ResearchRedefinedDensityField` (step 2d) and `voxel.ResearchParameterizedCave` (steps 2e/2f). Step 2f retains a metre-scaled 2e baseline for its rejection audit. New profiles use strongly jittered, individually rotated elliptical rooms capped against nearest neighbors and curved cubic-Bezier passages with deterministic centerline and width variation along each edge. Room heights and feature rolls vary by cell; ledges, occasional pillars, stalactites/stalagmites, basin floors, and bounded hanging masses remain features. Point and brick detail sampling share independently rotated 96/48/24-sample bands and a smooth ridge response, preserving 12/6/3 m wavelengths at 12.5 cm per sample. The parameterized mesher evaluates the exact field through prepared 4³ bricks and the same sound skip proof in commandlet and editor-game meshing. In-editor and in-game profile changes invalidate and rebuild all loaded tiles. In an editor game with this field enabled, sealed-solid, out-of-layout, and outer-classifier verdicts are declined without changing their console switches. | | `Public/VoxelRedefinedDensityField.h` / `Private/VoxelRedefinedDensityField.cpp` | Two default-off `WITH_EDITOR` research fields, selected by `voxel.ResearchRedefinedDensityField` (step 2d) and `voxel.ResearchParameterizedCave` (steps 2e/2f). Step 2f retains a metre-scaled 2e baseline for its rejection audit. New profiles use strongly jittered, individually rotated elliptical rooms capped against nearest neighbors and curved cubic-Bezier passages with deterministic centerline and width variation along each edge. Room heights and feature rolls vary by cell; ledges, occasional pillars, stalactites/stalagmites, basin floors, and bounded hanging masses remain features. Point and brick detail sampling share independently rotated 96/48/24-sample bands and a smooth ridge response, preserving 12/6/3 m wavelengths at 12.5 cm per sample. The parameterized mesher evaluates the exact field through prepared 4³ bricks and the same sound skip proof in commandlet and editor-game meshing. In-editor and in-game profile changes invalidate and rebuild all loaded tiles. In an editor game with this field enabled, sealed-solid, out-of-layout, and outer-classifier verdicts are declined without changing their console switches. |
| `Private/Tests/VoxelForgeParameterizedGenerationTest.cpp` | Separate three-seed functional sweep for the step 2f parameterized field; passage landing air/floor brackets and arrival-to-exit walking routes are checked without changing the live-world tests. | | `Private/Tests/VoxelForgeParameterizedGenerationTest.cpp` | Separate three-seed functional sweep for the step 2f parameterized field; passage landing air/floor brackets and arrival-to-exit walking routes are checked without changing the live-world tests. |
@@ -598,13 +602,16 @@ content lists, audio, `GameplayTags`(393). EditConditions show/hide param groups
type. type.
**`Public/VoxelStrateManager.h` + `.cpp`** — `UVoxelStrateManager : UObject` (h:152). **`Public/VoxelStrateManager.h` + `.cpp`** — `UVoxelStrateManager : UObject` (h:152).
Maps depth→strate at runtime; owns passages. Resolves each slot's physical metre interval, layout gap, transition width, world seed and passage
settings during `Initialize`, then owns passages. World-Z APIs query the physical intervals; legacy
chunk APIs use the cached integer projection.
- `FVoxelPassage` (h:46): standing endpoints, radius, type, wandering control points, and - `FVoxelPassage` (h:46): standing endpoints, radius, type, wandering control points, and
upper/lower local landing descriptors (room, floor, and door; no root connector). upper/lower local landing descriptors (room, floor, and door; no root connector).
- `FStrateSlot` (h:128): definition + chunk-Z range + index. - `FStrateSlot` (h:128): content definition, resolved micrometre bounds/height, resolved transition
and passage inputs, and compatibility chunk-Z projection + strate index.
| Method | .cpp line | Role | | Method | .cpp line | Role |
|--------|-----------|------| |--------|-----------|------|
| `Initialize` | 1164 | Keeps the existing sequencing through `GeneratePassages`, but selects one slot source: a validated cooked season (exact ordered bounds/vector/recipe) or the unchanged authored fixed+sorted/shuffled pool. Assigned-invalid seasons fail closed. Authored source definitions are duplicated as content bags; generated slots use deterministic C++ defaults and zero auto-passages because schema 2 does not store passage configuration. | | `Initialize` | 1186 | Keeps the existing sequencing through `GeneratePassages`, but selects one slot source: a validated cooked season (exact ordered bounds/vector/recipe) or the unchanged authored fixed+sorted/shuffled pool. Assigned-invalid seasons fail closed. Authored source definitions are duplicated as content bags; generated slots use deterministic C++ defaults and zero auto-passages because schema 2 does not store passage configuration. |
| `GeneratePassages` | 1659 | Deterministic passages between consecutive strates (per-strate `PassageConfig::Style` control points; auto passages retain the existing `EVoxelPassageType` default); placement and shape values are independently salted hashes of seed + boundary slot + connection index. Each mouth independently queries its own strate's pure player-fit `VF_SuggestLandingPoint` with its contract seed (cave room salt, otherwise world seed), may move within its archetype-specific budget, then receives a body-sized local room/floor. The origin landing is reserved for the future shaft; no landing-to-root road is generated. Unsupported/no-fit answers preserve the old random reach; a deterministic level dog-leg is inserted when the final ramp needs it to stay ≤44°. All descriptors and bounds are generated without reading another passage. | | `GeneratePassages` | 1659 | Deterministic passages between consecutive strates (per-strate `PassageConfig::Style` control points; auto passages retain the existing `EVoxelPassageType` default); placement and shape values are independently salted hashes of seed + boundary slot + connection index. Each mouth independently queries its own strate's pure player-fit `VF_SuggestLandingPoint` with its contract seed (cave room salt, otherwise world seed), may move within its archetype-specific budget, then receives a body-sized local room/floor. The origin landing is reserved for the future shaft; no landing-to-root road is generated. Unsupported/no-fit answers preserve the old random reach; a deterministic level dog-leg is inserted when the final ramp needs it to stay ≤44°. All descriptors and bounds are generated without reading another passage. |
| `EvaluateModifierSDF` | 2643 | SDF of passage tubes plus local landing rooms at a point. A per-chunk `thread_local` shortlist (`PassagesVersion`-stamped) → far chunks return `FLT_MAX` without walking `Passages`; the same cache serves floor membership. `FPassageEvaluationCache::FloorProjections` reuses native/generic floor projections and walkable-air results once per `(sample, passage)` using exact double XYZ equality and unchanged float outputs; `voxel.FloorRound1PassageProjectionCache=0` disables only this A/B cache. §8.10. | | `EvaluateModifierSDF` | 2643 | SDF of passage tubes plus local landing rooms at a point. A per-chunk `thread_local` shortlist (`PassagesVersion`-stamped) → far chunks return `FLT_MAX` without walking `Passages`; the same cache serves floor membership. `FPassageEvaluationCache::FloorProjections` reuses native/generic floor projections and walkable-air results once per `(sample, passage)` using exact double XYZ equality and unchanged float outputs; `voxel.FloorRound1PassageProjectionCache=0` disables only this A/B cache. §8.10. |
| `ApplyPassageModifier` | 2772 | Shared legacy/op-stack passage post: carves tube and local landing air through the idempotent landing primitive, then restores the local landing support slab in internal positive-solid density. | | `ApplyPassageModifier` | 2772 | Shared legacy/op-stack passage post: carves tube and local landing air through the idempotent landing primitive, then restores the local landing support slab in internal positive-solid density. |
@@ -612,18 +619,18 @@ Maps depth→strate at runtime; owns passages.
| `ApplyPassageLandingFloorMC` | 2983 | Reasserts the same support slab after MC-space disturbances and before the final XY edge seal. | | `ApplyPassageLandingFloorMC` | 2983 | Reasserts the same support slab after MC-space disturbances and before the final XY edge seal. |
| `AnyPassageNearBox` | — | Conservative sphere-vs-AABB test of every passage's bound against a voxel box (+carve blend pad). Per TILE (ClassifyTile guard), never per voxel. | | `AnyPassageNearBox` | — | Conservative sphere-vs-AABB test of every passage's bound against a voxel box (+carve blend pad). Per TILE (ClassifyTile guard), never per voxel. |
| `AnyPassageLandingFloorNearBox` | — | Conservative local-landing-floor AABB guard. It kills the `AllAir` hypothesis wherever a proved landing floor can occur; the op-stack returns `Both` for the same box. | | `AnyPassageLandingFloorNearBox` | — | Conservative local-landing-floor AABB guard. It kills the `AllAir` hypothesis wherever a proved landing floor can occur; the op-stack returns `Both` for the same box. |
| `FindSlotIndexForChunkZ` | 6111 | Z → layout index. | | `FindSlotIndexForChunkZ` / `FindSlotIndexForWorldZCentimetres` | 6239 / 6251 | Chunk callers use the cached compatibility projection; world-Z callers search physical half-open micrometre intervals. |
| `GetStrateAt` / `GetStrateIndex` | 6127 / 6139 | World-Z queries. | | `GetStrateAt` / `GetStrateIndex` | 6276 / 6287 | World-Z queries against half-open micrometre intervals. |
| `GetLayoutVersion` | h:250 (inline) | Layout/passage generation counter (= `PassagesVersion`, bumped by every `Initialize` and by the editor composer override). Hot-path callers key `thread_local` memos on it (strate-index memo in `GetDensityWithParams`, passage shortlist) so live changes never serve stale data. | | `GetLayoutVersion` | h:250 (inline) | Layout/passage generation counter (= `PassagesVersion`, bumped by every `Initialize` and by the editor composer override). Hot-path callers key `thread_local` memos on it (strate-index memo in `GetDensityWithParams`, passage shortlist) so live changes never serve stale data. |
| `GetStrateForChunk` | 6150 | Chunk → definition. | | `GetStrateForChunk` | 6298 | Chunk → definition via the compatibility projection. |
| `GetGeneratorTypeForChunk` | 6176 | Chunk → generator type. | | `GetGeneratorTypeForChunk` | 6324 | Chunk → generator type via the compatibility projection. |
| `UsesOperatorStackForChunk` | 6203 | Chunk → does its slot have an operator stack? True for any in-layout slot with a definition whose archetype is one of the 8 (Maze, FlatPlain, CrystalChamber, SurfaceWorld, VerticalShafts, FloatingIslands, TunnelNetwork, Underwater); false outside the layout. **That list is written down here and nowhere else.** `ClassifyTile` guards on it; `GetDensityAt` uses it to share TunnelNetwork stacks by chunk Z. | | `UsesOperatorStackForChunk` | 6351 | Chunk → does its slot have an operator stack? True for any in-layout slot with a definition whose archetype is one of the 8 (Maze, FlatPlain, CrystalChamber, SurfaceWorld, VerticalShafts, FloatingIslands, TunnelNetwork, Underwater); false outside the layout. **That list is written down here and nowhere else.** `ClassifyTile` guards on it; `GetDensityAt` uses it to share TunnelNetwork stacks by chunk Z. |
| `GetRecipeForChunk` | — | Runtime immutable recipe/vector/strate-seed copy for cooked seasons; editor slot overrides use the same worker hand-off. | | `GetRecipeForChunk` | — | Runtime immutable recipe/vector/strate-seed copy for cooked seasons; editor slot overrides use the same worker hand-off. |
| `GetSlabParamsForChunk` | 6263 | Slab params with runtime Z bounds (no blend — slabs use Hard). | | `GetSlabParamsForChunk` | 6415 | Slab params with runtime Z bounds (no blend — slabs use Hard). |
| `GetBiomeContextForChunk` | — | Flatten the strate's `Biomes[]` + `BiomeMapParams` into a POD `FBiomeContext` for the biome field. Empty ⇒ biomes disabled. §8.14. | | `GetBiomeContextForChunk` | — | Flatten the strate's `Biomes[]` + `BiomeMapParams` into a POD `FBiomeContext` for the biome field. Empty ⇒ biomes disabled. §8.14. |
| `GetGenerationParams` | 6509 | **Blended** TunnelNetwork params (handles Gradient/Hard/Interleaved transitions). | | `GetGenerationParams` | 6661 | **Blended** TunnelNetwork params (handles Gradient/Hard/Interleaved transitions). |
| `SetComposerOverrideForStrate` / `GetComposerOverrideForChunk` | editor-only | Temporary density-only candidate overlay for one existing slot. Copies immutable params/recipe into the worker refetch path, leaves layout/content/passages intact, and bumps `PassagesVersion` to invalidate generator memos. | | `SetComposerOverrideForStrate` / `GetComposerOverrideForChunk` | editor-only | Temporary density-only candidate overlay for one existing slot. Copies immutable params/recipe into the worker refetch path, leaves layout/content/passages intact, and bumps `PassagesVersion` to invalidate generator memos. |
| `BuildParamsFromDefinition` (static) | 6815 | Returns the definition's base `GenerationParams` only. Terrain-op assets are passed separately to `BuildChunkCache`, which applies one selected op per room and pre-bakes Column/Pit/Chimney features. | | `BuildParamsFromDefinition` (static) | 6985 | Returns the definition's base `GenerationParams` only. Terrain-op assets are passed separately to `BuildChunkCache`, which applies one selected op per room and pre-bakes Column/Pit/Chimney features. |
**`Public/VoxelTerrainOpDefinition.h` + `.cpp`** — `UVoxelTerrainOpDefinition : UPrimaryDataAsset` **`Public/VoxelTerrainOpDefinition.h` + `.cpp`** — `UVoxelTerrainOpDefinition : UPrimaryDataAsset`
(h:67). One asset = one terrain op. `EVoxelTerrainOpType` (h:36): Terrace, LayerLines, (h:67). One asset = one terrain op. `EVoxelTerrainOpType` (h:36): Terrace, LayerLines,
@@ -1160,7 +1167,7 @@ and [`Standalone/Research/WorldAssemblerM3/Linux18/REPORT.md`](Standalone/Resear
| Symbol | Canonical file | Role | | Symbol | Canonical file | Role |
|---|---|---| |---|---|---|
| `PlanResolvedLayoutPassagesV1`, `ResolvedLayoutPassageV1::lowerMouthControlIndex` | `Standalone/Core/src/ResolvedLayoutInternal.h`, public record in `VoxelForgeCore.h` | Plans deterministic fixed-metre passages from adjacent director-resolved strata, binds route departure/arrival mouths, and records the derived split between the monotone descent and route-bound landing tail. | | `PlanResolvedLayoutPassagesV1`, `ResolvedLayoutPassageV1::lowerMouthControlIndex` | `Standalone/Core/src/ResolvedLayoutInternal.h`, public record in `VoxelForgeCore.h` | Plans deterministic fixed-metre passages from adjacent director-resolved strata, binds route departure/arrival mouths, and records the derived split between the monotone descent and route-bound landing tail. |
| `FStandaloneShapeWorld::ApplyResolvedStrateComposition`, `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies boundary bands first, then opens the passage capsule and supported floor along the passage for every native generator. | | `FStandaloneShapeWorld::ApplyResolvedStrateComposition`, `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies resolved boundary bands first, then opens the passage capsule and supported floor along the passage for every native generator. |
| `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples passage capsules, floor support, mouth ownership/radius, in-strate route bindings, and a blocked-passage negative control. | | `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples passage capsules, floor support, mouth ownership/radius, in-strate route bindings, and a blocked-passage negative control. |
The evaluator version is 9. See ARCHITECTURE §8.41 and The evaluator version is 9. See ARCHITECTURE §8.41 and
@@ -1217,6 +1224,106 @@ schema V3 is unchanged. See ARCHITECTURE §8.44 and
See ARCHITECTURE §8.45 and See ARCHITECTURE §8.45 and
[Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md). [Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md).
### 3.29 Honored bedrock/ceiling transitions — World Assembler L29
| Symbol | Canonical file | Role |
|---|---|---|
| `EvaluateResolvedLayoutTransitionBandsV1`, `AuditResolvedStrateTransitions` | Public Core header / `Standalone/Core/src/ResolvedLayoutInternal.h`; `Standalone/Source/StreamBenchmarkPipeline.h` | Resolve the deterministic mode field from seed, interface and coordinates; audit actual crossing depth, minimum band thickness, passage-excluded holes, and strict negative controls. |
| `FStandaloneShapeWorld::ApplyResolvedStrateBoundaryBand` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies Hard, Gradient or Interleaved to both facing bands in the shared Core composition path; tapers locally at passage mouths to preserve landing support. |
The shared evaluator is version 15. Recipe and resolved-layout schema versions are unchanged.
The Unreal adapter now forwards the authored transition type and chunk width; those Unreal edits
were not compiled here. See ARCHITECTURE §8.46 and
[`Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md).
### 3.30 SurfaceWorld as an upper strate and floor-profile joins — World Assembler L33
| Symbol | Canonical file | Role |
|---|---|---|
| `BuildFiniteStrateRoute` | `Standalone/Source/WorldAssemblyPrototype.h` | Samples route heights from the composed terrain ground; bounds candidate chords by the arrival/departure placement radii and records per-segment grade failures. |
| `PlanResolvedLayoutPassagesV1` | `Standalone/Core/src/ResolvedLayoutInternal.h` | Starts an upper-SurfaceWorld departure at its terrain mouth, opens a small portal and smoothsteps to the rock tunnel while orienting the ring outward. |
| `BuildPassageVoidCrossingPlan`, `PassageBridgeSignedDistance` | `Standalone/Core/src/ShapeWorldInternal.h` | Sizes unsupported ranges from the full control-segment window only for upper SurfaceWorld; other upper generators keep the previous bracketing-sample bounds. |
| `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Uses floor height rather than bore-centre height for naturalness and records failed-only wall probes for column audits. |
| `FloorProfileBlend` | `Standalone/Core/src/ShapeWorldInternal.h` | Measures endpoint height/tangent continuity; chord deviation is informational, with step and tangent-step negative controls. |
The owner SurfaceWorld hashes match L32 and evaluator/recipe versions are unchanged. See
ARCHITECTURE §8.47 and
[`Standalone/Research/WorldAssemblerM3/Linux33/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux33/REPORT.md).
### 3.31 Passage-wall ripple and FI tread-shoulder audit — World Assembler M3 L35
| Symbol | Canonical file | Role |
|---|---|---|
| `VF_FieldPassageWallRelief` | `Standalone/Core/src/VoxelForgeFieldShared.h` | Supplies passage-only irregular wall relief at rotated 43/29/19 m scales. Other `VF_FieldSurfaceRelief` users stay unchanged. |
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Catmull–Rom smooths eligible interior sweep joins, applies passage-only wall relief, and tapers/grains the entry support shoulder below the capsule/support probe footprint. |
| `PlanResolvedLayoutPassagesV1` | `Standalone/Core/src/ResolvedLayoutInternal.h` | Gives each Cascade turn a seeded shallow radial profile instead of identical repeated full-depth cosine lobes. |
| `auditPassageRipples` / `PassageRippleAudit` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples wall radii and walk-floor zero crossings at 0.125 m spacing, scans periods 0.5–8 m in 32 m windows, and rejects wall/floor amplitudes ≥0.035/0.015 m. Sphere and beaded-floor controls must be detected. |
`WorldAssemblyEvaluatorVersion=21`. See ARCHITECTURE §8.48 and
[`Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md).
### 3.32 Walk-floor washboard and FI mouth blend — World Assembler M3 L36
| Symbol | Canonical file | Role |
|---|---|---|
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Extends eligible tread sections by up to 2 m and uses bounded signed-distance support on every sparse route, including SurfaceWorld-to-TunnelNetwork, removes seeded shoulder grain, and seats regular tread tops 5 cm lower with a smooth return at the lower landing. Capsule/open-air gates remain. Gravity-facing wall relief is reduced; side and ceiling relief remain. |
| `ApplyResolvedStratePassageCarves` mouth support | `Standalone/Core/src/ShapeWorldInternal.h` | Builds exact signed-distance route-mouth tread boxes and smooth-unions them with native terrain over 1.5 m. |
| `NativeFloatingIslandDensityAt` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies a local 2 m symmetric signed-field filter around FI passage mouths, exempts resolved route support, and removes isolated negative samples thinner than 2 m on all axes. |
| `auditPassageRipples` / `PassageRippleAudit` | `Standalone/Source/StreamBenchmarkPipeline.h` | Keeps the wall-radius audit and adds walk-floor zero-crossing profiles at 0.125 m, with five 32 m windows and a 0.015 m limit plus a beaded-floor negative control. |
| `RunMouthFragmentAuditL36.py`, `ProbeWalkFloorProfileL36.py` | `Standalone/Research/WorldAssemblerM3/Linux36/` | Compare L35 floor and FI-mouth controls with L36; the mouth check fails on detached components at or above 0.0001 m² and checks normal jumps across the fade ring. |
`WorldAssemblyEvaluatorVersion=23`. See ARCHITECTURE §8.49 and
[`Standalone/Research/WorldAssemblerM3/Linux36/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux36/REPORT.md).
### 3.33 Immutable field binding — World Assembler M3 L40
| Symbol | Canonical file | Role |
|---|---|---|
| `FSurfaceWorldFieldAdapter`, `FFloatingIslandFieldAdapter`, `FLegacyGeneratorFieldAdapter`, `FGeneratorFieldAdapterV1` | `Standalone/Core/src/ShapeWorldInternal.h` | Concrete, immutable density adapters with one shared `densityAt(x,y,z,out)` call shape; the variant avoids per-sample virtual dispatch. FI retains its fixed-point entry point and prebound legacy boundary parameters. |
| `FBoundStratumField`, `BindResolvedStratumFields`, `FindBoundStratumForChunk` | `Standalone/Core/src/ShapeWorldInternal.h` | Bind a resolved strate's plan, evaluator and chunk interval once during world initialization; native field and classification queries select this table. |
| `NativeSurfaceDensityAt`, `NativeFloatingIslandDensityAt`, `ClassifyBoxDetailed` | `Standalone/Core/src/ShapeWorldInternal.h` | Consume the prebound adapters and plan pointers instead of looking up strate type/parameters through the manager per query. |
Hash parity is unchanged from L37/L39; see ARCHITECTURE §8.50 and
[`Standalone/Research/WorldAssemblerM3/Linux40/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux40/REPORT.md).
### 3.34 Resolved boundary and landing capabilities — World Assembler M3 L42
| Symbol | Canonical file | Role |
|---|---|---|
| `ResolvedLayoutStratePassageCapabilitiesV1`, `ResolvedLayoutInterfaceCapabilitiesV1` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Runtime-only resolved records for surface/island landing, passage-column anchoring, the FI Gradient air gap, and SW-to-TunnelNetwork support. They are derived during layout finalization from fields already included in the canonical layout identity. |
| `ResolvedLayoutSnapshotV3::finalize` | `Standalone/Core/src/ResolvedLayoutInternal.h` | Resolves those capabilities once before sampling; canonical JSON and stable layout IDs stay unchanged. |
| `FStandaloneShapeWorld::ApplyResolvedStrateBoundaryBand`, `ApplyResolvedStratePassageCarves`, `BuildPassageVoidCrossingPlan`, `PassageBridgeSignedDistance` | `Standalone/Core/src/ShapeWorldInternal.h` | Consume resolved capability records. Generator-kind selection remains only in `BindResolvedStratumFields`, where it binds the concrete evaluator once. |
Owner hashes match L41; see ARCHITECTURE §8.52 and
[`Standalone/Research/WorldAssemblerM3/Linux42/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux42/REPORT.md).
### 3.35 Core landmark SDF stamps — World Assembler M3 L43
| Symbol | Canonical file | Role |
|---|---|---|
| `ResolvedLayoutStampRequestV1`, `ResolvedLayoutStampV1`, `ResolvedSdfStampKindV1` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Version-1 bounded stamp vocabulary and integer-micrometre request/resolved records shared by all Core generators. |
| `ResolveResolvedLayoutStampsV1`, `AuditResolvedLayoutStampPlacementV1` | `Standalone/Core/src/ResolvedLayoutInternal.h` | Seed/strate/stable-ID placement with bounded retries; rejects passage-mouth, walk-corridor, content-face, ceiling and bedrock conflicts. Empty requests preserve the old canonical layout payload. |
| `BuildStampSpatialIndex`, `ApplyResolvedSdfStamps`, `StampSdf` | `Standalone/Core/src/ShapeWorldInternal.h` | BVH-built conservative bounds select stamps once per tile; shared density composition applies smooth carve/fill SDFs with seeded value-noise relief. Box classification and native-only readiness decline boxes touched by a stamp. |
| `--landmark-chamber-demo` | `Standalone/Source/StreamBenchmarkHeadless.cpp` | Opt-in owner-world recipe: one SW ellipsoid chamber, arched entrance and low fill dais, placed beside the passage arrival. Off by default. |
See ARCHITECTURE §8.53 and
[`Standalone/Research/WorldAssemblerM3/Linux43/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux43/REPORT.md)
for layout identities, owner hashes, outside-box timing, audits and captures.
### 3.36 Native lattice field — World Assembler M3 R26 slices 1–2 (Linux L58–L59)
| Symbol | Canonical file | Role |
|---|---|---|
| `NativeLatticeFieldRecipeVersion`, `IsNativeLatticeFieldStratum`, `LayoutLatticeFeatureSeed` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/ResolvedLayoutInternal.h` | Version-2 Maze/VerticalShafts opt-in and deterministic seed from `(seed, strate, recipe version, stable ID)`. |
| `MakeNativeLatticeSpine`, `--recipe=lattice`, `--lattice-strate=N` | `Standalone/Source/StreamBenchmarkHeadless.cpp` | Resolves actual adjacent mouths, builds the radius-4.5 m open shaft core, supported four-turn wall ledge and maze spine, then adds two seeded dead ends; the orphan control translates the shaft away from both junctions. |
| `AuditNativeLatticeField` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples the final field along the mouth-to-mouth capsule route, checking every-station support, headroom, grade, orphan rejection, composed fillet, shaft air/wall intervals and tile exclusion. |
| `BuildLatticeFixtureL59.py`, `CaptureNativeLatticeL59.py`, `AuditLatticeSurfaceL59.py` | `Standalone/Research/WorldAssemblerM3/Linux59/` | Isolated Maze fixture, four JPEG views of the open shaft and maze, and L54 whole-surface audit extended to include the ramp underside. |
See ARCHITECTURE §8.54 and the
[`Linux59 report`](Standalone/Research/WorldAssemblerM3/Linux59/REPORT.md) for the open shaft's
measured geometry, final-field reachability, controls, timing, hashes and captures. The L58 report
retains the first-slice evidence.
## 4. The density pipeline (most-edited hot path) ## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 ### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
@@ -107,6 +107,14 @@ std::int64_t WorldAssemblyRoundRatio(std::int64_t Numerator, std::int64_t Denomi
return -((-Numerator + Denominator / 2) / Denominator); return -((-Numerator + Denominator / 2) / Denominator);
} }
std::int64_t WorldAssemblyMicrometresToSubvoxels(
std::int64_t Micrometres, std::uint32_t CoreVoxelMicrometres)
{
if (CoreVoxelMicrometres == 0) return 0;
return WorldAssemblyRoundRatio(Micrometres * WorldAssemblySubvoxelsPerVoxel,
static_cast<std::int64_t>(CoreVoxelMicrometres));
}
std::uint64_t WorldAssemblyRoundedSqrt(std::uint64_t Value) std::uint64_t WorldAssemblyRoundedSqrt(std::uint64_t Value)
{ {
std::uint64_t Root = 0; std::uint64_t Root = 0;
@@ -312,17 +320,18 @@ VoxelForgeCore::WorldAssemblyBiomeProfileV1 BuildSurfaceProfile(
bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager, bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
const FStrateSlot& Slot, const FStrateSlot& Slot,
VoxelForgeCore::WorldAssemblyRecipeV1& Recipe, VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum, FString& OutError) VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum,
std::uint32_t CoreVoxelMicrometres, FString& OutError)
{ {
using namespace VoxelForgeCore; using namespace VoxelForgeCore;
const FIntVector Chunk(0, 0, Slot.TopChunkZ); const FIntVector Chunk(0, 0, Slot.TopChunkZ);
const FSurfaceGenerationParams Surface = StrateManager->GetSurfaceParamsForChunk(Chunk); const FSurfaceGenerationParams Surface = StrateManager->GetSurfaceParamsForChunk(Chunk);
const FBiomeContext BiomeContext = StrateManager->GetBiomeContextForChunk(Chunk); const FBiomeContext BiomeContext = StrateManager->GetBiomeContextForChunk(Chunk);
const UVoxelStrateDefinition* Definition = Slot.Definition; const UVoxelStrateDefinition* Definition = Slot.Definition;
const double LayoutChunkVoxels = static_cast<double>(Recipe.layoutChunkWidthSubvoxels) const double BottomVoxel = static_cast<double>(Slot.BottomWorldMicrometres)
/ WorldAssemblySubvoxelsPerVoxel; / std::max<std::uint32_t>(1, CoreVoxelMicrometres);
const double BottomVoxel = static_cast<double>(Slot.BottomChunkZ) * LayoutChunkVoxels; const double HeightVoxels = static_cast<double>(Slot.HeightMicrometres)
const double HeightVoxels = static_cast<double>(Slot.TopChunkZ - Slot.BottomChunkZ + 1) * LayoutChunkVoxels; / std::max<std::uint32_t>(1, CoreVoxelMicrometres);
const FBiomeMapParams& Map = BiomeContext.Map; const FBiomeMapParams& Map = BiomeContext.Map;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed()); Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.biomeCellSizeSubvoxels = WorldAssemblyFixed(Map.CellSize); Recipe.biomeCellSizeSubvoxels = WorldAssemblyFixed(Map.CellSize);
@@ -454,9 +463,12 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
return false; return false;
} }
Shelf.minimumSubvoxels = {WorldAssemblyFixed(MinX), WorldAssemblyFixed(MinY), Shelf.minimumSubvoxels = {WorldAssemblyFixed(MinX), WorldAssemblyFixed(MinY),
std::max(WorldAssemblyFixed(BottomVoxel), LowestGround - WorldAssemblyFixed(4.0))}; std::max(WorldAssemblyMicrometresToSubvoxels(
Slot.BottomWorldMicrometres, CoreVoxelMicrometres),
LowestGround - WorldAssemblyFixed(4.0))};
Shelf.maximumSubvoxels = {WorldAssemblyFixed(MaxX), WorldAssemblyFixed(MaxY), Shelf.maximumSubvoxels = {WorldAssemblyFixed(MaxX), WorldAssemblyFixed(MaxY),
std::min(WorldAssemblyFixed((Slot.TopChunkZ + 1) * LayoutChunkVoxels), std::min(WorldAssemblyMicrometresToSubvoxels(
Slot.TopWorldMicrometres, CoreVoxelMicrometres),
HighestGround + WorldAssemblyFixed(ShelfHeight + 4.0))}; HighestGround + WorldAssemblyFixed(ShelfHeight + 4.0))};
Shelf.parametersSubvoxels = {WorldAssemblyFixed(DirX), WorldAssemblyFixed(DirY), Shelf.parametersSubvoxels = {WorldAssemblyFixed(DirX), WorldAssemblyFixed(DirY),
WorldAssemblyFixed(ShelfHeight), WorldAssemblyFixed(Reach)}; WorldAssemblyFixed(ShelfHeight), WorldAssemblyFixed(Reach)};
@@ -479,13 +491,14 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
using namespace VoxelForgeCore; using namespace VoxelForgeCore;
const FIntVector Chunk(0, 0, Slot.TopChunkZ); const FIntVector Chunk(0, 0, Slot.TopChunkZ);
const FFloatingIslandParams Params = StrateManager->GetFloatingIslandParamsForChunk(Chunk); const FFloatingIslandParams Params = StrateManager->GetFloatingIslandParamsForChunk(Chunk);
const FStratePassageConfig& Passage = Slot.Definition->PassageConfig; const FStratePassageConfig& Passage = Slot.ResolvedPassageConfig;
WorldAssemblyFloatingIslandPlanV1 IslandPlan; WorldAssemblyFloatingIslandPlanV1 IslandPlan;
IslandPlan.strateIndex = Slot.StrateIndex; IslandPlan.strateIndex = Slot.StrateIndex;
IslandPlan.topChunkZ = Slot.TopChunkZ; IslandPlan.topChunkZ = Slot.TopChunkZ;
IslandPlan.bottomChunkZ = Slot.BottomChunkZ; IslandPlan.bottomChunkZ = Slot.BottomChunkZ;
auto& Recipe = IslandPlan.manifest.recipe; auto& Recipe = IslandPlan.manifest.recipe;
Recipe.geometryVersion = 8; Recipe.geometryVersion = 8;
Recipe.terrainFollowingRoute = static_cast<std::uint32_t>(Passage.Style) == 0;
Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels; Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed()); Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0)); Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
@@ -499,10 +512,10 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
} }
Recipe.ownerToCoreScaleNumerator = Recipe.ownerVoxelSizeMicrons / ScaleGcd; Recipe.ownerToCoreScaleNumerator = Recipe.ownerVoxelSizeMicrons / ScaleGcd;
Recipe.ownerToCoreScaleDenominator = Recipe.coreVoxelSizeMicrons / ScaleGcd; Recipe.ownerToCoreScaleDenominator = Recipe.coreVoxelSizeMicrons / ScaleGcd;
Recipe.layoutMinimumZSubvoxels = static_cast<std::int64_t>(Slot.BottomChunkZ) Recipe.layoutMinimumZSubvoxels = WorldAssemblyMicrometresToSubvoxels(
* LayoutChunkWidthSubvoxels; Slot.BottomWorldMicrometres, Recipe.coreVoxelSizeMicrons);
Recipe.layoutMaximumZSubvoxels = static_cast<std::int64_t>(Slot.TopChunkZ + 1) Recipe.layoutMaximumZSubvoxels = WorldAssemblyMicrometresToSubvoxels(
* LayoutChunkWidthSubvoxels; Slot.TopWorldMicrometres, Recipe.coreVoxelSizeMicrons);
Recipe.layoutCenterTopZSubvoxels = (Recipe.layoutMinimumZSubvoxels Recipe.layoutCenterTopZSubvoxels = (Recipe.layoutMinimumZSubvoxels
+ Recipe.layoutMaximumZSubvoxels) / 2; + Recipe.layoutMaximumZSubvoxels) / 2;
Recipe.islandDensityQ16 = WorldAssemblyQ16(FMath::Clamp(Params.IslandDensity, 0.0f, 1.0f)); Recipe.islandDensityQ16 = WorldAssemblyQ16(FMath::Clamp(Params.IslandDensity, 0.0f, 1.0f));
@@ -523,20 +536,35 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
*WorldAssemblySubvoxelsPerVoxel/Recipe.coreVoxelSizeMicrons)); *WorldAssemblySubvoxelsPerVoxel/Recipe.coreVoxelSizeMicrons));
}; };
Recipe.anchorIslandOrdinal=Recipe.primaryIslandCount-1; Recipe.anchorIslandOrdinal=Recipe.primaryIslandCount-1;
Recipe.anchorPlacementRadiusSubvoxels=OwnerVoxelsToCoreQ(Passage.DistanceMax); Recipe.anchorPlacementRadiusSubvoxels=WorldAssemblyMicrometresToSubvoxels(
const std::int64_t spiralRadius=OwnerVoxelsToCoreQ(Passage.SpiralRadius); Slot.PassageDistanceMaximumMicrometres, Recipe.coreVoxelSizeMicrons);
const std::int64_t spiralRadius=WorldAssemblyMicrometresToSubvoxels(
Slot.PassageSpiralRadiusMicrometres, Recipe.coreVoxelSizeMicrons);
// Reserve at least a 1.5 m mid-bore through the anchor root. // Reserve at least a 1.5 m mid-bore through the anchor root.
constexpr double AnchorPassageMinimumBoreOwnerVoxels=12.0; constexpr double AnchorPassageMinimumBoreOwnerVoxels=12.0;
const std::int64_t mouthBore=std::max(OwnerVoxelsToCoreQ(Passage.MouthRadius), const std::int64_t OwnerVoxelMicrometres = Recipe.ownerVoxelSizeMicrons;
OwnerVoxelsToCoreQ(12.0)); const std::int64_t mouthBore=std::max(
const std::int64_t bore=std::max(OwnerVoxelsToCoreQ(Passage.MidRadius), WorldAssemblyMicrometresToSubvoxels(Slot.PassageMouthRadiusMicrometres,
OwnerVoxelsToCoreQ(AnchorPassageMinimumBoreOwnerVoxels)); Recipe.coreVoxelSizeMicrons),
const std::int64_t wallReserve=OwnerVoxelsToCoreQ(12.0); WorldAssemblyMicrometresToSubvoxels(12 * OwnerVoxelMicrometres,
Recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+OwnerVoxelsToCoreQ(4.0); Recipe.coreVoxelSizeMicrons));
const std::int64_t excursion=std::max(OwnerVoxelsToCoreQ(Passage.Wander),spiralRadius); const std::int64_t bore=std::max(
WorldAssemblyMicrometresToSubvoxels(Slot.PassageMidRadiusMicrometres,
Recipe.coreVoxelSizeMicrons),
WorldAssemblyMicrometresToSubvoxels(
static_cast<std::int64_t>(AnchorPassageMinimumBoreOwnerVoxels * OwnerVoxelMicrometres),
Recipe.coreVoxelSizeMicrons));
const std::int64_t wallReserve=WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrometres, Recipe.coreVoxelSizeMicrons);
const std::int64_t FourOwnerVoxelsSubvoxels=WorldAssemblyMicrometresToSubvoxels(
4 * OwnerVoxelMicrometres, Recipe.coreVoxelSizeMicrons);
Recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+FourOwnerVoxelsSubvoxels;
const std::int64_t excursion=std::max(
WorldAssemblyMicrometresToSubvoxels(Slot.PassageWanderMicrometres,
Recipe.coreVoxelSizeMicrons), spiralRadius);
Recipe.anchorRootRadiusSubvoxels=excursion+bore+wallReserve; Recipe.anchorRootRadiusSubvoxels=excursion+bore+wallReserve;
Recipe.anchorIslandMinimumRadiusSubvoxels=std::max(Recipe.minimumRadiusSubvoxels, Recipe.anchorIslandMinimumRadiusSubvoxels=std::max(Recipe.minimumRadiusSubvoxels,
Recipe.anchorMouthOffsetSubvoxels+mouthBore+OwnerVoxelsToCoreQ(4.0)); Recipe.anchorMouthOffsetSubvoxels+mouthBore+FourOwnerVoxelsSubvoxels);
Recipe.anchorRootBottomZSubvoxels=Recipe.layoutMinimumZSubvoxels Recipe.anchorRootBottomZSubvoxels=Recipe.layoutMinimumZSubvoxels
+Recipe.boundarySealSubvoxels; +Recipe.boundarySealSubvoxels;
std::string PlanError; std::string PlanError;
@@ -881,7 +909,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
SlotRecipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels; SlotRecipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
VoxelForgeCore::WorldAssemblyStratumPlanV1 Stratum; VoxelForgeCore::WorldAssemblyStratumPlanV1 Stratum;
if (!BuildSurfaceStratumPlan(StrateManager, Slot, if (!BuildSurfaceStratumPlan(StrateManager, Slot,
SlotRecipe, Stratum, OutError)) SlotRecipe, Stratum, CoreVoxelMicrometres, OutError))
{ {
return false; return false;
} }
@@ -1047,41 +1075,47 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
return "Unknown"; return "Unknown";
}; };
const std::int64_t OwnerVoxelMicrons = Layout.ownerVoxelSizeMicrometres; const std::int64_t OwnerVoxelMicrons = Layout.ownerVoxelSizeMicrometres;
const auto FixedToMicrons = [ExpectedCoreVoxelMicrons](std::int64_t Fixed)
{
return static_cast<std::int64_t>(std::llround(static_cast<double>(Fixed)
* ExpectedCoreVoxelMicrons / VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
};
std::vector<VoxelForgeCore::ResolvedLayoutPassageConfigV1> PassageConfigs; std::vector<VoxelForgeCore::ResolvedLayoutPassageConfigV1> PassageConfigs;
for (const FStrateSlot& Slot : StrateManager->GetLayout()) for (const FStrateSlot& Slot : StrateManager->GetLayout())
{ {
if (Slot.Definition == nullptr) continue; if (Slot.Definition == nullptr) continue;
const FStratePassageConfig& AuthoredPassage = Slot.Definition->PassageConfig; const FStratePassageConfig& ResolvedPassage = Slot.ResolvedPassageConfig;
const auto OwnerVoxelsToCoreQ = [OwnerVoxelMicrons, ExpectedCoreVoxelMicrons](double Value)
{
return static_cast<std::int64_t>(std::llround(Value * OwnerVoxelMicrons
* VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel / ExpectedCoreVoxelMicrons));
};
VoxelForgeCore::ResolvedLayoutPassageConfigV1 PassageConfig; VoxelForgeCore::ResolvedLayoutPassageConfigV1 PassageConfig;
PassageConfig.upperStrateIndex = Slot.StrateIndex; PassageConfig.upperStrateIndex = Slot.StrateIndex;
PassageConfig.connections = AuthoredPassage.Connections; PassageConfig.connections = ResolvedPassage.Connections;
PassageConfig.style = static_cast<std::uint32_t>(AuthoredPassage.Style); PassageConfig.style = static_cast<std::uint32_t>(ResolvedPassage.Style);
PassageConfig.mouthRadiusSubvoxels = std::max(OwnerVoxelsToCoreQ(AuthoredPassage.MouthRadius), PassageConfig.mouthRadiusSubvoxels = std::max(
OwnerVoxelsToCoreQ(12.0)); WorldAssemblyMicrometresToSubvoxels(
PassageConfig.midRadiusSubvoxels = std::max(OwnerVoxelsToCoreQ(AuthoredPassage.MidRadius), Slot.PassageMouthRadiusMicrometres, ExpectedCoreVoxelMicrons),
OwnerVoxelsToCoreQ(12.0)); WorldAssemblyMicrometresToSubvoxels(
PassageConfig.reachMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMin); 12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.reachMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMax); PassageConfig.midRadiusSubvoxels = std::max(
PassageConfig.distanceMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMin); WorldAssemblyMicrometresToSubvoxels(
PassageConfig.distanceMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMax); Slot.PassageMidRadiusMicrometres, ExpectedCoreVoxelMicrons),
PassageConfig.wanderSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.Wander); WorldAssemblyMicrometresToSubvoxels(
PassageConfig.segments = static_cast<std::uint32_t>(FMath::Clamp(AuthoredPassage.Segments,1,48)); 12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.verticalWobbleSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.VerticalWobble); PassageConfig.reachMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
PassageConfig.spiralRadiusSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.SpiralRadius); Slot.PassageReachMinimumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.reachMaximumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageReachMaximumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.distanceMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageDistanceMinimumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.distanceMaximumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageDistanceMaximumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.wanderSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageWanderMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.segments = static_cast<std::uint32_t>(
FMath::Clamp(ResolvedPassage.Segments, 1, 48));
PassageConfig.verticalWobbleSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageVerticalWobbleMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.spiralRadiusSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageSpiralRadiusMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.spiralTurnsQ16 = static_cast<std::int64_t>(std::llround( PassageConfig.spiralTurnsQ16 = static_cast<std::int64_t>(std::llround(
static_cast<double>(AuthoredPassage.SpiralTurns)*65536.0)); static_cast<double>(ResolvedPassage.SpiralTurns) * 65536.0));
PassageConfig.cascadeSteps = static_cast<std::uint32_t>(FMath::Clamp(AuthoredPassage.CascadeSteps,1,16)); PassageConfig.cascadeSteps = static_cast<std::uint32_t>(
PassageConfig.cascadeLedgeSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.CascadeLedge); FMath::Clamp(ResolvedPassage.CascadeSteps, 1, 16));
PassageConfig.cascadeLedgeSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageCascadeLedgeMicrometres, ExpectedCoreVoxelMicrons);
PassageConfigs.push_back(PassageConfig); PassageConfigs.push_back(PassageConfig);
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk( const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ)); FIntVector(0, 0, Slot.TopChunkZ));
@@ -1095,24 +1129,29 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
: std::string("legacy.") + generatorName(Type); : std::string("legacy.") + generatorName(Type);
Item.recipeVersion = Type == ECaveGeneratorType::SurfaceWorld ? Manifest->recipe.schemaVersion : 0; Item.recipeVersion = Type == ECaveGeneratorType::SurfaceWorld ? Manifest->recipe.schemaVersion : 0;
Item.voxelSizeMicrometres = ExpectedCoreVoxelMicrons; Item.voxelSizeMicrometres = ExpectedCoreVoxelMicrons;
Item.interiorBottomMicrometres = static_cast<std::int64_t>(Slot.BottomChunkZ) Item.interiorBottomMicrometres = Slot.BottomWorldMicrometres;
* LayoutChunkSizeMicrometres; Item.interiorTopMicrometres = Slot.TopWorldMicrometres;
Item.interiorTopMicrometres = static_cast<std::int64_t>(Slot.TopChunkZ + 1)
* LayoutChunkSizeMicrometres;
Item.managerChunkWidthMicrometres = LayoutChunkSizeMicrometres; Item.managerChunkWidthMicrometres = LayoutChunkSizeMicrometres;
Item.dispatchChunkWidthMicrometres = LayoutChunkSizeMicrometres; Item.dispatchChunkWidthMicrometres = LayoutChunkSizeMicrometres;
Item.transitionMode = transitionName(Slot.Definition->TransitionType); Item.transitionMode = transitionName(Slot.ResolvedTransitionType);
Item.transitionBlendChunks = Slot.Definition->TransitionBlendChunks; Item.transitionBlendChunks = Slot.ResolvedTransitionBlendChunks;
Item.transitionWidthMicrometres = static_cast<std::int64_t>(Item.transitionBlendChunks) Item.transitionWidthMicrometres = Slot.TransitionWidthMicrometres;
* Item.managerChunkWidthMicrometres;
Item.transitionWarpAmplitudeMicrometres = Item.transitionMode == "Interleaved" Item.transitionWarpAmplitudeMicrometres = Item.transitionMode == "Interleaved"
? 2 * Item.managerChunkWidthMicrometres : 0; ? std::min(Item.transitionWidthMicrometres / 2, Item.managerChunkWidthMicrometres) : 0;
Item.transitionInterpenetrationMicrometres = 0; Item.transitionInterpenetrationMicrometres = Item.transitionMode == "Interleaved"
Item.transitionHonouredInCore = Slot.Definition->TransitionType == EVoxelStrateTransition::Hard; ? Item.transitionWidthMicrometres
Item.transitionFallsBackToHard = Slot.Definition->TransitionType != EVoxelStrateTransition::Hard; : Item.transitionMode == "Gradient" ? Item.transitionWidthMicrometres / 4 : 0;
Item.transitionFallsBackToHard = Item.transitionMode != "Hard"
&& Item.transitionWidthMicrometres <= 0;
Item.transitionHonouredInCore = Item.transitionMode == "Hard"
|| !Item.transitionFallsBackToHard;
Item.transitionDispatchPath = Item.transitionFallsBackToHard Item.transitionDispatchPath = Item.transitionFallsBackToHard
? "declared Gradient/Interleaved recorded; Core uses an explicit Hard transition fallback with zero interpenetration" ? "declared zero-width transition uses an explicit Hard fallback"
: "declared Hard transition is applied at this interface"; : Item.transitionMode == "Hard"
? "declared Hard transition preserves the flat shared bedrock/ceiling interface"
: Item.transitionMode == "Interleaved"
? "shared composition overlaps both facing bands with deterministic domain-warped fingers inside the declared width"
: "shared composition moves both band faces together with the smooth declared-width transition field";
Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate"; Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate";
if (Type == ECaveGeneratorType::SurfaceWorld) if (Type == ECaveGeneratorType::SurfaceWorld)
{ {
@@ -1152,11 +1191,8 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Item.queryDispatchPath = "NativeFloatingIslandDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3"; Item.queryDispatchPath = "NativeFloatingIslandDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3";
const auto Plan = std::find_if(Manifest->floatingIslands.begin(), Manifest->floatingIslands.end(), const auto Plan = std::find_if(Manifest->floatingIslands.begin(), Manifest->floatingIslands.end(),
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; }); [&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
std::int64_t SealMicrometres = 0;
if (Plan != Manifest->floatingIslands.end()) if (Plan != Manifest->floatingIslands.end())
{ {
Item.interiorBottomMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMinimumZSubvoxels);
Item.interiorTopMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMaximumZSubvoxels);
Item.recipeVersion = Plan->manifest.recipe.geometryVersion; Item.recipeVersion = Plan->manifest.recipe.geometryVersion;
for (std::size_t IslandIndex = 0; IslandIndex < Plan->manifest.plan.islands.size(); ++IslandIndex) for (std::size_t IslandIndex = 0; IslandIndex < Plan->manifest.plan.islands.size(); ++IslandIndex)
{ {
@@ -1306,6 +1342,8 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
} }
const int32 Seed = StrateManager->GetWorldSeed(); const int32 Seed = StrateManager->GetWorldSeed();
const std::uint32_t LayoutChunkSizeMicrometres =
StrateManager->GetStrateLayoutChunkSizeMicrometres();
const float OriginSpineRadius = Settings->GetEffectiveOriginSpineRadius(); const float OriginSpineRadius = Settings->GetEffectiveOriginSpineRadius();
const float WorldRadiusVoxels = Settings->GetEffectiveWorldRadiusVoxels(); const float WorldRadiusVoxels = Settings->GetEffectiveWorldRadiusVoxels();
const int32 CoreClipRadius = FMath::Clamp(ClipRadius, 1, 64); const int32 CoreClipRadius = FMath::Clamp(ClipRadius, 1, 64);
@@ -1323,7 +1361,7 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
SettingsValue->AsObject()->SetNumberField(TEXT("ClipRadius"), CoreClipRadius); SettingsValue->AsObject()->SetNumberField(TEXT("ClipRadius"), CoreClipRadius);
SettingsValue->AsObject()->SetNumberField(TEXT("MaxClipLevel"), CoreMaxClipLevel); SettingsValue->AsObject()->SetNumberField(TEXT("MaxClipLevel"), CoreMaxClipLevel);
SettingsValue->AsObject()->SetNumberField(TEXT("StrateLayoutChunkSizeMeters"), SettingsValue->AsObject()->SetNumberField(TEXT("StrateLayoutChunkSizeMeters"),
Settings->StrateLayoutChunkSizeMeters); static_cast<double>(StrateManager->GetStrateLayoutChunkSizeMicrometres()) / 1000000.0);
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
Root->SetNumberField(TEXT("schema_version"), 1); Root->SetNumberField(TEXT("schema_version"), 1);
@@ -1334,17 +1372,20 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0))); static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0)));
Root->SetNumberField(TEXT("core_voxel_size_cm"), ResolvedCoreVoxelSizeCm); Root->SetNumberField(TEXT("core_voxel_size_cm"), ResolvedCoreVoxelSizeCm);
Root->SetNumberField(TEXT("core_voxel_size_microns"), CoreVoxelSizeMicrons); Root->SetNumberField(TEXT("core_voxel_size_microns"), CoreVoxelSizeMicrons);
Root->SetNumberField(TEXT("owner_to_core_length_scale"),
OwnerVoxelSizeCm / ResolvedCoreVoxelSizeCm);
Root->SetNumberField(TEXT("seed"), Seed); Root->SetNumberField(TEXT("seed"), Seed);
Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry); Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry);
Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius); Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius);
Root->SetNumberField(TEXT("world_radius_voxels"), WorldRadiusVoxels); Root->SetNumberField(TEXT("world_radius_voxels"), WorldRadiusVoxels);
Root->SetNumberField(TEXT("edge_seal_thickness"), Settings->EdgeSealThickness); Root->SetNumberField(TEXT("edge_seal_thickness"), Settings->EdgeSealThickness);
Root->SetNumberField(TEXT("total_strates"), StrateManager->GetNumStrates()); Root->SetNumberField(TEXT("total_strates"), StrateManager->GetNumStrates());
Root->SetNumberField(TEXT("inter_strate_gap_chunks"), Settings->InterStrateGapChunks); const std::int64_t ResolvedGapMicrometres =
StrateManager->GetResolvedInterStrateGapMicrometres();
Root->SetNumberField(TEXT("inter_strate_gap_chunks"),
LayoutChunkSizeMicrometres > 0
? ResolvedGapMicrometres / LayoutChunkSizeMicrometres : 0);
Root->SetNumberField(TEXT("inter_strate_gap_um"), ResolvedGapMicrometres);
Root->SetNumberField(TEXT("strate_layout_chunk_size_um"), Root->SetNumberField(TEXT("strate_layout_chunk_size_um"),
std::llround(Settings->StrateLayoutChunkSizeMeters*1000000.0)); LayoutChunkSizeMicrometres);
Root->SetNumberField(TEXT("clip_radius"), CoreClipRadius); Root->SetNumberField(TEXT("clip_radius"), CoreClipRadius);
Root->SetNumberField(TEXT("max_clip_level"), CoreMaxClipLevel); Root->SetNumberField(TEXT("max_clip_level"), CoreMaxClipLevel);
Root->SetNumberField(TEXT("full_res_clip_levels"), Settings->FullResClipLevels); Root->SetNumberField(TEXT("full_res_clip_levels"), Settings->FullResClipLevels);
+219 -91
View File
@@ -13,6 +13,7 @@
#include "VoxelTerrainOpDefinition.h" // For UVoxelTerrainOpDefinition::ApplyTo #include "VoxelTerrainOpDefinition.h" // For UVoxelTerrainOpDefinition::ApplyTo
#include "VoxelBiomeDefinition.h" // For UVoxelBiomeDefinition (biome context flatten) #include "VoxelBiomeDefinition.h" // For UVoxelBiomeDefinition (biome context flatten)
#include "HAL/IConsoleManager.h" #include "HAL/IConsoleManager.h"
#include <cmath> // std::llround
#include "Misc/CommandLine.h" #include "Misc/CommandLine.h"
#include "Misc/Crc.h" #include "Misc/Crc.h"
#include "Misc/Parse.h" #include "Misc/Parse.h"
@@ -24,6 +25,27 @@ namespace
{ {
std::atomic<uint64> GNextStrateManagerLifetimeId { 0 }; std::atomic<uint64> GNextStrateManagerLifetimeId { 0 };
int64 VF_OwnerVoxelsToMicrometres(double OwnerVoxels)
{
return static_cast<int64>(std::llround(
OwnerVoxels * static_cast<double>(VOXEL_SIZE) * 10000.0));
}
float VF_ResolvedWorldMicrometresToOwnerVoxels(int64 WorldMicrometres)
{
return static_cast<float>(static_cast<double>(WorldMicrometres)
/ (static_cast<double>(VOXEL_SIZE) * 10000.0));
}
int64 VF_FloorDivide64(int64 Numerator, int64 Denominator)
{
if (Denominator <= 0) return 0;
int64 Quotient = Numerator / Denominator;
const int64 Remainder = Numerator % Denominator;
if (Remainder < 0) --Quotient;
return Quotient;
}
// Exact A/B switch for the repeated passage-floor projection item. The other field- // Exact A/B switch for the repeated passage-floor projection item. The other field-
// preserving rewrites in this round are deliberately unconditional; this switch isolates // preserving rewrites in this round are deliberately unconditional; this switch isolates
// the projection reuse without changing any density arithmetic. // the projection reuse without changing any density arithmetic.
@@ -1300,8 +1322,11 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
} }
} }
// Current Z position (in chunks). Starts at 0 and goes downward (negative). // Resolve the stack in physical integer micrometres first. The historical chunk indices are
int32 CurrentTopZ = 0; // projected from these half-open bounds only after each slot is complete.
int64 CurrentTopWorldMicrometres = StrateLayoutChunkSizeMicrometres;
const int64 ResolvedGapMicrometres = static_cast<int64>(LayoutGapChunks)
* StrateLayoutChunkSizeMicrometres;
for (int32 i = 0; i < TotalStrates; i++) for (int32 i = 0; i < TotalStrates; i++)
{ {
@@ -1356,16 +1381,28 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
const int32 LayoutChunkVoxels=FMath::RoundToInt(StrateLayoutChunkWidthVoxels); const int32 LayoutChunkVoxels=FMath::RoundToInt(StrateLayoutChunkWidthVoxels);
Slot.TopChunkZ = SeasonStrate.TopWorldZ / LayoutChunkVoxels - 1; Slot.TopChunkZ = SeasonStrate.TopWorldZ / LayoutChunkVoxels - 1;
Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / LayoutChunkVoxels; Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / LayoutChunkVoxels;
Slot.TopWorldMicrometres = (static_cast<int64>(Slot.TopChunkZ) + 1)
* StrateLayoutChunkSizeMicrometres;
Slot.BottomWorldMicrometres = static_cast<int64>(Slot.BottomChunkZ)
* StrateLayoutChunkSizeMicrometres;
Slot.HeightMicrometres = Slot.TopWorldMicrometres - Slot.BottomWorldMicrometres;
const int64 ExpectedTopChunk = VF_FloorDivide64(
CurrentTopWorldMicrometres, StrateLayoutChunkSizeMicrometres) - 1;
const int64 ExpectedBottomChunk = ExpectedTopChunk - (Slot.HeightInChunks - 1);
if (SeasonStrate.TopWorldZ % LayoutChunkVoxels != 0 if (SeasonStrate.TopWorldZ % LayoutChunkVoxels != 0
|| SeasonStrate.BottomWorldZ % LayoutChunkVoxels != 0 || SeasonStrate.BottomWorldZ % LayoutChunkVoxels != 0
|| Slot.TopChunkZ != CurrentTopZ || Slot.TopChunkZ != ExpectedTopChunk
|| Slot.BottomChunkZ != CurrentTopZ - (Slot.HeightInChunks - 1)) || Slot.BottomChunkZ != ExpectedBottomChunk
|| Slot.TopWorldMicrometres != CurrentTopWorldMicrometres
|| Slot.HeightMicrometres != static_cast<int64>(Slot.HeightInChunks)
* StrateLayoutChunkSizeMicrometres)
{ {
UE_LOG(LogTemp, Error, UE_LOG(LogTemp, Error,
TEXT("[StrateManager] Season slot %d bounds do not describe the declared stacked layout."), i); TEXT("[StrateManager] Season slot %d bounds do not describe the declared stacked layout."), i);
RestorePreviousLayout(); RestorePreviousLayout();
return false; return false;
} }
CurrentTopWorldMicrometres = Slot.BottomWorldMicrometres - ResolvedGapMicrometres;
SeasonStrates.Add(SeasonStrate); SeasonStrates.Add(SeasonStrate);
} }
else else
@@ -1388,15 +1425,50 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
continue; continue;
} }
// Compute Z range from definition's height // Resolve the authored chunk height into an exact physical interval.
Slot.HeightInChunks = Slot.Definition->StrateHeightInChunks; Slot.HeightInChunks = Slot.Definition->StrateHeightInChunks;
Slot.TopChunkZ = CurrentTopZ; Slot.TopWorldMicrometres = CurrentTopWorldMicrometres;
Slot.BottomChunkZ = CurrentTopZ - (Slot.HeightInChunks - 1); Slot.HeightMicrometres = static_cast<int64>(Slot.HeightInChunks)
* StrateLayoutChunkSizeMicrometres;
Slot.BottomWorldMicrometres = Slot.TopWorldMicrometres - Slot.HeightMicrometres;
Slot.TopChunkZ = static_cast<int32>(VF_FloorDivide64(
Slot.TopWorldMicrometres, StrateLayoutChunkSizeMicrometres) - 1);
Slot.BottomChunkZ = static_cast<int32>(VF_FloorDivide64(
Slot.BottomWorldMicrometres, StrateLayoutChunkSizeMicrometres));
CurrentTopWorldMicrometres = Slot.BottomWorldMicrometres - ResolvedGapMicrometres;
} }
// Move the cursor down for the next strate, leaving a solid-bedrock gap of Slot.ResolvedTransitionType = Slot.Definition->TransitionType;
// InterStrateGapChunks chunks between this strate and the next. Slot.ResolvedTransitionBlendChunks = Slot.Definition->TransitionBlendChunks;
CurrentTopZ = Slot.BottomChunkZ - 1 - LayoutGapChunks; Slot.TransitionWidthMicrometres = static_cast<int64>(
Slot.ResolvedTransitionBlendChunks) * StrateLayoutChunkSizeMicrometres;
Slot.ResolvedPassageConfig = Slot.Definition->PassageConfig;
Slot.PassageMouthRadiusMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.MouthRadius);
Slot.PassageMidRadiusMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.MidRadius);
Slot.PassageReachMinimumMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.ReachMin);
Slot.PassageReachMaximumMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.ReachMax);
Slot.PassageDistanceMinimumMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.DistanceMin);
Slot.PassageDistanceMaximumMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.DistanceMax);
Slot.PassageWanderMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.Wander);
Slot.PassageVerticalWobbleMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.VerticalWobble);
Slot.PassageSpiralRadiusMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.SpiralRadius);
Slot.PassageCascadeLedgeMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.CascadeLedge);
Slot.LegacyTopWorldZVoxels = VF_ResolvedWorldMicrometresToOwnerVoxels(
Slot.TopWorldMicrometres);
Slot.LegacyBottomWorldZVoxels = VF_ResolvedWorldMicrometresToOwnerVoxels(
Slot.BottomWorldMicrometres);
Slot.LegacyHeightVoxels = VF_ResolvedWorldMicrometresToOwnerVoxels(
Slot.HeightMicrometres);
StrateLayout.Add(Slot); StrateLayout.Add(Slot);
@@ -1429,6 +1501,7 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
OriginSpineRadius = bUseSeason OriginSpineRadius = bUseSeason
? SeasonManifest.OriginSpineRadius : Settings->OriginSpineRadius; ? SeasonManifest.OriginSpineRadius : Settings->OriginSpineRadius;
InterStrateGapChunks = LayoutGapChunks; InterStrateGapChunks = LayoutGapChunks;
ResolvedInterStrateGapMicrometres = ResolvedGapMicrometres;
if (bUseSeason) if (bUseSeason)
{ {
ActiveSeasonContentHash = SeasonManifest.ContentHash; ActiveSeasonContentHash = SeasonManifest.ContentHash;
@@ -1537,8 +1610,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
} }
VF_SetComposerRuntimeBounds( VF_SetComposerRuntimeBounds(
Override->Params, Override->Params,
(float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels, TargetSlot->LegacyTopWorldZVoxels,
(float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels); TargetSlot->LegacyBottomWorldZVoxels);
Override->bUseRecipe = bUseRecipe; Override->bUseRecipe = bUseRecipe;
if (Recipe != nullptr) if (Recipe != nullptr)
{ {
@@ -1557,8 +1630,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
Override->bUseRegions = true; Override->bUseRegions = true;
Override->Regions = *InRegions; Override->Regions = *InRegions;
VF_RekeyStrateRegionManifest(Override->Regions, CandidateSeed, StrateIndex); VF_RekeyStrateRegionManifest(Override->Regions, CandidateSeed, StrateIndex);
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; Override->Regions.StrateTopWorldZ = TargetSlot->LegacyTopWorldZVoxels;
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels; Override->Regions.StrateBottomWorldZ = TargetSlot->LegacyBottomWorldZVoxels;
VF_SetStrateArchetypeRuntimeBounds(Override->Params, VF_SetStrateArchetypeRuntimeBounds(Override->Params,
Override->Regions.StrateTopWorldZ, Override->Regions.StrateTopWorldZ,
Override->Regions.StrateBottomWorldZ); Override->Regions.StrateBottomWorldZ);
@@ -1805,14 +1878,36 @@ void UVoxelStrateManager::GeneratePassages()
// that opens the next room rather than a second vertical spine. // that opens the next room rather than a second vertical spine.
const UVoxelStrateDefinition* UpperDef = Upper.Definition; const UVoxelStrateDefinition* UpperDef = Upper.Definition;
if (!UpperDef) continue; if (!UpperDef) continue;
const FStratePassageConfig& Cfg = UpperDef->PassageConfig; // GeneratePassages is the legacy owner-voxel route adapter. Its immutable source is the
// physical passage record resolved during Initialize, converted back to owner voxels here.
FStratePassageConfig Cfg = Upper.ResolvedPassageConfig;
Cfg.MouthRadius = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageMouthRadiusMicrometres);
Cfg.MidRadius = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageMidRadiusMicrometres);
Cfg.ReachMin = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageReachMinimumMicrometres);
Cfg.ReachMax = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageReachMaximumMicrometres);
Cfg.DistanceMin = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageDistanceMinimumMicrometres);
Cfg.DistanceMax = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageDistanceMaximumMicrometres);
Cfg.Wander = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageWanderMicrometres);
Cfg.VerticalWobble = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageVerticalWobbleMicrometres);
Cfg.SpiralRadius = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageSpiralRadiusMicrometres);
Cfg.CascadeLedge = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageCascadeLedgeMicrometres);
// Upper strate floor and lower strate ceiling (differ when there's a bedrock gap). // Upper strate floor and lower strate ceiling (differ when there's a bedrock gap).
const float UpperTopZ = (float)(Upper.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; const float UpperTopZ = Upper.LegacyTopWorldZVoxels;
const float UpperBottomZ = (float)(Upper.BottomChunkZ) * StrateLayoutChunkWidthVoxels; const float UpperBottomZ = Upper.LegacyBottomWorldZVoxels;
const float LowerTopZ = (float)(Lower.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; const float LowerTopZ = Lower.LegacyTopWorldZVoxels;
const float UpperMax = (float)Upper.HeightInChunks * StrateLayoutChunkWidthVoxels * 0.9f; const float UpperMax = Upper.LegacyHeightVoxels * 0.9f;
const float LowerMax = (float)Lower.HeightInChunks * StrateLayoutChunkWidthVoxels * 0.9f; const float LowerMax = Lower.LegacyHeightVoxels * 0.9f;
const float DistLo = FMath::Min(Cfg.DistanceMin, Cfg.DistanceMax); const float DistLo = FMath::Min(Cfg.DistanceMin, Cfg.DistanceMax);
const float DistHi = FMath::Max(Cfg.DistanceMin, Cfg.DistanceMax); const float DistHi = FMath::Max(Cfg.DistanceMin, Cfg.DistanceMax);
@@ -1942,7 +2037,7 @@ void UVoxelStrateManager::GeneratePassages()
LowerDef->FloatingIslandParams, LowerDef->FloatingIslandParams,
LowerQuerySeed, LowerQuerySeed,
LowerTopZ, LowerTopZ,
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels, Lower.LegacyBottomWorldZVoxels,
PX, PX,
PY, PY,
MaxLateralSnap, MaxLateralSnap,
@@ -2002,7 +2097,7 @@ void UVoxelStrateManager::GeneratePassages()
if (bAimedLowerAtPlayerFitPoint) if (bAimedLowerAtPlayerFitPoint)
{ {
const float LowerSealThickness = BoundarySealThicknessFor(*LowerDef); const float LowerSealThickness = BoundarySealThicknessFor(*LowerDef);
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels; const float LowerBottomZ = Lower.LegacyBottomWorldZVoxels;
const float InnerBottomZ = LowerBottomZ + LowerSealThickness; const float InnerBottomZ = LowerBottomZ + LowerSealThickness;
const float InnerTopZ = LowerTopZ - LowerSealThickness; const float InnerTopZ = LowerTopZ - LowerSealThickness;
if (VoxelMath::IsFinite(SuggestedLowerPoint.X) if (VoxelMath::IsFinite(SuggestedLowerPoint.X)
@@ -2170,7 +2265,7 @@ void UVoxelStrateManager::GeneratePassages()
Cfg.MouthRadius, Cfg.MouthRadius,
LowerDoorDirection, LowerDoorDirection,
LowerTopZ, LowerTopZ,
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels, Lower.LegacyBottomWorldZVoxels,
LowerDef ? BoundarySealThicknessFor(*LowerDef) : 0.0f, LowerDef ? BoundarySealThicknessFor(*LowerDef) : 0.0f,
bAimedLowerAtPlayerFitPoint); bAimedLowerAtPlayerFitPoint);
Passage.UpperPoint = Passage.UpperLanding.StandingPoint; Passage.UpperPoint = Passage.UpperLanding.StandingPoint;
@@ -2595,8 +2690,8 @@ void UVoxelStrateManager::GeneratePassages()
if (bOpenSurfaceEntry && OriginSpineRadius > 0.0f && StrateLayout.Num() > 0) if (bOpenSurfaceEntry && OriginSpineRadius > 0.0f && StrateLayout.Num() > 0)
{ {
const FStrateSlot& Top = StrateLayout[0]; const FStrateSlot& Top = StrateLayout[0];
const float TopZ = (float)(Top.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; const float TopZ = Top.LegacyTopWorldZVoxels;
const float BottomZ = (float)Top.BottomChunkZ * StrateLayoutChunkWidthVoxels; const float BottomZ = Top.LegacyBottomWorldZVoxels;
const float TopSeal = Top.Definition const float TopSeal = Top.Definition
? BoundarySealThicknessFor(*Top.Definition) : 0.0f; ? BoundarySealThicknessFor(*Top.Definition) : 0.0f;
const VoxelPassageGeometry::FOriginLandingGeometry OriginLanding = const VoxelPassageGeometry::FOriginLandingGeometry OriginLanding =
@@ -5921,8 +6016,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
for (const FStrateSlot& Slot : StrateLayout) for (const FStrateSlot& Slot : StrateLayout)
{ {
if (Slot.Definition == nullptr) continue; if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels; const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition); const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
const VoxelPassageGeometry::FOriginLandingGeometry Geometry = const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry( VoxelPassageGeometry::BuildOriginLandingGeometry(
@@ -5964,8 +6059,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearBox(
for (const FStrateSlot& Slot : StrateLayout) for (const FStrateSlot& Slot : StrateLayout)
{ {
if (Slot.Definition == nullptr) continue; if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels; const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; const float BottomZ = Slot.LegacyBottomWorldZVoxels;
if (VoxelPassageGeometry::OriginLandingRoomTouchesBox( if (VoxelPassageGeometry::OriginLandingRoomTouchesBox(
VoxelBox, TopZ, BottomZ, VoxelBox, TopZ, BottomZ,
VF_BoundarySealThicknessForDefinition(*Slot.Definition), VF_BoundarySealThicknessForDefinition(*Slot.Definition),
@@ -5985,8 +6080,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearBox(
for (const FStrateSlot& Slot : StrateLayout) for (const FStrateSlot& Slot : StrateLayout)
{ {
if (Slot.Definition == nullptr) continue; if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels; const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; const float BottomZ = Slot.LegacyBottomWorldZVoxels;
if (VoxelPassageGeometry::OriginLandingFloorTouchesBox( if (VoxelPassageGeometry::OriginLandingFloorTouchesBox(
VoxelBox, TopZ, BottomZ, VoxelBox, TopZ, BottomZ,
VF_BoundarySealThicknessForDefinition(*Slot.Definition), VF_BoundarySealThicknessForDefinition(*Slot.Definition),
@@ -6010,8 +6105,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearLattice(
for (const FStrateSlot& Slot : StrateLayout) for (const FStrateSlot& Slot : StrateLayout)
{ {
if (Slot.Definition == nullptr) continue; if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels; const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition); const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
if (VoxelPassageGeometry::OriginLandingRoomTouchesLattice( if (VoxelPassageGeometry::OriginLandingRoomTouchesLattice(
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius, VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius,
@@ -6048,8 +6143,8 @@ bool UVoxelStrateManager::AnyOriginLandingAirNearLattice(
{ {
continue; continue;
} }
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels; const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition); const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
const VoxelPassageGeometry::FOriginLandingGeometry Geometry = const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry( VoxelPassageGeometry::BuildOriginLandingGeometry(
@@ -6125,8 +6220,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearLattice(
for (const FStrateSlot& Slot : StrateLayout) for (const FStrateSlot& Slot : StrateLayout)
{ {
if (Slot.Definition == nullptr) continue; if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels; const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition); const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
if (VoxelPassageGeometry::OriginLandingFloorTouchesLattice( if (VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius)) VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius))
@@ -6143,15 +6238,36 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearLattice(
int32 UVoxelStrateManager::FindSlotIndexForChunkZ(int32 ChunkZ) const int32 UVoxelStrateManager::FindSlotIndexForChunkZ(int32 ChunkZ) const
{ {
// Linear search through strate layout. // Legacy chunk callers use the projected interval cached during Initialize. The source bounds
// With ~10-20 strates this is fine. If we ever have hundreds, // are the slot's physical micrometre interval; these indices do not define or stack strates.
// switch to binary search (layout is sorted by Z). for (int32 i = 0; i < StrateLayout.Num(); ++i)
for (int32 i = 0; i < StrateLayout.Num(); i++)
{ {
const FStrateSlot& Slot = StrateLayout[i]; const FStrateSlot& Slot = StrateLayout[i];
if (ChunkZ <= Slot.TopChunkZ && ChunkZ >= Slot.BottomChunkZ) if (ChunkZ <= Slot.TopChunkZ && ChunkZ >= Slot.BottomChunkZ) return i;
}
return -1;
}
int32 UVoxelStrateManager::FindSlotIndexForWorldZCentimetres(double WorldZCentimetres) const
{
const double WorldZMicrometres = WorldZCentimetres * 10000.0;
int32 Low = 0;
int32 High = StrateLayout.Num() - 1;
while (Low <= High)
{ {
return i; const int32 Mid = Low + (High - Low) / 2;
const FStrateSlot& Slot = StrateLayout[Mid];
if (WorldZMicrometres >= static_cast<double>(Slot.TopWorldMicrometres))
{
High = Mid - 1;
}
else if (WorldZMicrometres < static_cast<double>(Slot.BottomWorldMicrometres))
{
Low = Mid + 1;
}
else
{
return Mid;
} }
} }
return -1; return -1;
@@ -6159,9 +6275,8 @@ int32 UVoxelStrateManager::FindSlotIndexForChunkZ(int32 ChunkZ) const
UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
{ {
// Convert world Z to chunk Z coordinate if (!FMath::IsFinite(WorldZ)) return nullptr;
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels); const int32 SlotIdx = FindSlotIndexForWorldZCentimetres(WorldZ);
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (SlotIdx >= 0) if (SlotIdx >= 0)
{ {
return StrateLayout[SlotIdx].Definition; return StrateLayout[SlotIdx].Definition;
@@ -6171,8 +6286,8 @@ UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
int32 UVoxelStrateManager::GetStrateIndex(float WorldZ) const int32 UVoxelStrateManager::GetStrateIndex(float WorldZ) const
{ {
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels); if (!FMath::IsFinite(WorldZ)) return -1;
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ); const int32 SlotIdx = FindSlotIndexForWorldZCentimetres(WorldZ);
if (SlotIdx >= 0) if (SlotIdx >= 0)
{ {
return StrateLayout[SlotIdx].StrateIndex; return StrateLayout[SlotIdx].StrateIndex;
@@ -6285,9 +6400,13 @@ bool UVoxelStrateManager::IsGapChunk(const FIntVector& ChunkCoord) const
if (StrateLayout.Num() == 0) return false; if (StrateLayout.Num() == 0) return false;
// Above the top strate or below the bottom strate = open air, NOT a gap. // Above the top strate or below the bottom strate = open air, NOT a gap.
const int32 StackTop = StrateLayout[0].TopChunkZ; const int64 ChunkCenterMicrometres = static_cast<int64>(ChunkCoord.Z)
const int32 StackBottom = StrateLayout.Last().BottomChunkZ; * StrateLayoutChunkSizeMicrometres + StrateLayoutChunkSizeMicrometres / 2;
if (ChunkCoord.Z > StackTop || ChunkCoord.Z < StackBottom) return false; if (ChunkCenterMicrometres >= StrateLayout[0].TopWorldMicrometres
|| ChunkCenterMicrometres < StrateLayout.Last().BottomWorldMicrometres)
{
return false;
}
// Inside the stack's Z span but not in any strate slot → it's a bedrock gap. // Inside the stack's Z span but not in any strate slot → it's a bedrock gap.
return FindSlotIndexForChunkZ(ChunkCoord.Z) < 0; return FindSlotIndexForChunkZ(ChunkCoord.Z) < 0;
@@ -6331,8 +6450,8 @@ FSlabGenerationParams UVoxelStrateManager::GetSlabParamsForChunk(const FIntVecto
// Fill in the runtime Z bounds (voxel coordinates, same convention as // Fill in the runtime Z bounds (voxel coordinates, same convention as
// FStrateGenerationParams::StrateTopWorldZ / StrateBottomWorldZ). // FStrateGenerationParams::StrateTopWorldZ / StrateBottomWorldZ).
// TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*StrateLayoutChunkWidthVoxels. // TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*StrateLayoutChunkWidthVoxels.
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result; return Result;
} }
@@ -6365,8 +6484,8 @@ FMazeGenerationParams UVoxelStrateManager::GetMazeParamsForChunk(const FIntVecto
} }
} }
#endif #endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels; Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result; return Result;
} }
@@ -6392,8 +6511,8 @@ FSurfaceGenerationParams UVoxelStrateManager::GetSurfaceParamsForChunk(const FIn
} }
} }
#endif #endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels; Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result; return Result;
} }
@@ -6419,8 +6538,8 @@ FVerticalShaftParams UVoxelStrateManager::GetVerticalShaftParamsForChunk(const F
} }
} }
#endif #endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels; Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result; return Result;
} }
@@ -6446,8 +6565,8 @@ FFloatingIslandParams UVoxelStrateManager::GetFloatingIslandParamsForChunk(const
} }
} }
#endif #endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels; Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result; return Result;
} }
@@ -6481,14 +6600,14 @@ FBiomeContext UVoxelStrateManager::GetBiomeContextForChunk(const FIntVector& Chu
bool UVoxelStrateManager::GetStrateUnrealZRange(float WorldZ, float& OutTopZ, float& OutBottomZ) const bool UVoxelStrateManager::GetStrateUnrealZRange(float WorldZ, float& OutTopZ, float& OutBottomZ) const
{ {
const int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels); if (!FMath::IsFinite(WorldZ)) return false;
const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ); const int32 SlotIdx = FindSlotIndexForWorldZCentimetres(WorldZ);
if (SlotIdx < 0) return false; if (SlotIdx < 0) return false;
const FStrateSlot& Slot = StrateLayout[SlotIdx]; const FStrateSlot& Slot = StrateLayout[SlotIdx];
// Voxel-space Z bounds → Unreal units. Ceiling = top chunk's upper edge. // Return the resolved physical bounds in Unreal centimeters.
OutTopZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE; OutTopZ = static_cast<float>(static_cast<double>(Slot.TopWorldMicrometres) / 10000.0);
OutBottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE; OutBottomZ = static_cast<float>(static_cast<double>(Slot.BottomWorldMicrometres) / 10000.0);
return true; return true;
} }
@@ -6501,8 +6620,8 @@ FStrateDisturbanceParams UVoxelStrateManager::GetDisturbanceParamsForChunk(const
} }
const FStrateSlot& Slot = StrateLayout[SlotIdx]; const FStrateSlot& Slot = StrateLayout[SlotIdx];
FStrateDisturbanceParams Result = Slot.Definition->Disturbances; FStrateDisturbanceParams Result = Slot.Definition->Disturbances;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result; return Result;
} }
@@ -6534,8 +6653,8 @@ float UVoxelStrateManager::GetWaterLevelWorldZForChunk(const FIntVector& ChunkCo
} }
if (Rel <= 0.0f) return -FLT_MAX; if (Rel <= 0.0f) return -FLT_MAX;
const float BottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float TopZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; const float TopZ = Slot.LegacyTopWorldZVoxels;
return FMath::Lerp(BottomZ, TopZ, FMath::Clamp(Rel, 0.0f, 1.0f)); return FMath::Lerp(BottomZ, TopZ, FMath::Clamp(Rel, 0.0f, 1.0f));
} }
@@ -6579,8 +6698,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
FindComposerOverride(Slot.StrateIndex)) FindComposerOverride(Slot.StrateIndex))
{ {
FStrateGenerationParams Result = Override->Params.TunnelNetworkParams; FStrateGenerationParams Result = Override->Params.TunnelNetworkParams;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels; Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
// A candidate is a hard replacement of this slot. Do not blend its rolled vector with an // A candidate is a hard replacement of this slot. Do not blend its rolled vector with an
// authored neighbour at a boundary; this is also how the offline fixture measures it. // authored neighbour at a boundary; this is also how the offline fixture measures it.
return ResolveFinalParams(Result); return ResolveFinalParams(Result);
@@ -6590,8 +6709,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (SeasonStrates.IsValidIndex(SlotIdx)) if (SeasonStrates.IsValidIndex(SlotIdx))
{ {
FStrateGenerationParams Result = SeasonStrates[SlotIdx].Params.TunnelNetworkParams; FStrateGenerationParams Result = SeasonStrates[SlotIdx].Params.TunnelNetworkParams;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels; Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
// Season vectors are already final, measured slot records. Vertical structural posts own // Season vectors are already final, measured slot records. Vertical structural posts own
// their boundary; blending them with another selected recipe would describe neither one. // their boundary; blending them with another selected recipe would describe neither one.
return ResolveFinalParams(Result); return ResolveFinalParams(Result);
@@ -6608,8 +6727,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
// //
// TopChunkZ=0, CHUNK_SIZE=32: top of the strate = chunk 0's top edge = voxel Z=32 // TopChunkZ=0, CHUNK_SIZE=32: top of the strate = chunk 0's top edge = voxel Z=32
// BottomChunkZ=-3: bottom of the strate = chunk -3's bottom edge = voxel Z=-3*32 = -96 // BottomChunkZ=-3: bottom of the strate = chunk -3's bottom edge = voxel Z=-3*32 = -96
BaseParams.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; BaseParams.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
BaseParams.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; BaseParams.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
// Core currently dispatches one generator field per resolved strate. Gradient and // Core currently dispatches one generator field per resolved strate. Gradient and
// interleaved metadata therefore use the explicitly declared Hard fallback for every // interleaved metadata therefore use the explicitly declared Hard fallback for every
@@ -6656,18 +6775,23 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// CHECK BOTTOM BOUNDARY (transitioning to strate below) // CHECK BOTTOM BOUNDARY (transitioning to strate below)
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// DistFromBottom = how many chunks above the bottom edge of this strate. // Legacy chunk callers project their sample to a layout interval; physical distance and
// transition width are both resolved in micrometres before projecting the blend fraction.
// When 0, we're right at the boundary. When == TransitionBlendChunks, we're at // When 0, we're right at the boundary. When == TransitionBlendChunks, we're at
// the outer edge of the transition zone. // the outer edge of the transition zone.
int32 DistFromBottom = ChunkCoord.Z - Slot.BottomChunkZ; const int64 DistFromBottomMicrometres = static_cast<int64>(ChunkCoord.Z)
* StrateLayoutChunkSizeMicrometres - Slot.BottomWorldMicrometres;
const int32 DistFromBottom = static_cast<int32>(VF_FloorDivide64(
DistFromBottomMicrometres, StrateLayoutChunkSizeMicrometres));
if (SlotIdx + 1 < StrateLayout.Num()) if (SlotIdx + 1 < StrateLayout.Num())
{ {
// The upper strate (this one) controls the transition type at its lower edge // The upper strate (this one) controls the transition type at its lower edge
const EVoxelStrateTransition TransType = Slot.Definition->TransitionType; const EVoxelStrateTransition TransType = Slot.ResolvedTransitionType;
// Per-definition blend distance. // Per-definition blend distance.
const int32 EffectiveBlend = Slot.Definition->TransitionBlendChunks; const int32 EffectiveBlend = static_cast<int32>(VF_FloorDivide64(
Slot.TransitionWidthMicrometres, StrateLayoutChunkSizeMicrometres));
// Prepare the neighbor's params (only used for Gradient and Interleaved) // Prepare the neighbor's params (only used for Gradient and Interleaved)
const FStrateSlot& BelowSlot = StrateLayout[SlotIdx + 1]; const FStrateSlot& BelowSlot = StrateLayout[SlotIdx + 1];
@@ -6691,8 +6815,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (EffectiveBlend > 0 && DistFromBottom < EffectiveBlend) if (EffectiveBlend > 0 && DistFromBottom < EffectiveBlend)
{ {
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition); FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; BelowParams.StrateTopWorldZ = BelowSlot.LegacyTopWorldZVoxels;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; BelowParams.StrateBottomWorldZ = BelowSlot.LegacyBottomWorldZVoxels;
// Linear alpha: 0 at EffectiveBlend chunks away, 1 at the boundary // Linear alpha: 0 at EffectiveBlend chunks away, 1 at the boundary
float Alpha = 1.0f - ((float)DistFromBottom / (float)EffectiveBlend); float Alpha = 1.0f - ((float)DistFromBottom / (float)EffectiveBlend);
@@ -6729,8 +6853,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (DistFromBottom < CheckRange) if (DistFromBottom < CheckRange)
{ {
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition); FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; BelowParams.StrateTopWorldZ = BelowSlot.LegacyTopWorldZVoxels;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; BelowParams.StrateBottomWorldZ = BelowSlot.LegacyBottomWorldZVoxels;
// Sample 3D Perlin noise to warp the boundary position. // Sample 3D Perlin noise to warp the boundary position.
// XY frequency 0.15 gives medium-scale variation (~6-7 chunks per cycle). // XY frequency 0.15 gives medium-scale variation (~6-7 chunks per cycle).
@@ -6768,14 +6892,18 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// Mirror logic: the ABOVE strate's TransitionType controls its lower edge, // Mirror logic: the ABOVE strate's TransitionType controls its lower edge,
// which is this strate's upper edge. So we read from StrateLayout[SlotIdx-1]. // which is this strate's upper edge. So we read from StrateLayout[SlotIdx-1].
int32 DistFromTop = Slot.TopChunkZ - ChunkCoord.Z; const int64 DistFromTopMicrometres = Slot.TopWorldMicrometres
- (static_cast<int64>(ChunkCoord.Z) + 1) * StrateLayoutChunkSizeMicrometres;
const int32 DistFromTop = static_cast<int32>(VF_FloorDivide64(
DistFromTopMicrometres, StrateLayoutChunkSizeMicrometres));
if (SlotIdx > 0) if (SlotIdx > 0)
{ {
// The strate ABOVE controls the transition at its lower edge (= our upper edge) // The strate ABOVE controls the transition at its lower edge (= our upper edge)
const FStrateSlot& AboveSlot = StrateLayout[SlotIdx - 1]; const FStrateSlot& AboveSlot = StrateLayout[SlotIdx - 1];
const EVoxelStrateTransition TransType = AboveSlot.Definition->TransitionType; const EVoxelStrateTransition TransType = AboveSlot.ResolvedTransitionType;
const int32 EffectiveBlend = AboveSlot.Definition->TransitionBlendChunks; const int32 EffectiveBlend = static_cast<int32>(VF_FloorDivide64(
AboveSlot.TransitionWidthMicrometres, StrateLayoutChunkSizeMicrometres));
switch (TransType) switch (TransType)
{ {
@@ -6790,8 +6918,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (EffectiveBlend > 0 && DistFromTop < EffectiveBlend) if (EffectiveBlend > 0 && DistFromTop < EffectiveBlend)
{ {
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition); FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; AboveParams.StrateTopWorldZ = AboveSlot.LegacyTopWorldZVoxels;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; AboveParams.StrateBottomWorldZ = AboveSlot.LegacyBottomWorldZVoxels;
float Alpha = 1.0f - ((float)DistFromTop / (float)EffectiveBlend); float Alpha = 1.0f - ((float)DistFromTop / (float)EffectiveBlend);
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f); Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
@@ -6812,8 +6940,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (DistFromTop < CheckRange) if (DistFromTop < CheckRange)
{ {
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition); FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels; AboveParams.StrateTopWorldZ = AboveSlot.LegacyTopWorldZVoxels;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels; AboveParams.StrateBottomWorldZ = AboveSlot.LegacyBottomWorldZVoxels;
// Same noise function but with a different seed offset to avoid // Same noise function but with a different seed offset to avoid
// symmetry between top and bottom boundaries of adjacent strates // symmetry between top and bottom boundaries of adjacent strates
+6
View File
@@ -7771,6 +7771,12 @@ void AVoxelWorld::FallBackToLegacyAfterCoreInitializationFailure(const FString&
VoxelForgeCoreWorldAssemblyJson.Reset(); VoxelForgeCoreWorldAssemblyJson.Reset();
VoxelForgeCoreWorldAssemblyHash.Reset(); VoxelForgeCoreWorldAssemblyHash.Reset();
bUseVoxelForgeCore = false; bUseVoxelForgeCore = false;
// The Core adapter sets this before validation; legacy generation must not inherit it.
// L'adaptateur Core l'active avant validation ; le générateur historique ne doit pas en hériter.
if (StrateManager != nullptr)
{
StrateManager->SetHardTransitionFallbackForAssembly(false);
}
CoreFallbackCenterLocalCm = FVector::ZeroVector; CoreFallbackCenterLocalCm = FVector::ZeroVector;
if (Mesher != nullptr) if (Mesher != nullptr)
{ {
+39 -6
View File
@@ -121,8 +121,8 @@ struct FVoxelPassage
* FStrateSlot — Runtime info for one strate in the layout. * FStrateSlot — Runtime info for one strate in the layout.
* *
* Created during Initialize() and stored in the StrateLayout array. * Created during Initialize() and stored in the StrateLayout array.
* Each slot knows its definition, its Z-range in chunk coordinates, * Each slot owns its resolved half-open physical Z interval. Chunk coordinates and legacy
* and its index in the sequence. * authoring values below are projections kept for existing callers and assets.
*/ */
USTRUCT() USTRUCT()
struct FStrateSlot struct FStrateSlot
@@ -136,6 +136,33 @@ struct FStrateSlot
// Strate index (0 = topmost, increases downward) // Strate index (0 = topmost, increases downward)
int32 StrateIndex = 0; int32 StrateIndex = 0;
// Resolved physical bounds, in micrometres, with interval [bottom, top). These are computed
// once during Initialize from the configured metre chunk width and legacy asset dimensions.
int64 TopWorldMicrometres = 0;
int64 BottomWorldMicrometres = 0;
int64 HeightMicrometres = 0;
float LegacyTopWorldZVoxels = 0.0f;
float LegacyBottomWorldZVoxels = 0.0f;
float LegacyHeightVoxels = 0.0f;
// Immutable transition and passage inputs copied when this slot is resolved. The width is
// physical; chunk counts and raw owner-voxel values remain compatibility projections for
// legacy parameter and route adapters.
EVoxelStrateTransition ResolvedTransitionType = EVoxelStrateTransition::Gradient;
int32 ResolvedTransitionBlendChunks = 2;
int64 TransitionWidthMicrometres = 0;
FStratePassageConfig ResolvedPassageConfig;
int64 PassageMouthRadiusMicrometres = 0;
int64 PassageMidRadiusMicrometres = 0;
int64 PassageReachMinimumMicrometres = 0;
int64 PassageReachMaximumMicrometres = 0;
int64 PassageDistanceMinimumMicrometres = 0;
int64 PassageDistanceMaximumMicrometres = 0;
int64 PassageWanderMicrometres = 0;
int64 PassageVerticalWobbleMicrometres = 0;
int64 PassageSpiralRadiusMicrometres = 0;
int64 PassageCascadeLedgeMicrometres = 0;
// Z chunk range (inclusive). TopZ > BottomZ since Z decreases downward. // Z chunk range (inclusive). TopZ > BottomZ since Z decreases downward.
// Example: TopZ = 0, BottomZ = -3 means this strate spans chunks Z=0 to Z=-3 // Example: TopZ = 0, BottomZ = -3 means this strate spans chunks Z=0 to Z=-3
int32 TopChunkZ = 0; int32 TopChunkZ = 0;
@@ -248,7 +275,11 @@ public:
// This is independent of Core's 32^3 tile width. // This is independent of Core's 32^3 tile width.
float GetStrateLayoutChunkWidthVoxels() const { return StrateLayoutChunkWidthVoxels; } float GetStrateLayoutChunkWidthVoxels() const { return StrateLayoutChunkWidthVoxels; }
uint32 GetStrateLayoutChunkSizeMicrometres() const { return StrateLayoutChunkSizeMicrometres; } uint32 GetStrateLayoutChunkSizeMicrometres() const { return StrateLayoutChunkSizeMicrometres; }
void SetHardTransitionFallbackForAssembly(bool bEnabled) int64 GetResolvedInterStrateGapMicrometres() const
{ return ResolvedInterStrateGapMicrometres; }
// Called by the Core adapter through a const manager pointer; the flag is runtime routing
// state, not layout data, hence const + mutable.
void SetHardTransitionFallbackForAssembly(bool bEnabled) const
{ bHardTransitionFallbackForAssembly = bEnabled; } { bHardTransitionFallbackForAssembly = bEnabled; }
/** Layout/passage generation counter (bumped by every Initialize → GeneratePassages). /** Layout/passage generation counter (bumped by every Initialize → GeneratePassages).
@@ -528,14 +559,15 @@ protected:
// Copied from VoxelSettings::OriginSpineRadius. // Copied from VoxelSettings::OriginSpineRadius.
float OriginSpineRadius = 28.0f; float OriginSpineRadius = 28.0f;
// Solid-bedrock gap between consecutive strates, in chunks. Copied from // Compatibility gap projection, in layout chunks. Physical authority is the micrometre value
// VoxelSettings::InterStrateGapChunks during Initialize(). // resolved during Initialize below.
int32 InterStrateGapChunks = 0; int32 InterStrateGapChunks = 0;
int64 ResolvedInterStrateGapMicrometres = 0;
// Resolved once from VoxelSettings::StrateLayoutChunkSizeMeters during Initialize(). // Resolved once from VoxelSettings::StrateLayoutChunkSizeMeters during Initialize().
float StrateLayoutChunkWidthVoxels = 32.0f; float StrateLayoutChunkWidthVoxels = 32.0f;
uint32 StrateLayoutChunkSizeMicrometres = 8000000; uint32 StrateLayoutChunkSizeMicrometres = 8000000;
bool bHardTransitionFallbackForAssembly = false; mutable bool bHardTransitionFallbackForAssembly = false;
// Production keeps the legacy writer available only as a fail-closed fallback for a // Production keeps the legacy writer available only as a fail-closed fallback for a
// non-native/malformed passage; native D-floors bypass it. This switch never changes the // non-native/malformed passage; native D-floors bypass it. This switch never changes the
@@ -552,6 +584,7 @@ protected:
// Find which slot a chunk Z coordinate falls into. // Find which slot a chunk Z coordinate falls into.
// Returns INDEX into StrateLayout, or -1 if not found. // Returns INDEX into StrateLayout, or -1 if not found.
int32 FindSlotIndexForChunkZ(int32 ChunkZ) const; int32 FindSlotIndexForChunkZ(int32 ChunkZ) const;
int32 FindSlotIndexForWorldZCentimetres(double WorldZCentimetres) const;
#if WITH_EDITOR #if WITH_EDITOR
const FVoxelStrateComposerSlotOverride* FindComposerOverride(int32 StrateIndex) const; const FVoxelStrateComposerSlotOverride* FindComposerOverride(int32 StrateIndex) const;
+13 -14
View File
@@ -201,15 +201,19 @@ enum class ECaveGeneratorType : uint8
}; };
/** /**
* EVoxelStrateTransition — How two adjacent strates blend at their shared boundary. * EVoxelStrateTransition — How two adjacent strates meet at their shared boundary.
* *
* Each strate boundary can use a different transition style. The transition type * Each strate boundary can use a different transition style. The transition type
* is defined on the UPPER strate's definition and controls the boundary between * is defined on the UPPER strate's definition and controls the boundary between
* itself and the strate below it. * itself and the strate below it.
* *
* Gradient: Smooth linear interpolation of generation params across TransitionBlendChunks. * In the Core composition path, TransitionBlendChunks defines the full permitted
* This is the classic approach — no seam visible, but also no dramatic * crossing depth between the upper bedrock and lower ceiling bands. Each band keeps
* geological feature at the boundary. Best for similar-looking strates. * its authored thickness and the solid bands always overlap or touch. The legacy manager
* may also blend generation parameters for Gradient; Core does not blend unrelated fields.
*
* Gradient: A low-amplitude smooth height field moves the shared interface and both
* content-facing band surfaces. Best for gentle geological variation.
* *
* Hard: No blending at all. The strate's params apply right up to the boundary, * Hard: No blending at all. The strate's params apply right up to the boundary,
* then instantly switch to the neighbor's params. The abrupt change in * then instantly switch to the neighbor's params. The abrupt change in
@@ -217,26 +221,21 @@ enum class ECaveGeneratorType : uint8
* discontinuity at the boundary. Best for strates that should feel like * discontinuity at the boundary. Best for strates that should feel like
* distinct geological layers with a visible dividing line. * distinct geological layers with a visible dividing line.
* *
* Interleaved: The boundary position is warped by a 3D noise field, so "fingers" * Interleaved: A deterministic 3D domain-warped hash field sets the overlap depth per XY
* of one strate reach into the other. Some XY columns transition early, * column from zero to the declared width, creating irregular fingers.
* others late, creating an irregular, interlocking boundary. The warp * Best for strates that should feel grown together.
* amplitude is ~2 chunks, and Z frequency is lower than XY to produce
* horizontal finger-like intrusions. Best for strates that should feel
* like they grew into each other over geological time.
*/ */
UENUM(BlueprintType) UENUM(BlueprintType)
enum class EVoxelStrateTransition : uint8 enum class EVoxelStrateTransition : uint8
{ {
// Smooth linear interpolation of all generation params across the blend zone. // Smoothly undulates the shared bedrock/ceiling interface in the Core composition path.
// No visible boundary — params change gradually over TransitionBlendChunks distance.
Gradient UMETA(DisplayName = "Gradient (smooth blend)"), Gradient UMETA(DisplayName = "Gradient (smooth blend)"),
// No blending — params switch instantly at the boundary. // No blending — params switch instantly at the boundary.
// Creates natural cliffs, ledges, or material discontinuities. // Creates natural cliffs, ledges, or material discontinuities.
Hard UMETA(DisplayName = "Hard (sharp boundary with cliff)"), Hard UMETA(DisplayName = "Hard (sharp boundary with cliff)"),
// 3D noise warps the boundary position per XY column. // A 3D domain-warped hash field creates finger-like intrusions within the declared width.
// Creates finger-like intrusions of one strate into the other.
Interleaved UMETA(DisplayName = "Interleaved (domain-warped fingers)") Interleaved UMETA(DisplayName = "Interleaved (domain-warped fingers)")
}; };
+19
View File
@@ -7,6 +7,8 @@ fi
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$SCRIPT_DIR" ROOT="$SCRIPT_DIR"
PROJECT_ROOT="$(cd -- "$ROOT/.." && pwd)"
VERIFY_DIR="$ROOT/Tools/verify"
COMPILER="clang++-19" COMPILER="clang++-19"
VARIANT="clang19" VARIANT="clang19"
FP_CONTRACT="off" FP_CONTRACT="off"
@@ -41,6 +43,15 @@ BIN_DIR="${VOXELFORGE_BIN_DIR:-$ROOT/bin/linux}"
BUILD_DIR="$BUILD_ROOT/linux-$VARIANT" BUILD_DIR="$BUILD_ROOT/linux-$VARIANT"
mkdir -p "$BUILD_DIR/wrappers" "$BUILD_DIR/objects" "$BIN_DIR" mkdir -p "$BUILD_DIR/wrappers" "$BUILD_DIR/objects" "$BIN_DIR"
STAMP_HELPER="$VERIFY_DIR/source_stamp.py"
STAMP_ARGS=()
if [[ "${VOXELFORGE_INCLUDE_UNREAL_ADAPTER:-0}" == "1" ]]; then
STAMP_ARGS+=(--include-unreal-adapter)
fi
SOURCE_STAMP="$(python3 -B "$STAMP_HELPER" stamp --root "$PROJECT_ROOT" "${STAMP_ARGS[@]}")"
python3 -B "$STAMP_HELPER" write-manifest --root "$PROJECT_ROOT" --output "$BUILD_DIR/source-manifest.txt" "${STAMP_ARGS[@]}" >/dev/null
printf 'Source stamp: %s\n' "$SOURCE_STAMP"
INCLUDES=( INCLUDES=(
"-I$ROOT/Core/include" "-I$ROOT/Core/include"
"-I$ROOT/Core/src" "-I$ROOT/Core/src"
@@ -52,6 +63,7 @@ INCLUDES=(
FLAGS=( FLAGS=(
-std=c++20 -O2 -mavx2 -mfma -fno-fast-math -std=c++20 -O2 -mavx2 -mfma -fno-fast-math
-fexceptions -pthread "${INCLUDES[@]}" -fexceptions -pthread "${INCLUDES[@]}"
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\""
) )
if [[ "$FP_CONTRACT" != "default" ]]; then if [[ "$FP_CONTRACT" != "default" ]]; then
FLAGS+=("-ffp-contract=$FP_CONTRACT") FLAGS+=("-ffp-contract=$FP_CONTRACT")
@@ -122,8 +134,15 @@ OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT"
"$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT" "$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT"
CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT" CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT"
"$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \ "$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" -I"$ROOT/Source" \
"$ROOT/Source/LinuxOffscreenCapture.cpp" -o "$CAPTURE_OUTPUT" \ "$ROOT/Source/LinuxOffscreenCapture.cpp" -o "$CAPTURE_OUTPUT" \
$(pkg-config --cflags --libs egl glesv2 libpng) $(pkg-config --cflags --libs egl glesv2 libpng)
POST_BUILD_STAMP="$(python3 -B "$STAMP_HELPER" stamp --root "$PROJECT_ROOT" "${STAMP_ARGS[@]}")"
if [[ "$POST_BUILD_STAMP" != "$SOURCE_STAMP" ]]; then
printf 'Source changed during build: before=%s after=%s\n' "$SOURCE_STAMP" "$POST_BUILD_STAMP" >&2
exit 1
fi
printf 'Built Core library: %s\nBuilt headless benchmark: %s\n' "$CORE_LIBRARY" "$OUTPUT" printf 'Built Core library: %s\nBuilt headless benchmark: %s\n' "$CORE_LIBRARY" "$OUTPUT"
printf 'Built EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT" printf 'Built EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT"
printf 'Compiler: %s | variant: %s | fp-contract: %s | jobs: %s\n' "$COMPILER" "$VARIANT" "$FP_CONTRACT" "$JOBS" printf 'Compiler: %s | variant: %s | fp-contract: %s | jobs: %s\n' "$COMPILER" "$VARIANT" "$FP_CONTRACT" "$JOBS"
@@ -0,0 +1,55 @@
#pragma once
#include <cstdint>
#include <algorithm>
#include <limits>
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
#include <intrin.h>
#elif defined(__i386__) || defined(__x86_64__)
#include <immintrin.h>
#endif
namespace VoxelForgeCoreShared
{
inline std::uint64_t ReadCpuCycles() noexcept
{
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
_mm_lfence();
const std::uint64_t value=__rdtsc();
_mm_lfence();
return value;
#elif defined(__i386__) || defined(__x86_64__)
_mm_lfence();
const std::uint64_t value=__rdtsc();
_mm_lfence();
return value;
#else
return 0;
#endif
}
inline std::uint64_t CpuCycleReadPairOverhead() noexcept
{
static const std::uint64_t overhead=[]()
{
std::uint64_t minimum=std::numeric_limits<std::uint64_t>::max();
for(unsigned i=0;i<64;++i)
{
const std::uint64_t start=ReadCpuCycles();
const std::uint64_t end=ReadCpuCycles();
minimum=std::min(minimum,end-start);
}
return minimum==std::numeric_limits<std::uint64_t>::max()?0:minimum;
}();
return overhead;
}
inline std::uint64_t AdjustedCpuCycleDelta(std::uint64_t start,
std::uint64_t end) noexcept
{
const std::uint64_t delta=end-start;
const std::uint64_t overhead=CpuCycleReadPairOverhead();
return delta>overhead?delta-overhead:0;
}
}
@@ -0,0 +1,180 @@
#pragma once
#include "VoxelForgeCore/CpuCycleCounter.h"
#include <array>
#include <cstddef>
#include <cstdint>
namespace VoxelForgeCore::floating_island_detail
{
enum class EIslandFieldCaller : std::uint8_t
{
GridFill=0,Skirt=1,Boundary=2,PassageFilter=3,Other=4,Count=5
};
enum class EIslandAttributionBucket : std::uint8_t
{
Field=0,Island=1,Root=2,Connector=3,Helix=4,Column=5,
LocalProbe=6,SharedCache=7,MouthFilter=8,Boundary=9,CallerGrid=10,
CallerSkirt=11,CallerBoundary=12,CallerPassageFilter=13,CallerOther=14,
Count=15
};
struct FIslandFieldAttributionInstrumentation
{
static constexpr std::uint64_t SampleMask=255;
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> sampledFieldCalls{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> fieldCycles{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> sampledNativeCalls{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> nativeCycles{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandAttributionBucket::Count)> exclusiveCycles{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandAttributionBucket::Count)> scopeSamples{};
std::uint64_t traceSampledMisses=0,traceSeenBefore=0,traceEvictions=0,
traceReusedEvictions=0;
std::array<std::uint64_t,8> traceSet{};
std::array<std::int64_t,8> traceX{},traceY{},traceEvictedX{},traceEvictedY{};
std::array<std::uint64_t,8> traceIsland{},traceEvictedIsland{};
std::array<std::uint8_t,8> traceFlags{};
std::size_t traceCount=0;
};
inline FIslandFieldAttributionInstrumentation*& ActiveIslandFieldAttribution() noexcept
{
static thread_local FIslandFieldAttributionInstrumentation* active=nullptr;
return active;
}
inline FIslandFieldAttributionInstrumentation* SetActiveIslandFieldAttribution(
FIslandFieldAttributionInstrumentation* value) noexcept
{
auto* previous=ActiveIslandFieldAttribution();
ActiveIslandFieldAttribution()=value;
return previous;
}
inline EIslandFieldCaller& ActiveIslandFieldCaller() noexcept
{
static thread_local EIslandFieldCaller caller=EIslandFieldCaller::Other;
return caller;
}
struct FScopedIslandFieldCaller
{
explicit FScopedIslandFieldCaller(EIslandFieldCaller value) noexcept
:previous(ActiveIslandFieldCaller()),enabled(ActiveIslandFieldAttribution()!=nullptr)
{if(enabled)ActiveIslandFieldCaller()=value;}
~FScopedIslandFieldCaller(){if(enabled)ActiveIslandFieldCaller()=previous;}
EIslandFieldCaller previous;
bool enabled=false;
};
inline std::array<std::uint64_t,
static_cast<std::size_t>(EIslandAttributionBucket::Count)>&
IslandComponentSampleSequence() noexcept
{
static thread_local std::array<std::uint64_t,
static_cast<std::size_t>(EIslandAttributionBucket::Count)> sequence{};
return sequence;
}
inline std::array<std::uint64_t,
static_cast<std::size_t>(EIslandFieldCaller::Count)>&
IslandFieldSampleSequence() noexcept
{
static thread_local std::array<std::uint64_t,
static_cast<std::size_t>(EIslandFieldCaller::Count)> sequence{};
return sequence;
}
inline std::array<std::uint64_t,
static_cast<std::size_t>(EIslandFieldCaller::Count)>&
IslandNativeSampleSequence() noexcept
{
static thread_local std::array<std::uint64_t,
static_cast<std::size_t>(EIslandFieldCaller::Count)> sequence{};
return sequence;
}
struct FScopedIslandAttributionTimer
{
explicit FScopedIslandAttributionTimer(EIslandAttributionBucket bucket) noexcept
:metrics(ActiveIslandFieldAttribution()),bucketIndex(static_cast<std::size_t>(bucket))
{
if(!metrics)return;
if(bucketIndex==static_cast<std::size_t>(EIslandAttributionBucket::Field))
{
const auto callerIndex=static_cast<std::size_t>(ActiveIslandFieldCaller());
const auto calls=++IslandFieldSampleSequence()[callerIndex];
const std::uint64_t phase=(callerIndex*43u+17u)
&FIslandFieldAttributionInstrumentation::SampleMask;
sampled=(((calls+phase)&FIslandFieldAttributionInstrumentation::SampleMask)==0);
}
else
{
auto& calls=IslandComponentSampleSequence()[bucketIndex];
++calls;
const std::uint64_t phase=(static_cast<std::uint64_t>(bucketIndex)*29u+17u)
&FIslandFieldAttributionInstrumentation::SampleMask;
sampled=(((calls+phase)&FIslandFieldAttributionInstrumentation::SampleMask)==0);
}
if(sampled)start=VoxelForgeCoreShared::ReadCpuCycles();
}
~FScopedIslandAttributionTimer(){Stop();}
std::uint64_t Stop() noexcept
{
if(!sampled)return 0;
sampled=false;
const std::uint64_t raw=VoxelForgeCoreShared::ReadCpuCycles()-start;
const std::uint64_t overhead=VoxelForgeCoreShared::CpuCycleReadPairOverhead();
const std::uint64_t cycles=raw>overhead?raw-overhead:0;
metrics->exclusiveCycles[bucketIndex]+=cycles;
++metrics->scopeSamples[bucketIndex];
if(bucketIndex==static_cast<std::size_t>(EIslandAttributionBucket::Field))
{
const std::size_t callerIndex=static_cast<std::size_t>(ActiveIslandFieldCaller());
metrics->fieldCycles[callerIndex]+=cycles;
++metrics->sampledFieldCalls[callerIndex];
}
return cycles;
}
FIslandFieldAttributionInstrumentation* metrics=nullptr;
std::size_t bucketIndex=0;
std::uint64_t start=0;
bool sampled=false;
};
struct FScopedIslandAttributionSample
{
explicit FScopedIslandAttributionSample() noexcept
{
auto* attribution=ActiveIslandFieldAttribution();
if(!attribution)return;
const auto caller=ActiveIslandFieldCaller();
const std::size_t callerIndex=static_cast<std::size_t>(caller);
const std::uint64_t sequence=++IslandNativeSampleSequence()[callerIndex];
const std::uint64_t phase=(callerIndex*43u+93u)
&FIslandFieldAttributionInstrumentation::SampleMask;
if(((sequence+phase)&FIslandFieldAttributionInstrumentation::SampleMask)!=0)return;
sampledAttribution=attribution;
sampledCaller=caller;
sampled=true;
start=VoxelForgeCoreShared::ReadCpuCycles();
++attribution->sampledNativeCalls[callerIndex];
}
~FScopedIslandAttributionSample()
{
if(!sampled)return;
const std::uint64_t raw=VoxelForgeCoreShared::ReadCpuCycles()-start;
const std::uint64_t overhead=VoxelForgeCoreShared::CpuCycleReadPairOverhead();
const std::uint64_t duration=raw>overhead?raw-overhead:0;
const std::size_t callerIndex=static_cast<std::size_t>(sampledCaller);
sampledAttribution->nativeCycles[callerIndex]+=duration;
}
FIslandFieldAttributionInstrumentation* sampledAttribution=nullptr;
EIslandFieldCaller sampledCaller=EIslandFieldCaller::Other;
std::uint64_t start=0;
bool sampled=false;
};
}
@@ -1,9 +1,12 @@
#pragma once #pragma once
#include "VoxelForgeCore/CpuCycleCounter.h"
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <chrono>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <thread> #include <thread>
@@ -12,6 +15,23 @@
namespace VoxelForgeCoreShared namespace VoxelForgeCoreShared
{ {
struct FColumnCacheMetrics
{
std::uint64_t hits = 0;
std::uint64_t misses = 0;
std::uint64_t computes = 0;
std::uint64_t conflictReplacements = 0;
std::uint64_t lockWaitNanoseconds = 0;
std::uint64_t sampledInitialReadCycles = 0;
std::uint64_t sampledInitialReads = 0;
std::uint64_t sampledLockedReadCycles = 0;
std::uint64_t sampledLockedReads = 0;
std::uint64_t sampledLockCycles = 0;
std::uint64_t sampledLocks = 0;
std::uint64_t sampledBuildCycles = 0;
std::uint64_t sampledBuilds = 0;
};
// Exact input identity for a pure XY column value. LOD is deliberately not part of this key: // Exact input identity for a pure XY column value. LOD is deliberately not part of this key:
// the same recipe/plan column has the same bits at every sampling step. // the same recipe/plan column has the same bits at every sampling step.
struct FColumnCacheKey struct FColumnCacheKey
@@ -69,13 +89,28 @@ namespace VoxelForgeCoreShared
struct FSpinGuard struct FSpinGuard
{ {
explicit FSpinGuard(std::atomic_flag& value) : Flag(value) explicit FSpinGuard(std::atomic_flag& value, FColumnCacheMetrics* metrics,
bool sampleCycles)
: Flag(value), Metrics(metrics)
{ {
const auto started = Metrics ? std::chrono::steady_clock::now()
: std::chrono::steady_clock::time_point{};
const std::uint64_t cycleStart=sampleCycles?ReadCpuCycles():0;
while (Flag.test_and_set(std::memory_order_acquire)) while (Flag.test_and_set(std::memory_order_acquire))
std::this_thread::yield(); std::this_thread::yield();
if (Metrics)
Metrics->lockWaitNanoseconds += static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - started).count());
if(sampleCycles)
{
Metrics->sampledLockCycles+=AdjustedCpuCycleDelta(cycleStart,ReadCpuCycles());
++Metrics->sampledLocks;
}
} }
~FSpinGuard() { Flag.clear(std::memory_order_release); } ~FSpinGuard() { Flag.clear(std::memory_order_release); }
std::atomic_flag& Flag; std::atomic_flag& Flag;
FColumnCacheMetrics* Metrics;
}; };
public: public:
@@ -94,7 +129,7 @@ namespace VoxelForgeCoreShared
template <typename TFactory> template <typename TFactory>
TValue getOrCompute(const FColumnCacheKey& key, TFactory&& factory, TValue getOrCompute(const FColumnCacheKey& key, TFactory&& factory,
bool& computed) bool& computed, FColumnCacheMetrics* metrics = nullptr)
{ {
computed = false; computed = false;
const FKeyWords keyWords = key.words(); const FKeyWords keyWords = key.words();
@@ -102,13 +137,47 @@ namespace VoxelForgeCoreShared
const std::size_t setIndex = static_cast<std::size_t>(tag) & (SetCount - 1); const std::size_t setIndex = static_cast<std::size_t>(tag) & (SetCount - 1);
FSet& set = Sets[setIndex]; FSet& set = Sets[setIndex];
TValue value{}; TValue value{};
if (TryReadSet(set, keyWords, tag, value)) return value; static thread_local std::uint64_t timingSequence=0;
const bool sampleCycles=metrics&&((++timingSequence&127u)==0);
const std::uint64_t initialReadStart=sampleCycles?ReadCpuCycles():0;
const bool initialHit=TryReadSet(set, keyWords, tag, value);
if(sampleCycles)
{
metrics->sampledInitialReadCycles+=AdjustedCpuCycleDelta(
initialReadStart,ReadCpuCycles());
++metrics->sampledInitialReads;
}
if (initialHit)
{
if (metrics) ++metrics->hits;
return value;
}
if (metrics) ++metrics->misses;
FSpinGuard lock(set.Writer); FSpinGuard lock(set.Writer, metrics,sampleCycles);
if (TryReadSet(set, keyWords, tag, value)) return value; const std::uint64_t lockedReadStart=sampleCycles?ReadCpuCycles():0;
const bool lockedHit=TryReadSet(set, keyWords, tag, value);
if(sampleCycles)
{
metrics->sampledLockedReadCycles+=AdjustedCpuCycleDelta(
lockedReadStart,ReadCpuCycles());
++metrics->sampledLockedReads;
}
if (lockedHit)
{
if (metrics) ++metrics->hits;
return value;
}
const std::uint64_t buildStart=sampleCycles?ReadCpuCycles():0;
value = std::forward<TFactory>(factory)(); value = std::forward<TFactory>(factory)();
if(sampleCycles)
{
metrics->sampledBuildCycles+=AdjustedCpuCycleDelta(buildStart,ReadCpuCycles());
++metrics->sampledBuilds;
}
computed = true; computed = true;
if (metrics) ++metrics->computes;
FValueWords valueWords{}; FValueWords valueWords{};
std::memcpy(valueWords.data(), &value, sizeof(TValue)); std::memcpy(valueWords.data(), &value, sizeof(TValue));
@@ -118,6 +187,7 @@ namespace VoxelForgeCoreShared
{ replacement = way; break; } { replacement = way; break; }
if (replacement == Ways) if (replacement == Ways)
{ {
if (metrics) ++metrics->conflictReplacements;
replacement = set.NextWay; replacement = set.NextWay;
set.NextWay = static_cast<std::uint8_t>((set.NextWay + 1) % Ways); set.NextWay = static_cast<std::uint8_t>((set.NextWay + 1) % Ways);
} }
@@ -81,6 +81,9 @@ namespace VoxelForgeCore
Vec3d normal; Vec3d normal;
Vec2f uv; Vec2f uv;
ColorRgba8 color; ColorRgba8 color;
std::uint16_t hostMaterialId = 1;
std::array<std::uint16_t, 2> resourceMaterialIds{};
std::array<std::uint16_t, 2> resourceWeightsQ16{};
}; };
struct MeshGroups struct MeshGroups
@@ -136,6 +139,14 @@ namespace VoxelForgeCore
std::uint64_t columnCacheConflictMisses = 0; std::uint64_t columnCacheConflictMisses = 0;
std::uint64_t sharedColumnCacheHits = 0; std::uint64_t sharedColumnCacheHits = 0;
std::uint64_t columnEvaluations = 0; std::uint64_t columnEvaluations = 0;
std::uint64_t islandLocalCacheHits = 0;
std::uint64_t islandLocalCacheMisses = 0;
std::uint64_t islandLocalCacheConflictReplacements = 0;
std::uint64_t islandSharedCacheHits = 0;
std::uint64_t islandSharedCacheMisses = 0;
std::uint64_t islandSharedCacheComputes = 0;
std::uint64_t islandSharedCacheConflictReplacements = 0;
std::uint64_t islandSharedCacheLockWaitNanoseconds = 0;
std::uint64_t uniformBlockProofsChecked = 0; std::uint64_t uniformBlockProofsChecked = 0;
std::uint64_t uniformBlockProofSamplesChecked = 0; std::uint64_t uniformBlockProofSamplesChecked = 0;
std::uint64_t uniformBlockProofFallbackBlocks = 0; std::uint64_t uniformBlockProofFallbackBlocks = 0;
@@ -157,12 +168,30 @@ namespace VoxelForgeCore
double marchingCubesSkirtMilliseconds = 0.0; double marchingCubesSkirtMilliseconds = 0.0;
double marchingCubesOtherMilliseconds = 0.0; double marchingCubesOtherMilliseconds = 0.0;
std::uint64_t marchingCubesGradientFallbackDensityCalls = 0; std::uint64_t marchingCubesGradientFallbackDensityCalls = 0;
std::uint64_t marchingCubesGradientCalls = 0;
std::uint64_t marchingCubesEdgeMidpointDensityCalls = 0; std::uint64_t marchingCubesEdgeMidpointDensityCalls = 0;
std::uint64_t marchingCubesEdgeMidpointDensityMemoHits = 0;
std::uint64_t marchingCubesGradientMemoHits = 0; std::uint64_t marchingCubesGradientMemoHits = 0;
std::uint64_t marchingCubesX8DensityCalls = 0;
std::uint64_t marchingCubesX8ActiveLanes = 0;
std::uint64_t marchingCubesCandidateCells = 0;
std::uint64_t marchingCubesZBandRejectedCells = 0;
std::uint64_t marchingCubesCellClassificationCalls = 0;
std::uint64_t marchingCubesSurfaceCellCalls = 0;
std::uint64_t marchingCubesProofSkippedSolidCells = 0;
std::uint64_t marchingCubesProofSkippedAirCells = 0;
std::uint64_t marchingCubesProofSkippedDensitySamples = 0;
std::uint64_t marchingCubesSkirtEdgeCandidates = 0;
std::uint64_t marchingCubesSkirtBoundaryCandidates = 0;
std::uint64_t marchingCubesSkirtDensityProbes = 0;
double groundRefinementExclusiveMilliseconds = 0.0; double groundRefinementExclusiveMilliseconds = 0.0;
double capRefinementExclusiveMilliseconds = 0.0; double capRefinementExclusiveMilliseconds = 0.0;
double meshAssemblyMilliseconds = 0.0; double meshAssemblyMilliseconds = 0.0;
double meshingMilliseconds = 0.0; double meshingMilliseconds = 0.0;
double materialQueryMilliseconds = 0.0;
std::uint64_t materialQueries = 0;
std::uint64_t materialCandidateChecks = 0;
bool materialTileOutsideEveryVein = true;
double featureListMilliseconds = 0.0; double featureListMilliseconds = 0.0;
double farShellMilliseconds = 0.0; double farShellMilliseconds = 0.0;
double farStratumShellMilliseconds = 0.0; double farStratumShellMilliseconds = 0.0;
@@ -216,7 +245,9 @@ namespace VoxelForgeCore
std::uint64_t densitySampleCount = 0; std::uint64_t densitySampleCount = 0;
std::uint64_t meshPayloadAllocatedBytes = 0; std::uint64_t meshPayloadAllocatedBytes = 0;
std::uint64_t densityHash = 0, meshHash = 0, payloadHash = 0, topologyHash = 0; std::uint64_t densityHash = 0, meshHash = 0, payloadHash = 0, topologyHash = 0;
bool hasDensityHash = false, hasMeshHash = false, hasPayloadHash = false, hasTopologyHash = false; std::uint64_t materialHash = 0;
bool hasDensityHash = false, hasMeshHash = false, hasPayloadHash = false,
hasTopologyHash = false, hasMaterialHash = false;
}; };
struct WorldConfiguration struct WorldConfiguration
@@ -249,8 +280,127 @@ namespace VoxelForgeCore
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles. // L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256; inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2; inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
// Version 14 adds shared passage columns for long native open-air crossings. // Version 14 adds shared passage columns; version 16 smooths owner Gradient faces;
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 14; // version 20 restores full Gradient amplitude and uses a continuous signed-distance band union;
// version 22 overlaps tread sections and smooths FloatingIslands passage-mouth features;
// version 23 uses signed-distance support for sparse SurfaceWorld-to-TunnelNetwork treads;
// version 24 narrows passage-mouth support and filters native ground detail;
// version 25 joins open-mouth treads to native terrain relief before flattening inward;
// version 26 tapers mouth width and bounds the support to the native terrain surface;
// version 27 keeps the native blend through the entrance ramp and narrows its filter;
// version 28 tapers the open end to a worn path and blends it into walkable support;
// version 29 curves the supported and carved path through both mouth shoulders;
// version 30 narrows the flat core and blends support through a noisy natural shoulder;
// version 31 adds shared, capsule-safe field relief to both open-mouth walking surfaces;
// version 32 lowers the mouth core into the bore and removes thin filtered-field remnants;
// version 33 fades relief before small detached field remnants;
// version 34 keeps the open-mouth tread within native terrain curvature and noise;
// version 35 follows the native heightfield farther into the mouth ramp;
// version 36 protects the walking line with a gradual filter fade;
// version 37 lengthens that fade and restores the last capsule margin;
// version 38 keeps native ground at the lip and blends the tread in below it;
// version 39 bends the mouth tread across the native field and restores the open bore;
// version 40 uses broad route-only filtering and smooth, low-frequency tread relief;
// version 41 rounds the mouth tread and removes filter route masks;
// version 42 clips the mouth bore at the native walking surface;
// version 43 localizes the mouth field filter without masking the surface route;
// version 44 widens the natural shoulder blend and follows lower-frequency terrain;
// version 45 tests the passage tread independently of route-anchor support;
// version 46 fades the native filter broadly along the authored surface path;
// version 47 carries the full winding route with a narrow native-height tread;
// version 48 fades route support out before the surface mouth;
// version 49 keeps the mouth filtered and restores detail on the outer route;
// version 50 limits route-field restoration to 65% of the convolution weight;
// version 51 narrows the route fade to 8 m horizontal and 6 m vertical;
// version 52 evaluates mouth support in positive-density route tiles;
// version 53 fades the supported passage tread in from native ground at both mouths;
// version 54 tapers mouth-support width and depth;
// version 55 blends that tread from native ground and applies route support after carving;
// version 56 seats route support at the audited floor;
// version 57's raised lip overfilled the mouth;
// version 58 restores the native-height lip and explicitly covers route-support queries;
// version 59 fixes route-support distance units and lengthens the worn-mouth taper;
// version 60 brings the narrow terrain-following route tread in before the outer mouth lip;
// version 61 finishes that tread by five metres and preserves its exact walking boundary;
// version 62 fades the route tread along the authored path and seats it below the capsule;
// version 63 only adds that tread through native-air gaps and fades it over two path metres;
// version 64 rounds the path-to-ground join while keeping its top below the capsule;
// version 65 tests rounded tread cross-sections and softer native-height following;
// version 66 fades the mouth carve and support together so native crater ground forms the lip;
// version 67 follows the native route height more closely and completes the entrance fade sooner;
// version 68 narrows the flat walk core and eases a shallow worn ramp into the cave floor;
// version 69 lets capsule-cleared mouth treads bridge positive-density floor gaps;
// version 70 lowers the mouth tread enough to clear the capsule's toe margin;
// version 71 keeps its rounded top inside the route's shallow support-probe band;
// version 72 adds mouth support only below its signed floor, avoiding capsule-space fill;
// version 73 carries the terrain-following route tread to the actual passage mouth;
// version 74 seats that short connection in the audited shallow support band;
// version 75 keeps the rounded path edge outside the capsule's shallow floor probes;
// version 76 carries the endpoint tread through the passage's first support-probe depth;
// version 77 joins the mouth floor to native ground through a noisy walk ramp;
// version 78 rounds the ramp shoulder and clears its initial capsule envelope;
// version 79 tested the unbuilt native-ground mouth;
// version 80 narrows the walk tread;
// version 81 adds bounded path relief and a wavering, worn shoulder;
// version 82 lowers the relief into the tread to keep its capsule clearance;
// version 83 increases the natural edge warp and recessed surface variation;
// version 84 moves that variation to the actual mouth and restores support width;
// version 85 keeps the established lower-mouth capsule profile while shaping entry mouths;
// version 86 restricts route reinforcement to native solid and recesses the worn top;
// version 87 restores its pre-carve route placement.
// version 88 removes the independent mouth cap; the native crater surface forms the entry ramp.
// version 89 removes the one-sided entry tread band as well.
// version 90 lengthens the entrance floor-carve fade to retain native ground beneath the route.
// version 91 lets a narrow mouth support follow the full native surface relief.
// version 92 derives mouth floor alignment from the entrance path for first-segment samples too.
// version 93 blends the carve against native density so a zero fade really preserves ground.
// version 94 applies the mouth floor fade only beneath the walking surface.
// version 95 seats retained native ground just below the capsule's foot clearance.
// version 96 limits that preservation to the shallow support band, away from inner coils.
// version 97 extends the native support band down to the sampled rock envelope.
// version 98 raises the support band back to the route floor while keeping it below the capsule.
// version 99 restores native-solid route support after the passage carve;
// version 100 restores a shallow, noise-shaped entrance tread under its capsule gate;
// version 101 preserves the native FI field around the resolved walking line;
// version 102 tapers the mouth filter from its 1.5 m core to the same 12.5 m edge;
// version 103 blends route protection over a metre-scale shoulder;
// version 104 composes overlapping route masks once, by maximum influence;
// version 105 widens the route blend to soften the native/filter seam;
// version 106 preserves raw negative rock under the route without unfiltering its crown;
// version 107 softens that restore and broadens the narrow upper-coil walk core;
// version 108 deepens relief and lowers route restoration to soften the remaining crease;
// version 109 blended 60% of the two-metre mouth field convolution;
// version 110 restores the full filtered contour and widens the upper walk core;
// version 111 permits the capsule-cleared signed tread to bridge the upper mouth gap;
// version 112 adds a tenth-metre margin to the rounded upper tread edge.
// version 139 keeps native ground only in a capsule-scale mouth lane and carries
// the seeded terrain contour through the full upper-mouth support;
// versions 140-145 tested short-span decks, mouth fade length, route phase and tread SDF;
// versions 146-148 tested a sunk chord, near-side phase and all-style FI spiral.
// version 149 routes non-spiral FI mouths into a tight near-side ring.
// version 150 widened that ring but left a central pillar.
// version 151 keeps the ring half a metre outside the passage bore.
// version 152 fills capsule-clear positive-density gaps under the narrow mouth tread.
// version 153 tested a one-metre entry-turn sink; it left the raised route visible and rippled the floor.
// version 154 tightens the FI ring and uses a steeper, still walkable descent to separate hidden coils.
// version 155's tighter coil removed the fin but made sharp fragments and route collisions.
// version 156 keeps the 3 m ring, overlaps the bore by 0.25 m, and seats the native tread below the capsule.
// version 157 joins the near-side FI ring at a tangent so its entrance does not kink across the crater.
// version 158 tested a descending entry coil; its exposed canopy is rejected.
// version 159 keeps the tangent and uses a steeper sub-15-degree FI descent to separate its coils.
// version 160 removed the mouth walk lane; it left the bridge and lost one support sample.
// version 161 tapered the anchored entry radius to 3 m; a fin remained at the crater lip.
// version 162 disabled native-tread restoration alone; the carve fade still preserved the ridge.
// version 163 disabled both passage restorations; it removed the path support.
// version 164 narrowed the first two inner FI route points; it enlarged the lip fin.
// version 165 disabled native-rock blending around the FI stratum route; the ridge remained.
// version 166 disabled the FI mouth support handoff; route support failed on the island approach.
// version 167 tested centerline footing on the FI approach; its bridge remained and floor ripple rose.
// version 168 removed the connector's 1 m lift; the crater crossing remained exposed.
// version 169 sank every connector 2 m; it left four FI route support probes low.
// version 170 sinks only connectors touching the anchored root by 1 m.
// Version 208 keeps the crater tread scoped to style-zero FI mouths.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 209;
enum class FieldChannel : std::uint32_t enum class FieldChannel : std::uint32_t
{ {
@@ -297,6 +447,59 @@ namespace VoxelForgeCore
std::int64_t capRidgeWavelengthSubvoxels = 256 * 64; std::int64_t capRidgeWavelengthSubvoxels = 256 * 64;
}; };
enum class WorldAssemblySurfaceProfileKindV1 : std::uint32_t
{
natural = 0,
periodicHills = 1,
mirroredRelief = 2,
flatField = 3
};
// Version zero is the historical noise terrain and is intentionally omitted from
// canonical JSON and hashes. Version one uses metre-derived fixed-scale dimensions;
// phases are Q0.32 turns generated from the recipe seed during plan construction.
struct WorldAssemblySurfaceProfileV1
{
std::uint32_t version = 0;
WorldAssemblySurfaceProfileKindV1 kind = WorldAssemblySurfaceProfileKindV1::natural;
std::int64_t h0Subvoxels = 0;
std::int64_t amplitudeXSubvoxels = 0;
std::int64_t amplitudeYSubvoxels = 0;
std::int64_t wavelengthXSubvoxels = 0;
std::int64_t wavelengthYSubvoxels = 0;
std::uint32_t phaseXTurnsQ32 = 0;
std::uint32_t phaseYTurnsQ32 = 0;
std::int64_t mirrorLambdaQ16 = 0;
std::int64_t minimumArenaClearanceSubvoxels = 0;
friend bool operator==(const WorldAssemblySurfaceProfileV1&,
const WorldAssemblySurfaceProfileV1&) = default;
};
inline std::uint32_t WorldAssemblySurfaceProfileSeededPhaseQ32(
std::uint32_t seed, std::uint32_t axis) noexcept
{
std::uint64_t value = static_cast<std::uint64_t>(seed)
^ (axis == 0 ? 0x706572696f645831ull : 0x706572696f645931ull);
value += 0x9E3779B97F4A7C15ull;
value = (value ^ (value >> 30)) * 0xBF58476D1CE4E5B9ull;
value = (value ^ (value >> 27)) * 0x94D049BB133111EBull;
value ^= value >> 31;
return static_cast<std::uint32_t>(value >> 32);
}
inline void ResolveWorldAssemblySurfaceProfilePhasesV1(
WorldAssemblySurfaceProfileV1& profile, std::uint32_t seed) noexcept
{
if (profile.version == 1
&& (profile.kind == WorldAssemblySurfaceProfileKindV1::periodicHills
|| profile.kind == WorldAssemblySurfaceProfileKindV1::mirroredRelief))
{
profile.phaseXTurnsQ32 = WorldAssemblySurfaceProfileSeededPhaseQ32(seed, 0);
profile.phaseYTurnsQ32 = WorldAssemblySurfaceProfileSeededPhaseQ32(seed, 1);
}
}
struct WorldAssemblyRecipeV1 struct WorldAssemblyRecipeV1
{ {
std::uint32_t schemaVersion = WorldAssemblySchemaVersion; std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
@@ -314,6 +517,7 @@ namespace VoxelForgeCore
std::int64_t biomeClimateReliefWavelengthSubvoxels = 256 * 3333; std::int64_t biomeClimateReliefWavelengthSubvoxels = 256 * 3333;
std::int64_t biomeClimateMoistureWavelengthSubvoxels = 256 * 2500; std::int64_t biomeClimateMoistureWavelengthSubvoxels = 256 * 2500;
std::int64_t biomeClimateReliefContrastQ16 = 2ll << 16; std::int64_t biomeClimateReliefContrastQ16 = 2ll << 16;
WorldAssemblySurfaceProfileV1 surfaceProfile;
std::vector<WorldAssemblyBiomeProfileV1> biomes; std::vector<WorldAssemblyBiomeProfileV1> biomes;
std::uint64_t contentHash = 0; std::uint64_t contentHash = 0;
}; };
@@ -373,6 +577,7 @@ namespace VoxelForgeCore
std::int64_t capRidgeWavelengthSubvoxels = 256 * 64; std::int64_t capRidgeWavelengthSubvoxels = 256 * 64;
std::int64_t waterSurfaceSubvoxels = 0; std::int64_t waterSurfaceSubvoxels = 0;
bool waterEnabled = false; bool waterEnabled = false;
WorldAssemblySurfaceProfileV1 surfaceProfile;
std::vector<WorldAssemblyFeatureV1> features; std::vector<WorldAssemblyFeatureV1> features;
WorldAssemblyRouteConstraintsV1 routeConstraints; WorldAssemblyRouteConstraintsV1 routeConstraints;
std::vector<WorldAssemblyRoutePointV1> route; std::vector<WorldAssemblyRoutePointV1> route;
@@ -444,6 +649,8 @@ namespace VoxelForgeCore
std::int64_t anchorIslandMinimumRadiusSubvoxels = 0; std::int64_t anchorIslandMinimumRadiusSubvoxels = 0;
std::int64_t anchorRootBottomZSubvoxels = 0; std::int64_t anchorRootBottomZSubvoxels = 0;
std::int64_t anchorMouthOffsetSubvoxels = 0; std::int64_t anchorMouthOffsetSubvoxels = 0;
// Style-zero mouths follow the sampled island surface into the passage lip.
bool terrainFollowingRoute = false;
WorldAssemblyRouteConstraintsV1 routeConstraints; WorldAssemblyRouteConstraintsV1 routeConstraints;
std::uint64_t contentHash = 0; std::uint64_t contentHash = 0;
}; };
@@ -584,8 +791,139 @@ namespace VoxelForgeCore
// Core adapter. Coordinates are half-open micrometre intervals; recipe anchors retain their // Core adapter. Coordinates are half-open micrometre intervals; recipe anchors retain their
// source fixed-point units so this record never rounds the recipe itself. // source fixed-point units so this record never rounds the recipe itself.
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV3 = 3; inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV3 = 3;
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV1 = 1;
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV2 = 2;
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3;
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1;
inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict }; enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
// Stable, bounded geometry edits composed into every Core field after the shared
// strate boundaries and passages. Coordinates and extents use integer micrometres.
enum class ResolvedSdfStampKindV1 : std::uint8_t
{
carveEllipsoid = 1,
carveArch = 2,
fillPillar = 3,
fillDais = 4,
fillMonolith = 5,
fillPlatform = 6
};
struct ResolvedLayoutStampRequestV1
{
std::string stableId;
std::int32_t strateIndex = -1;
ResolvedSdfStampKindV1 kind = ResolvedSdfStampKindV1::carveEllipsoid;
// A preferred center and a bounded deterministic search radius.
std::array<std::int64_t, 3> anchorPositionMicrometres{};
std::int64_t placementRadiusMicrometres = 0;
std::array<std::int64_t, 3> halfExtentsMicrometres{};
std::int64_t smoothWidthMicrometres = 0;
std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0;
std::int64_t roundingRadiusMicrometres = 0;
std::int64_t minimumSpacingMicrometres = 0;
// 0 = free placement, 1 = seated on the authored flat profile floor.
std::uint8_t attachmentPolicy = 0;
std::int64_t floorMicrometres = 0;
std::uint8_t yawQuarterTurns = 0;
};
struct ResolvedLayoutStampV1
{
std::string stableId;
std::int32_t strateIndex = -1;
ResolvedSdfStampKindV1 kind = ResolvedSdfStampKindV1::carveEllipsoid;
std::array<std::int64_t, 3> positionMicrometres{};
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
std::array<std::int64_t, 3> halfExtentsMicrometres{};
std::int64_t smoothWidthMicrometres = 0;
std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0;
std::int64_t roundingRadiusMicrometres = 0;
std::int64_t minimumSpacingMicrometres = 0;
std::uint8_t attachmentPolicy = 0;
std::int64_t floorMicrometres = 0;
std::uint64_t irregularitySeed = 0;
std::uint8_t yawQuarterTurns = 0;
};
// Optional room and swept-edge records for a resolved cavern graph. Geometry is stored
// in integer micrometres; bounds include smooth blending, relief, and floor support.
struct ResolvedCavernRoomV1
{
std::string stableId;
std::int32_t strateIndex = -1;
std::array<std::int64_t, 3> centerMicrometres{};
std::array<std::int64_t, 3> halfExtentsMicrometres{};
std::int64_t floorMicrometres = 0;
std::int64_t smoothWidthMicrometres = 0;
// Optional feature-to-feature union width. Zero preserves the historical
// behavior where smoothWidthMicrometres also controls the graph union.
std::int64_t unionWidthMicrometres = 0;
std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0;
std::uint64_t irregularitySeed = 0;
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
};
struct ResolvedCavernEdgeV1
{
std::string stableId;
std::string startAnchorStableId;
std::string endRoomStableId;
std::int32_t strateIndex = -1;
// A single cubic Bezier in XY. Its four Z values are the cubic floor profile.
std::array<std::array<std::int64_t, 3>, 4> controlPointsMicrometres{};
// Resolve-time, error-bounded approximation of the cubic centerline. Each
// consecutive pair is a physical segment used by the native cave field.
std::vector<std::array<std::int64_t, 3>> polylineMicrometres;
std::int64_t boreRadiusMicrometres = 0;
std::int64_t boreHeightMicrometres = 0;
std::int64_t floorSupportDepthMicrometres = 0;
std::int64_t smoothWidthMicrometres = 0;
// Optional feature-to-feature union width; floor fillets still use smoothWidth.
std::int64_t unionWidthMicrometres = 0;
std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0;
std::uint64_t irregularitySeed = 0;
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
};
// Immutable material-only deposits. The capsule and its conservative bounds are in
// micrometres; bounds include irregularity and the blend envelope.
struct ResolvedLayoutOreVeinRequestV1
{
std::string stableId;
std::int32_t strateIndex = -1;
std::uint16_t resourceMaterialId = 0;
std::array<std::array<std::int64_t, 3>, 2> anchorEndpointsMicrometres{};
std::int64_t placementRadiusMicrometres = 0;
std::int64_t radiusMicrometres = 0;
std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0;
std::int64_t blendWidthMicrometres = 0;
};
struct ResolvedLayoutOreVeinV1
{
std::string stableId;
std::int32_t strateIndex = -1;
std::uint16_t resourceMaterialId = 0;
std::array<std::array<std::int64_t, 3>, 2> endpointsMicrometres{};
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
std::int64_t radiusMicrometres = 0;
std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0;
std::int64_t blendWidthMicrometres = 0;
std::uint64_t irregularitySeed = 0;
};
struct ResolvedLayoutAnchorV2 struct ResolvedLayoutAnchorV2
{ {
std::string kind; std::string kind;
@@ -608,6 +946,24 @@ namespace VoxelForgeCore
std::int64_t solidDensityQ16 = 1ll << 16; std::int64_t solidDensityQ16 = 1ll << 16;
}; };
// Runtime policy resolved once from the director's immutable strate/interface
// description. These derived capabilities are deliberately omitted from canonical
// JSON: their source fields are already hashed and the records preserve that identity.
struct ResolvedLayoutStratePassageCapabilitiesV1
{
bool hasSurfaceLanding = false;
bool hasFloatingIslandLanding = false;
bool usePassageAnchorForColumns = false;
bool hasNativeCaveField = false;
bool hasNativeLatticeField = false;
};
struct ResolvedLayoutInterfaceCapabilitiesV1
{
bool preserveUpperBedrockAirGap = false;
bool surfaceToTunnelLandingSupport = false;
};
struct ResolvedLayoutStratumV2 struct ResolvedLayoutStratumV2
{ {
std::string stableId; std::string stableId;
@@ -637,6 +993,41 @@ namespace VoxelForgeCore
std::int64_t managerChunkWidthMicrometres = 0; std::int64_t managerChunkWidthMicrometres = 0;
std::int64_t dispatchChunkWidthMicrometres = 0; std::int64_t dispatchChunkWidthMicrometres = 0;
std::vector<ResolvedLayoutAnchorV2> anchors; std::vector<ResolvedLayoutAnchorV2> anchors;
ResolvedLayoutStratePassageCapabilitiesV1 passageCapabilities;
ResolvedLayoutInterfaceCapabilitiesV1 lowerInterfaceCapabilities;
};
inline bool IsNativeCaveFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
{
return stratum.generatorKind=="Caves"
&&stratum.recipeId=="Core.Caves.PrimitiveField"
&&stratum.recipeVersion==1;
}
inline constexpr std::uint32_t NativeLatticeFieldRecipeVersion=2;
inline bool IsNativeLatticeFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
{
return (stratum.generatorKind=="Maze"||stratum.generatorKind=="VerticalShafts")
&&stratum.recipeId=="Core.Lattice.PrimitiveField"
&&stratum.recipeVersion==NativeLatticeFieldRecipeVersion;
}
inline bool HasNativeGraphField(const ResolvedLayoutStratumV2& stratum) noexcept
{
return IsNativeCaveFieldStratum(stratum)||IsNativeLatticeFieldStratum(stratum);
}
/** Per-column resolved bands for one adjacent bedrock/ceiling interface.
* crossingDepthMicrometres is the actual overlap depth between the two facing bands.
*/
struct ResolvedLayoutTransitionBandsV1
{
std::int64_t crossingDepthMicrometres = 0;
std::int64_t upperBedrockBottomMicrometres = 0;
std::int64_t upperBedrockTopMicrometres = 0;
std::int64_t lowerCeilingBottomMicrometres = 0;
std::int64_t lowerCeilingTopMicrometres = 0;
}; };
struct ResolvedLayoutIssueV2 struct ResolvedLayoutIssueV2
@@ -656,6 +1047,8 @@ namespace VoxelForgeCore
std::string stableId; std::string stableId;
std::int32_t upperStrateIndex = -1; std::int32_t upperStrateIndex = -1;
std::int32_t lowerStrateIndex = -1; std::int32_t lowerStrateIndex = -1;
// EVoxelPassageStyle numeric value used by style-specific passage support.
std::uint32_t style = 1;
std::int64_t placementRadiusSubvoxels = 0; std::int64_t placementRadiusSubvoxels = 0;
std::array<std::int64_t, 3> upperMouthSubvoxels{}; std::array<std::int64_t, 3> upperMouthSubvoxels{};
std::array<std::int64_t, 3> lowerMouthSubvoxels{}; std::array<std::int64_t, 3> lowerMouthSubvoxels{};
@@ -719,6 +1112,16 @@ namespace VoxelForgeCore
std::int64_t strateLayoutChunkWidthSubvoxels = 8192; std::int64_t strateLayoutChunkWidthSubvoxels = 8192;
std::vector<ResolvedLayoutStratumV2> strates; std::vector<ResolvedLayoutStratumV2> strates;
std::vector<ResolvedLayoutPassageV1> passages; std::vector<ResolvedLayoutPassageV1> passages;
// Kept absent from canonical layout identity when no feature requests stamps.
std::uint32_t stampVocabularyVersion = 0;
std::vector<ResolvedLayoutStampV1> stamps;
// Kept absent from canonical identity when there are no graph records.
std::uint32_t cavernGraphVocabularyVersion = 0;
std::vector<ResolvedCavernRoomV1> cavernRooms;
std::vector<ResolvedCavernEdgeV1> cavernEdges;
// Omitted from canonical identity when no records are present.
std::uint32_t oreVeinVocabularyVersion = 0;
std::vector<ResolvedLayoutOreVeinV1> oreVeins;
std::string stableIdentity; std::string stableIdentity;
bool finalize(std::string& error); bool finalize(std::string& error);
@@ -730,6 +1133,17 @@ namespace VoxelForgeCore
std::string_view json, std::string& error); std::string_view json, std::string& error);
}; };
/**
* Resolve the deterministic band faces at one XY column. Gradient shares one smooth
* displaced face; Interleaved moves the faces inward by a total crossing no larger than
* the declared width. Both bands retain authored thickness and touch or overlap, so no
* air gap can form.
*/
VFCORE_API ResolvedLayoutTransitionBandsV1 EvaluateResolvedLayoutTransitionBandsV1(
const ResolvedLayoutStratumV2& upper, const ResolvedLayoutStratumV2& lower,
std::int32_t worldSeed, std::int64_t layoutChunkSizeMicrometres,
std::int64_t xMicrometres, std::int64_t yMicrometres);
struct ResolvedLayoutValidatorAuditV3 struct ResolvedLayoutValidatorAuditV3
{ {
std::uint32_t controlsRun = 0; std::uint32_t controlsRun = 0;
@@ -743,6 +1157,68 @@ namespace VoxelForgeCore
const std::vector<ResolvedLayoutPassageConfigV1>& configs, const std::vector<ResolvedLayoutPassageConfigV1>& configs,
std::string& error); std::string& error);
struct ResolvedLayoutStampPlacementAuditV1
{
std::uint32_t negativeControlsRun = 0;
std::uint32_t negativeControlsRejected = 0;
bool passed = false;
};
VFCORE_API bool ResolveResolvedLayoutStampsV1(
ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
std::span<const ResolvedLayoutStampRequestV1> requests,
std::string& error);
VFCORE_API bool ResolveResolvedLayoutStampsV2(
ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
std::span<const ResolvedLayoutStampRequestV1> requests,
std::string& error);
// Vocabulary 3 adds explicit rounded capped-cylinder monoliths and rounded-box platforms.
VFCORE_API bool ResolveResolvedLayoutStampsV3(
ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
std::span<const ResolvedLayoutStampRequestV1> requests,
std::string& error);
VFCORE_API ResolvedLayoutStampPlacementAuditV1 AuditResolvedLayoutStampPlacementV1(
const ResolvedLayoutSnapshotV3& layout);
VFCORE_API bool ResolveResolvedCavernGraphV1(
ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
std::span<const ResolvedCavernRoomV1> rooms,
std::span<const ResolvedCavernEdgeV1> edges,
std::string& error);
VFCORE_API bool ResolveResolvedLayoutOreVeinsV1(
ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
std::span<const ResolvedLayoutOreVeinRequestV1> requests,
std::string& error);
struct ResolvedLayoutOreVeinPlacementAuditV1
{
std::uint32_t negativeControlsRun = 0;
std::uint32_t negativeControlsRejected = 0;
bool passed = false;
};
VFCORE_API ResolvedLayoutOreVeinPlacementAuditV1 AuditResolvedLayoutOreVeinPlacementV1(
const ResolvedLayoutSnapshotV3& layout);
struct MaterialSampleV1
{
std::uint16_t hostMaterialId = 1;
std::array<std::uint16_t, 2> resourceMaterialIds{};
std::array<std::uint16_t, 2> resourceWeightsQ16{};
};
struct ResourceVolumeV1
{
std::uint16_t resourceMaterialId = 0;
std::uint64_t weightedSamplesQ16 = 0;
double volumeCubicMetres = 0.0;
};
struct OreMiningYieldV1
{
std::uint64_t solidSamplesRemoved = 0;
double sampleSpacingMetres = 0.0;
std::vector<ResourceVolumeV1> resources;
};
struct FloatingIslandBoundsAuditResult struct FloatingIslandBoundsAuditResult
{ {
std::uint64_t boxesTested = 0; std::uint64_t boxesTested = 0;
@@ -1021,6 +1497,7 @@ namespace VoxelForgeCore
const WorldConfiguration& configuration() const; const WorldConfiguration& configuration() const;
const WorldAssemblyManifest* worldAssembly() const noexcept; const WorldAssemblyManifest* worldAssembly() const noexcept;
const ResolvedLayoutSnapshotV3* resolvedLayout() const noexcept; const ResolvedLayoutSnapshotV3* resolvedLayout() const noexcept;
ResolvedWorldSnapshot withResolvedLayout(const ResolvedLayoutSnapshotV3& layout) const;
ResolvedWorldSnapshot withoutResolvedLayoutPassages() const; ResolvedWorldSnapshot withoutResolvedLayoutPassages() const;
private: private:
@@ -1064,6 +1541,7 @@ namespace VoxelForgeCore
bool roundTunnels = false; bool roundTunnels = false;
bool enhancedTunnelWalls = true; bool enhancedTunnelWalls = true;
bool profileDetailBand = false; bool profileDetailBand = false;
bool parallelSignEdgeRepair = false;
// World-wide walkability is a validation pass, not an input to the compiled field. // World-wide walkability is a validation pass, not an input to the compiled field.
// Keep it enabled for benchmarks/audits; the interactive viewer can defer it. // Keep it enabled for benchmarks/audits; the interactive viewer can defer it.
bool validatePassageRoutes = true; bool validatePassageRoutes = true;
@@ -1266,6 +1744,12 @@ namespace VoxelForgeCore
// Separate cold pass only; samples the MC inner stages without changing output. // Separate cold pass only; samples the MC inner stages without changing output.
// Passe froide separee seulement; mesure les etapes internes MC sans changer la sortie. // Passe froide separee seulement; mesure les etapes internes MC sans changer la sortie.
bool profileMesherStages = false; bool profileMesherStages = false;
// Research-only: collect tile-drain kernel counters and floating-island cache metrics.
// Inactive on ordinary generation so the hot path only pays the option check.
bool instrumentTileDrain = false;
bool exactMidpointDensityMemo = false;
bool activeLaneBatching = false;
std::uint32_t proofBlockCells = 8;
// Diagnostic only; fills and checks every lattice sample in blocks certified uniform. // Diagnostic only; fills and checks every lattice sample in blocks certified uniform.
// Diagnostic seulement; evalue et verifie tous les echantillons des blocs uniformes. // Diagnostic seulement; evalue et verifie tous les echantillons des blocs uniformes.
bool auditUniformBlockProofs = false; bool auditUniformBlockProofs = false;
@@ -1282,7 +1766,7 @@ namespace VoxelForgeCore
// LOD4-8 mesh hashes are salted; density and LOD0-3 keep their direct identities. // LOD4-8 mesh hashes are salted; density and LOD0-3 keep their direct identities.
// Incremente quand les plafonds partages ou l'horizon lointain changent. // Incremente quand les plafonds partages ou l'horizon lointain changent.
// Les hashes de maillage LOD4-8 incluent cette version; LOD0-3 gardent leur identite directe. // Les hashes de maillage LOD4-8 incluent cette version; LOD0-3 gardent leur identite directe.
std::uint32_t farHorizonVersion = 4; std::uint32_t farHorizonVersion = 5;
// Linux research controls for reproducing L5 negative controls. Defaults preserve output. // Linux research controls for reproducing L5 negative controls. Defaults preserve output.
std::uint32_t diagnosticGlobalOctaveDrop = 0; std::uint32_t diagnosticGlobalOctaveDrop = 0;
std::uint32_t diagnosticCellStride = 1; std::uint32_t diagnosticCellStride = 1;
@@ -1336,6 +1820,12 @@ namespace VoxelForgeCore
ShapeBuildStats buildStats() const; ShapeBuildStats buildStats() const;
float densityAt(Vec3d voxelPosition) const; float densityAt(Vec3d voxelPosition) const;
// Companion material query for the bound strate evaluator; coordinates use Core voxels.
MaterialSampleV1 materialAt(Vec3d voxelPosition) const;
// Measure-only carve estimate. This does not apply an edit or grant inventory.
OreMiningYieldV1 measureOreMiningYieldForSphere(
Vec3d centerVoxel, double radiusMetres,
double sampleSpacingMetres = 0.25) const;
// Audit-only view of density-field columns and short spans supporting passage voids. // Audit-only view of density-field columns and short spans supporting passage voids.
std::vector<PassageVoidCrossingAuditV1> passageVoidCrossings() const; std::vector<PassageVoidCrossingAuditV1> passageVoidCrossings() const;
bool isPassageColumnSample(std::size_t passageIndex, std::size_t segmentIndex, bool isPassageColumnSample(std::size_t passageIndex, std::size_t segmentIndex,
@@ -56,6 +56,7 @@
#include "VoxelForgeCore/VoxelForgeCore.h" #include "VoxelForgeCore/VoxelForgeCore.h"
#include "VoxelForgeCore/ThreadLocalStorage.h" #include "VoxelForgeCore/ThreadLocalStorage.h"
#include "VoxelForgeCore/SharedColumnCache.h" #include "VoxelForgeCore/SharedColumnCache.h"
#include "VoxelForgeCore/FloatingIslandInstrumentation.h"
// Keep Core's expression-level FMA behavior fixed regardless of the toolchain setting. // Keep Core's expression-level FMA behavior fixed regardless of the toolchain setting.
// Clang's "fast" mode ignores the pragma, so Core's runtime self-test rejects that build. // Clang's "fast" mode ignores the pragma, so Core's runtime self-test rejects that build.
+240 -25
View File
@@ -1217,6 +1217,82 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices; Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
} }
// Fill only underside cells that marching cubes missed. Active transitions use the
// same displaced face and passage-mouth fade as the signed-distance field.
struct FTransitionFaceReplacement
{
double BaseZVoxels=0.0;
double MaximumDisplacementVoxels=0.0;
std::vector<uint8> Cells;
};
inline int32 RemoveCoarseTransitionFaceTriangles(FVoxelMeshData& Mesh,
const FIntVector& Origin,int32 Step,int32 GridSide,double VoxelSizeCm,
const std::vector<FTransitionFaceReplacement>& Replacements,bool bUpward)
{
if(Step<=0||GridSide<2||VoxelSizeCm<=0.0||Replacements.empty())return 0;
const int32 TriangleCount=Mesh.Triangles.Num()/3;
const int32 OldCeilingStart=std::max(0,TriangleCount-Mesh.NumCeilingTriangles);
TArray<uint8> OldMasks=Mesh.TriangleSkirtFaceMasks;
OldMasks.SetNum(TriangleCount);
TArray<int32> KeptTriangles;
TArray<uint8> KeptMasks;
KeptTriangles.Reserve(Mesh.Triangles.Num());
KeptMasks.Reserve(TriangleCount);
int32 Removed=0,RemovedCeiling=0;
const int32 CellSide=GridSide-1;
for(int32 Triangle=0;Triangle<TriangleCount;++Triangle)
{
const int32 IA=Mesh.Triangles[Triangle*3];
const int32 IB=Mesh.Triangles[Triangle*3+1];
const int32 IC=Mesh.Triangles[Triangle*3+2];
bool bRemove=false;
if(OldMasks[Triangle]==0&&IA>=0&&IB>=0&&IC>=0
&&IA<Mesh.Vertices.Num()&&IB<Mesh.Vertices.Num()&&IC<Mesh.Vertices.Num())
{
const FVector& A=Mesh.Vertices[IA];
const FVector& B=Mesh.Vertices[IB];
const FVector& C=Mesh.Vertices[IC];
const FVector GeometricNormal=FVector::CrossProduct(B-A,C-A);
const double NormalLength=GeometricNormal.Size();
const double Facing=NormalLength>1.0e-12?GeometricNormal.Z/NormalLength:0.0;
if((bUpward&&Facing>0.35)||(!bUpward&&Facing< -0.35))
{
const double X=(A.X+B.X+C.X)/(3.0*VoxelSizeCm);
const double Y=(A.Y+B.Y+C.Y)/(3.0*VoxelSizeCm);
const double Z=(A.Z+B.Z+C.Z)/(3.0*VoxelSizeCm);
const int32 CellX=static_cast<int32>(std::floor((X-Origin.X)/Step));
const int32 CellY=static_cast<int32>(std::floor((Y-Origin.Y)/Step));
if(CellX>=0&&CellX<CellSide&&CellY>=0&&CellY<CellSide)
{
const std::size_t Cell=static_cast<std::size_t>(CellY)*CellSide+CellX;
for(const auto& Replacement:Replacements)
{
if(Cell>=Replacement.Cells.size()||!Replacement.Cells[Cell])continue;
const double Margin=Replacement.MaximumDisplacementVoxels+4.0;
if(Z>=Replacement.BaseZVoxels-Margin
&&Z<=Replacement.BaseZVoxels+Margin)
{bRemove=true;break;}
}
}
}
}
if(bRemove)
{
++Removed;
if(Triangle>=OldCeilingStart)++RemovedCeiling;
continue;
}
KeptTriangles.Add(IA);KeptTriangles.Add(IB);KeptTriangles.Add(IC);
KeptMasks.Add(OldMasks[Triangle]);
}
if(Removed==0)return 0;
Mesh.Triangles=std::move(KeptTriangles);
Mesh.TriangleSkirtFaceMasks=std::move(KeptMasks);
Mesh.NumCeilingTriangles=std::max(0,Mesh.NumCeilingTriangles-RemovedCeiling);
return Removed;
}
inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World, inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh) const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
{ {
@@ -1230,9 +1306,14 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
{ {
double FaceZ=0.0; double FaceZ=0.0;
double Thickness=0.0; double Thickness=0.0;
double MaximumDisplacementVoxels=0.0;
std::vector<double> FaceHeights;
std::vector<uint8> SolidAbove; std::vector<uint8> SolidAbove;
const VoxelForgeCore::ResolvedLayoutStratumV2* Stratum=nullptr;
bool bTransition=false;
}; };
std::vector<FCandidate> Candidates; std::vector<FCandidate> Candidates;
std::vector<FTransitionFaceReplacement> Replacements;
const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons); const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
for(const FStrateSlot& Slot:World.Manager->GetLayout()) for(const FStrateSlot& Slot:World.Manager->GetLayout())
{ {
@@ -1243,19 +1324,34 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
Candidate.FaceZ=static_cast<double>(Stratum->ceiling.bottomMicrometres)/CoreMicrons; Candidate.FaceZ=static_cast<double>(Stratum->ceiling.bottomMicrometres)/CoreMicrons;
Candidate.Thickness=static_cast<double>(Stratum->ceiling.topMicrometres Candidate.Thickness=static_cast<double>(Stratum->ceiling.topMicrometres
-Stratum->ceiling.bottomMicrometres)/CoreMicrons; -Stratum->ceiling.bottomMicrometres)/CoreMicrons;
if(Candidate.FaceZ<Origin.Z-1.0e-6||Candidate.FaceZ>Origin.Z+Extent+1.0e-6)continue; Candidate.Stratum=Stratum;
const auto* transitionUpper=Shape.FindResolvedLayoutStratumByOrder(Stratum->order-1);
const bool bTransition=transitionUpper
&&Shape.IsResolvedLayoutTransitionActive(*transitionUpper);
const double displacement=bTransition?static_cast<double>(
Shape.ResolvedTransitionMaximumFaceDisplacementMicrometres(*transitionUpper,*Stratum))
/CoreMicrons:0.0;
if(Candidate.FaceZ<Origin.Z-displacement-1.0e-6
||Candidate.FaceZ>Origin.Z+Extent+displacement+1.0e-6)continue;
Candidate.bTransition=bTransition;
Candidate.MaximumDisplacementVoxels=displacement;
const double Epsilon=std::min(0.5,Candidate.Thickness*0.25); const double Epsilon=std::min(0.5,Candidate.Thickness*0.25);
if(Epsilon<=0.0)continue; if(Epsilon<=0.0)continue;
Candidate.SolidAbove.resize(static_cast<std::size_t>(GridSide)*GridSide,0); const std::size_t GridCount=static_cast<std::size_t>(GridSide)*GridSide;
Candidate.FaceHeights.resize(GridCount,Candidate.FaceZ);
Candidate.SolidAbove.resize(GridCount,0);
bool AnySolidAbove=false; bool AnySolidAbove=false;
for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X) for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X)
{ {
const double WorldX=Origin.X+X*GridStep; const double WorldX=Origin.X+X*GridStep;
const double WorldY=Origin.Y+Y*GridStep; const double WorldY=Origin.Y+Y*GridStep;
const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
Candidate.bTransition=Shape.ResolvedCeilingBottomAt(*Candidate.Stratum,
WorldX,WorldY,Candidate.FaceHeights[GridIndex])||Candidate.bTransition;
const float Above=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY), const float Above=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
static_cast<float>(Candidate.FaceZ+Epsilon)); static_cast<float>(Candidate.FaceHeights[GridIndex]+Epsilon));
const bool bSolidAbove=std::isfinite(Above)&&Above<0.0f; const bool bSolidAbove=std::isfinite(Above)&&Above<0.0f;
Candidate.SolidAbove[static_cast<std::size_t>(Y*GridSide+X)]=bSolidAbove?1:0; Candidate.SolidAbove[GridIndex]=bSolidAbove?1:0;
AnySolidAbove=AnySolidAbove||bSolidAbove; AnySolidAbove=AnySolidAbove||bSolidAbove;
} }
if(AnySolidAbove)Candidates.push_back(std::move(Candidate)); if(AnySolidAbove)Candidates.push_back(std::move(Candidate));
@@ -1277,8 +1373,19 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
std::vector<int32> NewTriangles; std::vector<int32> NewTriangles;
for(const FCandidate& Candidate:Candidates) for(const FCandidate& Candidate:Candidates)
{ {
std::vector<int32> VertexMap(static_cast<std::size_t>(GridSide)*GridSide,INDEX_NONE); const std::size_t GridCount=static_cast<std::size_t>(GridSide)*GridSide;
const double CeilingZ=Candidate.FaceZ; std::vector<int32> VertexMap(GridCount,INDEX_NONE);
FTransitionFaceReplacement Replacement;
Replacement.BaseZVoxels=Candidate.FaceZ;
Replacement.MaximumDisplacementVoxels=Candidate.MaximumDisplacementVoxels;
Replacement.Cells.resize(static_cast<std::size_t>(GridSide-1)*(GridSide-1),0);
const auto heightAt=[&](double X,double Y)
{
if(!Candidate.bTransition)return Candidate.FaceZ;
double height=Candidate.FaceZ;
Shape.ResolvedCeilingBottomAt(*Candidate.Stratum,X,Y,height);
return height;
};
const auto AddVertex=[&](int32 X,int32 Y) const auto AddVertex=[&](int32 X,int32 Y)
{ {
const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X); const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
@@ -1286,8 +1393,20 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
if(Mapped!=INDEX_NONE)return Mapped; if(Mapped!=INDEX_NONE)return Mapped;
Mapped=static_cast<int32>(NewVertices.size()); Mapped=static_cast<int32>(NewVertices.size());
const double WorldX=Origin.X+X*GridStep,WorldY=Origin.Y+Y*GridStep; const double WorldX=Origin.X+X*GridStep,WorldY=Origin.Y+Y*GridStep;
NewVertices.emplace_back(WorldX*VoxelSizeCm,WorldY*VoxelSizeCm,CeilingZ*VoxelSizeCm); const double FaceZ=Candidate.FaceHeights[GridIndex];
NewNormals.emplace_back(0.0,0.0,-1.0); FVector Normal(0.0,0.0,-1.0);
if(Candidate.bTransition)
{
const double Delta=std::max(1.0,std::min<double>(GridStep,4.0));
const double DX=(heightAt(WorldX+Delta,WorldY)-heightAt(WorldX-Delta,WorldY))
/(2.0*Delta);
const double DY=(heightAt(WorldX,WorldY+Delta)-heightAt(WorldX,WorldY-Delta))
/(2.0*Delta);
Normal=FVector(DX,DY,-1.0);
Normal.Normalize();
}
NewVertices.emplace_back(WorldX*VoxelSizeCm,WorldY*VoxelSizeCm,FaceZ*VoxelSizeCm);
NewNormals.push_back(Normal);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(WorldX),static_cast<float>(WorldY)); if(bHasUVs)NewUVs.emplace_back(static_cast<float>(WorldX),static_cast<float>(WorldY));
if(bHasColors)NewColors.push_back(Color); if(bHasColors)NewColors.push_back(Color);
return Mapped; return Mapped;
@@ -1300,15 +1419,40 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
||!Candidate.SolidAbove[I01]||!Candidate.SolidAbove[I11])continue; ||!Candidate.SolidAbove[I01]||!Candidate.SolidAbove[I11])continue;
const double CellX0=Origin.X+X*GridStep,CellY0=Origin.Y+Y*GridStep; const double CellX0=Origin.X+X*GridStep,CellY0=Origin.Y+Y*GridStep;
const double CellX1=CellX0+GridStep,CellY1=CellY0+GridStep; const double CellX1=CellX0+GridStep,CellY1=CellY0+GridStep;
// Suppress a coincident plane only when it covers the full cell. A hit at just the bool fullyCovered=false;
// center of a curved shell triangle is not enough to leave the rest of this cap cell open. if(!Candidate.bTransition)
if(MeshIndex.HasDownwardCellCoverage(CellX0,CellY0,CellX1,CellY1,CeilingZ,0.001))continue; fullyCovered=MeshIndex.HasDownwardCellCoverage(
CellX0,CellY0,CellX1,CellY1,Candidate.FaceZ,0.001);
else
{
fullyCovered=true;
constexpr double CoverageSamples[5][2]={
{0.0,0.0},{1.0,0.0},{0.0,1.0},{1.0,1.0},{0.5,0.5}};
for(const auto& Sample:CoverageSamples)
{
const double U=Sample[0],V=Sample[1];
const double SampleX=CellX0+(CellX1-CellX0)*U;
const double SampleY=CellY0+(CellY1-CellY0)*V;
const double SampleZ=heightAt(SampleX,SampleY);
if(!MeshIndex.HasDownwardHit(SampleX,SampleY,SampleZ,0.25))
fullyCovered=false;
}
}
if(fullyCovered)continue;
if(Key.Level>=4&&Candidate.bTransition)
Replacement.Cells[static_cast<std::size_t>(Y)*(GridSide-1)+X]=1;
const int32 A=AddVertex(X,Y),B=AddVertex(X+1,Y); const int32 A=AddVertex(X,Y),B=AddVertex(X+1,Y);
const int32 C=AddVertex(X+1,Y+1),D=AddVertex(X,Y+1); const int32 C=AddVertex(X+1,Y+1),D=AddVertex(X,Y+1);
NewTriangles.insert(NewTriangles.end(),{A,C,B,A,D,C}); NewTriangles.insert(NewTriangles.end(),{A,C,B,A,D,C});
} }
if(Key.Level>=4&&Candidate.bTransition
&&std::any_of(Replacement.Cells.begin(),Replacement.Cells.end(),
[](uint8 Value){return Value!=0;}))
Replacements.push_back(std::move(Replacement));
} }
if(NewTriangles.empty())return; if(NewTriangles.empty())return;
const int32 removedCoarse=RemoveCoarseTransitionFaceTriangles(Mesh,Origin,GridStep,
GridSide,VoxelSizeCm,Replacements,false);
const int32 AddedVertices=static_cast<int32>(NewVertices.size()); const int32 AddedVertices=static_cast<int32>(NewVertices.size());
auto InsertVertices=[&](auto& Existing,const auto& Added) auto InsertVertices=[&](auto& Existing,const auto& Added)
@@ -1347,9 +1491,8 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
Mesh.NumCeilingTriangles+=static_cast<int32>(NewTriangles.size()/3); Mesh.NumCeilingTriangles+=static_cast<int32>(NewTriangles.size()/3);
} }
// Add the interior-facing top of each bedrock band. The density composition already // Fill only top cells that marching cubes missed. Active transitions use the same
// makes this band solid and editable; this planar closure keeps its visible floor // displaced face and passage-mouth fade as the signed-distance field.
// continuous when a coarse marching-cubes step would otherwise skip a sub-voxel band.
inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World, inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World,
const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh) const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
{ {
@@ -1358,8 +1501,16 @@ inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World,
const int32 Extent=Key.ExtentVoxels(); const int32 Extent=Key.ExtentVoxels();
if(Extent<=0||Extent%Step!=0)return; if(Extent<=0||Extent%Step!=0)return;
const int32 GridSide=Extent/Step+1; const int32 GridSide=Extent/Step+1;
struct FCandidate { double FloorZ=0.0; double Thickness=0.0; }; struct FCandidate
{
double FloorZ=0.0;
double Thickness=0.0;
double MaximumDisplacementVoxels=0.0;
const VoxelForgeCore::ResolvedLayoutStratumV2* Stratum=nullptr;
bool bTransition=false;
};
std::vector<FCandidate> Candidates; std::vector<FCandidate> Candidates;
std::vector<FTransitionFaceReplacement> Replacements;
const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons); const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
for(const FStrateSlot& Slot:World.Manager->GetLayout()) for(const FStrateSlot& Slot:World.Manager->GetLayout())
{ {
@@ -1367,9 +1518,17 @@ inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World,
if(!Stratum||!Stratum->bedrock.presentInCurrentField if(!Stratum||!Stratum->bedrock.presentInCurrentField
||Stratum->bedrock.topMicrometres<=Stratum->bedrock.bottomMicrometres)continue; ||Stratum->bedrock.topMicrometres<=Stratum->bedrock.bottomMicrometres)continue;
const double FloorZ=static_cast<double>(Stratum->bedrock.topMicrometres)/CoreMicrons; const double FloorZ=static_cast<double>(Stratum->bedrock.topMicrometres)/CoreMicrons;
if(FloorZ<Origin.Z-1.0e-6||FloorZ>Origin.Z+Extent+1.0e-6)continue; const auto* transitionLower=Shape.FindResolvedLayoutStratumByOrder(Stratum->order+1);
const bool bTransition=transitionLower
&&Shape.IsResolvedLayoutTransitionActive(*Stratum);
const double displacement=bTransition
?static_cast<double>(Shape.ResolvedTransitionMaximumFaceDisplacementMicrometres(
*Stratum,*transitionLower))/CoreMicrons
:0.0;
if(FloorZ<Origin.Z-displacement-1.0e-6
||FloorZ>Origin.Z+Extent+displacement+1.0e-6)continue;
Candidates.push_back({FloorZ,static_cast<double>(Stratum->bedrock.topMicrometres Candidates.push_back({FloorZ,static_cast<double>(Stratum->bedrock.topMicrometres
-Stratum->bedrock.bottomMicrometres)/CoreMicrons}); -Stratum->bedrock.bottomMicrometres)/CoreMicrons,displacement,Stratum,bTransition});
} }
if(Candidates.empty())return; if(Candidates.empty())return;
@@ -1390,16 +1549,34 @@ inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World,
{ {
const double Epsilon=std::min(0.5,Candidate.Thickness*0.25); const double Epsilon=std::min(0.5,Candidate.Thickness*0.25);
if(Epsilon<=0.0)continue; if(Epsilon<=0.0)continue;
std::vector<uint8> SolidBelow(static_cast<std::size_t>(GridSide)*GridSide,0); const std::size_t GridCount=static_cast<std::size_t>(GridSide)*GridSide;
std::vector<double> faceHeights(GridCount,Candidate.FloorZ);
std::vector<uint8> solidBelow(GridCount,0);
FTransitionFaceReplacement Replacement;
Replacement.BaseZVoxels=Candidate.FloorZ;
Replacement.MaximumDisplacementVoxels=Candidate.MaximumDisplacementVoxels;
Replacement.Cells.resize(static_cast<std::size_t>(GridSide-1)*(GridSide-1),0);
for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X) for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X)
{ {
const double WorldX=Origin.X+X*Step,WorldY=Origin.Y+Y*Step; const double WorldX=Origin.X+X*Step,WorldY=Origin.Y+Y*Step;
const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
if(Candidate.bTransition)
Shape.ResolvedBedrockTopAt(*Candidate.Stratum,WorldX,WorldY,
faceHeights[GridIndex]);
const float Density=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY), const float Density=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
static_cast<float>(Candidate.FloorZ-Epsilon)); static_cast<float>(faceHeights[GridIndex]-Epsilon));
SolidBelow[static_cast<std::size_t>(Y*GridSide+X)]= solidBelow[GridIndex]=
std::isfinite(Density)&&Density<0.0f?1:0; std::isfinite(Density)&&Density<0.0f?1:0;
} }
std::vector<int32> VertexMap(static_cast<std::size_t>(GridSide)*GridSide,INDEX_NONE); const auto heightAt=[&](double X,double Y)
{
if(!Candidate.bTransition)return Candidate.FloorZ;
double height=Candidate.FloorZ;
if(Candidate.bTransition)
Shape.ResolvedBedrockTopAt(*Candidate.Stratum,X,Y,height);
return height;
};
std::vector<int32> VertexMap(GridCount,INDEX_NONE);
const auto AddVertex=[&](int32 X,int32 Y) const auto AddVertex=[&](int32 X,int32 Y)
{ {
const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X); const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
@@ -1407,9 +1584,20 @@ inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World,
if(Mapped!=INDEX_NONE)return Mapped; if(Mapped!=INDEX_NONE)return Mapped;
Mapped=static_cast<int32>(NewVertices.size()); Mapped=static_cast<int32>(NewVertices.size());
const double WorldX=Origin.X+X*Step,WorldY=Origin.Y+Y*Step; const double WorldX=Origin.X+X*Step,WorldY=Origin.Y+Y*Step;
FVector Normal(0.0,0.0,1.0);
if(Candidate.bTransition)
{
const double Delta=std::max(1.0,std::min<double>(Step,4.0));
const double DX=(heightAt(WorldX+Delta,WorldY)-heightAt(WorldX-Delta,WorldY))
/(2.0*Delta);
const double DY=(heightAt(WorldX,WorldY+Delta)-heightAt(WorldX,WorldY-Delta))
/(2.0*Delta);
Normal=FVector(-DX,-DY,1.0);
Normal.Normalize();
}
NewVertices.emplace_back(WorldX*VoxelSizeCm,WorldY*VoxelSizeCm, NewVertices.emplace_back(WorldX*VoxelSizeCm,WorldY*VoxelSizeCm,
Candidate.FloorZ*VoxelSizeCm); faceHeights[GridIndex]*VoxelSizeCm);
NewNormals.emplace_back(0.0,0.0,1.0); NewNormals.push_back(Normal);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(WorldX),static_cast<float>(WorldY)); if(bHasUVs)NewUVs.emplace_back(static_cast<float>(WorldX),static_cast<float>(WorldY));
if(bHasColors)NewColors.push_back(Color); if(bHasColors)NewColors.push_back(Color);
return Mapped; return Mapped;
@@ -1418,16 +1606,43 @@ inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World,
{ {
const std::size_t I00=static_cast<std::size_t>(Y*GridSide+X); const std::size_t I00=static_cast<std::size_t>(Y*GridSide+X);
const std::size_t I10=I00+1,I01=I00+GridSide,I11=I01+1; const std::size_t I10=I00+1,I01=I00+GridSide,I11=I01+1;
if(!SolidBelow[I00]||!SolidBelow[I10]||!SolidBelow[I01]||!SolidBelow[I11])continue; if(!solidBelow[I00]||!solidBelow[I10]||!solidBelow[I01]||!solidBelow[I11])continue;
const double X0=Origin.X+X*Step,Y0=Origin.Y+Y*Step; const double X0=Origin.X+X*Step,Y0=Origin.Y+Y*Step;
const double X1=X0+Step,Y1=Y0+Step; const double X1=X0+Step,Y1=Y0+Step;
if(MeshIndex.HasUpwardCellCoverage(X0,Y0,X1,Y1,Candidate.FloorZ,0.001))continue; bool fullyCovered=false;
if(!Candidate.bTransition)
fullyCovered=MeshIndex.HasUpwardCellCoverage(
X0,Y0,X1,Y1,Candidate.FloorZ,0.001);
else
{
fullyCovered=true;
constexpr double CoverageSamples[5][2]={
{0.0,0.0},{1.0,0.0},{0.0,1.0},{1.0,1.0},{0.5,0.5}};
for(const auto& Sample:CoverageSamples)
{
const double U=Sample[0],V=Sample[1];
const double SampleX=X0+(X1-X0)*U;
const double SampleY=Y0+(Y1-Y0)*V;
const double SampleZ=heightAt(SampleX,SampleY);
if(!MeshIndex.HasUpwardHit(SampleX,SampleY,SampleZ,0.25))
fullyCovered=false;
}
}
if(fullyCovered)continue;
if(Key.Level>=4&&Candidate.bTransition)
Replacement.Cells[static_cast<std::size_t>(Y)*(GridSide-1)+X]=1;
const int32 A=AddVertex(X,Y),B=AddVertex(X+1,Y); const int32 A=AddVertex(X,Y),B=AddVertex(X+1,Y);
const int32 C=AddVertex(X+1,Y+1),D=AddVertex(X,Y+1); const int32 C=AddVertex(X+1,Y+1),D=AddVertex(X,Y+1);
NewTriangles.insert(NewTriangles.end(),{A,B,C,A,C,D}); NewTriangles.insert(NewTriangles.end(),{A,B,C,A,C,D});
} }
if(Key.Level>=4&&Candidate.bTransition
&&std::any_of(Replacement.Cells.begin(),Replacement.Cells.end(),
[](uint8 Value){return Value!=0;}))
Replacements.push_back(std::move(Replacement));
} }
if(NewTriangles.empty())return; if(NewTriangles.empty())return;
const int32 removedCoarse=RemoveCoarseTransitionFaceTriangles(Mesh,Origin,Step,
GridSide,VoxelSizeCm,Replacements,true);
const int32 AddedVertices=static_cast<int32>(NewVertices.size()); const int32 AddedVertices=static_cast<int32>(NewVertices.size());
auto InsertVertices=[&](auto& Existing,const auto& Added) auto InsertVertices=[&](auto& Existing,const auto& Added)
+516 -28
View File
@@ -1,6 +1,9 @@
#pragma once #pragma once
#include "VoxelForgeCore/ThreadLocalStorage.h" #include "VoxelForgeCore/ThreadLocalStorage.h"
#include "VoxelForgeCore/CpuCycleCounter.h"
#include "VoxelForgeCore/FloatingIslandInstrumentation.h"
#include "VoxelForgeCore/SharedColumnCache.h"
#include "Json.h" #include "Json.h"
#include "VoxelForgeCore/VoxelForgeCore.h" #include "VoxelForgeCore/VoxelForgeCore.h"
@@ -203,6 +206,7 @@ namespace floating_island_detail
HashWord(hash, static_cast<std::uint64_t>(recipe.boundarySealSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.boundarySealSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.bridgeHalfWidthSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.bridgeHalfWidthSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.bridgeThicknessSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.bridgeThicknessSubvoxels), 8);
if(recipe.terrainFollowingRoute)HashWord(hash,1,1);
HashWord(hash, HashRouteConstraints(recipe.routeConstraints), 8); HashWord(hash, HashRouteConstraints(recipe.routeConstraints), 8);
return hash; return hash;
} }
@@ -470,13 +474,69 @@ namespace floating_island_detail
double DeltaX=0.0,DeltaY=0.0,Radial=0.0,Angle=0.0,LobePhase=0.0,TopSurface=0.0; double DeltaX=0.0,DeltaY=0.0,Radial=0.0,Angle=0.0,LobePhase=0.0,TopSurface=0.0;
}; };
struct FIslandColumnCacheInstrumentation
{
std::uint64_t localHits=0,localMisses=0,localConflictReplacements=0;
std::uint64_t localProbeCycles=0,localProbeSamples=0;
std::uint64_t tileStartCycles=0,tileCycles=0;
VoxelForgeCoreShared::FColumnCacheMetrics shared;
FIslandFieldAttributionInstrumentation attribution;
};
inline FIslandColumnCacheInstrumentation*& ActiveIslandColumnCacheInstrumentation() noexcept
{
static thread_local FIslandColumnCacheInstrumentation* active=nullptr;
return active;
}
inline FIslandColumnCacheInstrumentation* SetIslandColumnCacheInstrumentation(
FIslandColumnCacheInstrumentation* value) noexcept
{
FIslandColumnCacheInstrumentation* previous=ActiveIslandColumnCacheInstrumentation();
ActiveIslandColumnCacheInstrumentation()=value;
SetActiveIslandFieldAttribution(value?&value->attribution:nullptr);
return previous;
}
using FSharedIslandColumnCache = using FSharedIslandColumnCache =
VoxelForgeCoreShared::TSharedColumnValueCache<FIslandColumnCacheEntry,16384,4>; VoxelForgeCoreShared::TSharedColumnValueCache<FIslandColumnCacheEntry,16384,4>;
struct FIslandColumnTraceKey
{
bool valid=false;
std::uint64_t recipeHash=0,planHash=0,island=0;
std::int64_t x=0,y=0;
};
struct FIslandColumnReuseTrace
{
std::array<FIslandColumnTraceKey,128> recentMisses{};
std::array<FIslandColumnTraceKey,128> recentEvictions{};
std::uint64_t missSequence=0,printedEvents=0;
};
inline bool SameTraceKey(const FIslandColumnTraceKey& left,
const FIslandColumnTraceKey& right) noexcept
{
return left.valid&&right.valid&&left.recipeHash==right.recipeHash
&&left.planHash==right.planHash&&left.island==right.island
&&left.x==right.x&&left.y==right.y;
}
inline std::size_t TraceKeyIndex(const FIslandColumnTraceKey& key) noexcept
{
std::uint64_t hash=key.island^static_cast<std::uint64_t>(key.x)*0x9e3779b97f4a7c15ull
^static_cast<std::uint64_t>(key.y)*0xc2b2ae3d27d4eb4full
^key.recipeHash^std::rotl(key.planHash,17);
hash^=hash>>33;hash*=0xff51afd7ed558ccdull;hash^=hash>>33;
return static_cast<std::size_t>(hash&127u);
}
inline const FIslandColumnCacheEntry& IslandColumnCache( inline const FIslandColumnCacheEntry& IslandColumnCache(
const FloatingIslandRecipeV1& recipe,const FloatingIslandPlanV1& plan, const FloatingIslandRecipeV1& recipe,const FloatingIslandPlanV1& plan,
const FloatingIslandAnchorV1& island,double x,double y) const FloatingIslandAnchorV1& island,double x,double y)
{ {
FScopedIslandAttributionTimer columnTimer(EIslandAttributionBucket::Column);
// Density samples arrive on integer Core lattice columns; reuse the exact column metrics // Density samples arrive on integer Core lattice columns; reuse the exact column metrics
// across Z samples without changing the field value or touching shared world state. // across Z samples without changing the field value or touching shared world state.
// Les echantillons suivent les colonnes Core entieres; reutiliser les memes metriques XY. // Les echantillons suivent les colonnes Core entieres; reutiliser les memes metriques XY.
@@ -486,6 +546,7 @@ namespace floating_island_detail
static thread_local VoxelForgeCoreThreadLocal::FLazy< static thread_local VoxelForgeCoreThreadLocal::FLazy<
std::array<FIslandColumnCacheEntry,SetCount*Ways>> Cache; std::array<FIslandColumnCacheEntry,SetCount*Ways>> Cache;
auto& cache=Cache.Get(); auto& cache=Cache.Get();
FScopedIslandAttributionTimer localProbeTimer(EIslandAttributionBucket::LocalProbe);
const std::int64_t SampleX=static_cast<std::int64_t>(x); const std::int64_t SampleX=static_cast<std::int64_t>(x);
const std::int64_t SampleY=static_cast<std::int64_t>(y); const std::int64_t SampleY=static_cast<std::int64_t>(y);
std::uint64_t IndexKey=island.stableId std::uint64_t IndexKey=island.stableId
@@ -500,6 +561,7 @@ namespace floating_island_detail
IndexKey^=IndexKey>>33; IndexKey^=IndexKey>>33;
const std::size_t Base=static_cast<std::size_t>(IndexKey&(SetCount-1))*Ways; const std::size_t Base=static_cast<std::size_t>(IndexKey&(SetCount-1))*Ways;
FIslandColumnCacheEntry* Replacement=&cache[Base+((IndexKey>>11)&(Ways-1))]; FIslandColumnCacheEntry* Replacement=&cache[Base+((IndexKey>>11)&(Ways-1))];
bool bFoundInvalid=false;
for(std::size_t Way=0;Way<Ways;++Way) for(std::size_t Way=0;Way<Ways;++Way)
{ {
FIslandColumnCacheEntry& Entry=cache[Base+Way]; FIslandColumnCacheEntry& Entry=cache[Base+Way];
@@ -515,8 +577,64 @@ namespace floating_island_detail
&&Entry.TopHalf==island.topHalfSubvoxels &&Entry.TopHalf==island.topHalfSubvoxels
&&Entry.TopFlattenQ16==recipe.topFlattenQ16 &&Entry.TopFlattenQ16==recipe.topFlattenQ16
&&Entry.ContourWarp==recipe.contourWarpSubvoxels; &&Entry.ContourWarp==recipe.contourWarpSubvoxels;
if(bKeyMatches)return Entry; if(bKeyMatches)
if(!Entry.bValid)Replacement=&Entry; {
if(auto* stats=ActiveIslandColumnCacheInstrumentation())++stats->localHits;
return Entry;
}
if(!Entry.bValid&&!bFoundInvalid)
{Replacement=&Entry;bFoundInvalid=true;}
}
localProbeTimer.Stop();
if(auto* stats=ActiveIslandColumnCacheInstrumentation())
{
++stats->localMisses;
if(!bFoundInvalid)++stats->localConflictReplacements;
static thread_local FIslandColumnReuseTrace trace;
if((++trace.missSequence&1023u)==0)
{
auto& a=stats->attribution;
++a.traceSampledMisses;
const FIslandColumnTraceKey requested{true,recipe.contentHash,
plan.contentHash,island.stableId,SampleX,SampleY};
const std::size_t requestSlot=TraceKeyIndex(requested);
const bool seenBefore=SameTraceKey(trace.recentMisses[requestSlot],requested);
const bool reusedEviction=SameTraceKey(
trace.recentEvictions[requestSlot],requested);
if(seenBefore)++a.traceSeenBefore;
if(reusedEviction)
{
++a.traceReusedEvictions;
trace.recentEvictions[requestSlot].valid=false;
}
FIslandColumnTraceKey evicted{};
std::size_t replacementWay=0;
if(!bFoundInvalid&&Replacement)
{
evicted={Replacement->bValid,Replacement->RecipeContentHash,
Replacement->PlanContentHash,Replacement->StableId,
Replacement->SampleX,Replacement->SampleY};
replacementWay=static_cast<std::size_t>(Replacement-&cache[Base]);
++a.traceEvictions;
if(evicted.valid)
trace.recentEvictions[TraceKeyIndex(evicted)]=evicted;
}
trace.recentMisses[requestSlot]=requested;
if(trace.printedEvents<16&&a.traceCount<a.traceSet.size())
{
const std::size_t event=a.traceCount++;
++trace.printedEvents;
a.traceSet[event]=Base/Ways;
a.traceX[event]=SampleX;a.traceY[event]=SampleY;
a.traceIsland[event]=island.stableId;
a.traceEvictedX[event]=evicted.valid?evicted.x:0;
a.traceEvictedY[event]=evicted.valid?evicted.y:0;
a.traceEvictedIsland[event]=evicted.valid?evicted.island:0;
a.traceFlags[event]=static_cast<std::uint8_t>(
(!bFoundInvalid?1u:0u)|(seenBefore?2u:0u)|(reusedEviction?4u:0u)
|(replacementWay<<3));
}
}
} }
const auto build=[&]() const auto build=[&]()
@@ -543,9 +661,14 @@ namespace floating_island_detail
Entry.Radial=std::hypot(Entry.DeltaX,Entry.DeltaY); Entry.Radial=std::hypot(Entry.DeltaX,Entry.DeltaY);
Entry.Angle=std::atan2(Entry.DeltaY,Entry.DeltaX); Entry.Angle=std::atan2(Entry.DeltaY,Entry.DeltaX);
Entry.LobePhase=LobePhase(recipe,island); Entry.LobePhase=LobePhase(recipe,island);
double contourOffset=0.0;
{
FScopedIslandAttributionTimer helixTimer(EIslandAttributionBucket::Helix);
contourOffset=LobeOffsetWithPhase(recipe,Entry.Angle,
static_cast<double>(island.topZSubvoxels),Entry.LobePhase);
}
const double EffectiveRadius=std::max(1.0,static_cast<double>(island.radiusSubvoxels) const double EffectiveRadius=std::max(1.0,static_cast<double>(island.radiusSubvoxels)
+LobeOffsetWithPhase(recipe,Entry.Angle,static_cast<double>(island.topZSubvoxels), +contourOffset);
Entry.LobePhase));
const double Edge=std::clamp(Entry.Radial/EffectiveRadius,0.0,1.0); const double Edge=std::clamp(Entry.Radial/EffectiveRadius,0.0,1.0);
const double Drop=(1.0-Q16ToDouble(recipe.topFlattenQ16)) const double Drop=(1.0-Q16ToDouble(recipe.topFlattenQ16))
*static_cast<double>(island.topHalfSubvoxels)*2.0*Edge*Edge; *static_cast<double>(island.topHalfSubvoxels)*2.0*Edge*Edge;
@@ -561,7 +684,12 @@ namespace floating_island_detail
static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)), static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),
plan.contentHash,SampleX,SampleY,island.stableId}; plan.contentHash,SampleX,SampleY,island.stableId};
bool computed=false; bool computed=false;
Entry=shared->getOrCompute(key,build,computed); auto* stats=ActiveIslandColumnCacheInstrumentation();
{
FScopedIslandAttributionTimer sharedCacheTimer(EIslandAttributionBucket::SharedCache);
Entry=shared->getOrCompute(key,build,computed,
stats?&stats->shared:nullptr);
}
} }
else Entry=build(); else Entry=build();
*Replacement=Entry; *Replacement=Entry;
@@ -624,6 +752,7 @@ namespace floating_island_detail
const FloatingIslandAnchorV1& island, const FloatingIslandAnchorV1& island,
std::int32_t rootIndex, double x, double y, double z) std::int32_t rootIndex, double x, double y, double z)
{ {
FScopedIslandAttributionTimer attributionTimer(EIslandAttributionBucket::Root);
// Cache derived root constants per worker. The complete immutable input key keeps this // Cache derived root constants per worker. The complete immutable input key keeps this
// value-only cache safe across worlds, epochs and worker schedules. // value-only cache safe across worlds, epochs and worker schedules.
// Mettre en cache les constantes par worker; la cle complete evite tout partage d'etat. // Mettre en cache les constantes par worker; la cle complete evite tout partage d'etat.
@@ -694,6 +823,38 @@ namespace floating_island_detail
return (std::sqrt(std::max(q, 0.0)) - 1.0) * Parameters.MinimumRadius; return (std::sqrt(std::max(q, 0.0)) - 1.0) * Parameters.MinimumRadius;
} }
inline FRootLobeParameters RootLobeParametersForBounds(
const FloatingIslandRecipeV1& recipe,const FloatingIslandAnchorV1& island,
std::int32_t rootIndex)
{
constexpr std::array<std::array<double,2>,4> pattern{{
{{-0.92,-0.12}},{{0.28,0.98}},{{0.83,-0.42}},{{-0.16,-0.94}}}};
const std::uint64_t salt=0x524f4f540000ull+static_cast<std::uint64_t>(rootIndex);
const std::uint64_t roll=Roll(recipe.seed,island.stableId,salt);
const double radius=static_cast<double>(island.radiusSubvoxels);
const double rootDepth=radius*Q16ToDouble(recipe.rootDepthRatioQ16);
const double crustBottom=static_cast<double>(IslandCrustBottom(recipe,island));
const bool centralButtress=rootIndex==4;
const double orbit=centralButtress?0.0:radius*(0.17+0.07*UnitHash(roll));
const double rootX=static_cast<double>(island.centerSubvoxels[0])
+(centralButtress?0.0:pattern[static_cast<std::size_t>(rootIndex)][0]*orbit);
const double rootY=static_cast<double>(island.centerSubvoxels[1])
+(centralButtress?0.0:pattern[static_cast<std::size_t>(rootIndex)][1]*orbit);
const double shortRootVariation=UnitHash(Roll(recipe.seed,island.stableId,salt^0x4c454eull));
const double lengthRatio=centralButtress?1.0:(rootIndex==0?1.0
:Q16ToDouble(island.taperEndQ16)*(0.82+0.18*shortRootVariation));
const double lowerDepth=rootDepth*lengthRatio;
const double embedDepth=rootDepth*(centralButtress?0.22:(rootIndex==0?0.30:0.22));
const double verticalRadius=0.5*(lowerDepth+embedDepth);
const double centerZ=crustBottom+0.5*(embedDepth-lowerDepth);
const double rx=radius*(centralButtress?0.25
:0.15+0.055*UnitHash(Roll(recipe.seed,island.stableId,salt^0x5258ull)));
const double ry=radius*(centralButtress?0.25
:0.15+0.065*UnitHash(Roll(recipe.seed,island.stableId,salt^0x5259ull)));
return {rootX,rootY,centerZ,rx*rx,ry*ry,verticalRadius*verticalRadius,
std::min({rx,ry,verticalRadius})};
}
inline double ApplySurfaceDetail(const FloatingIslandRecipeV1& recipe, inline double ApplySurfaceDetail(const FloatingIslandRecipeV1& recipe,
std::uint64_t featureId, double structural, std::uint64_t featureId, double structural,
double x, double y, double z, double detailScale = 1.0) double x, double y, double z, double detailScale = 1.0)
@@ -712,6 +873,7 @@ namespace floating_island_detail
const FloatingIslandAnchorV1& island, const FloatingIslandAnchorV1& island,
double x, double y, double z) double x, double y, double z)
{ {
FScopedIslandAttributionTimer islandTimer(EIslandAttributionBucket::Island);
if (recipe.geometryVersion < 2) if (recipe.geometryVersion < 2)
{ {
const double dx = x - static_cast<double>(island.centerSubvoxels[0]); const double dx = x - static_cast<double>(island.centerSubvoxels[0]);
@@ -739,8 +901,13 @@ namespace floating_island_detail
const double radial=Column.Radial; const double radial=Column.Radial;
const double topZ = static_cast<double>(island.topZSubvoxels); const double topZ = static_cast<double>(island.topZSubvoxels);
const double radius = static_cast<double>(island.radiusSubvoxels); const double radius = static_cast<double>(island.radiusSubvoxels);
double contourOffset=0.0;
{
FScopedIslandAttributionTimer helixTimer(EIslandAttributionBucket::Helix);
contourOffset=LobeOffsetWithPhase(recipe,Column.Angle,z,Column.LobePhase);
}
const double effectiveRadius = std::max(0.0, const double effectiveRadius = std::max(0.0,
radius + LobeOffsetWithPhase(recipe,Column.Angle,z,Column.LobePhase)); radius + contourOffset);
const double edge = std::clamp(radial / std::max(effectiveRadius, 1.0), 0.0, 1.0); const double edge = std::clamp(radial / std::max(effectiveRadius, 1.0), 0.0, 1.0);
const double lowerSurface = static_cast<double>(IslandCrustBottom(recipe, island)) const double lowerSurface = static_cast<double>(IslandCrustBottom(recipe, island))
+ radius * 0.16 * edge * edge; + radius * 0.16 * edge * edge;
@@ -787,6 +954,7 @@ namespace floating_island_detail
bool* topSurface = nullptr, bool* topSurface = nullptr,
double* alongFraction = nullptr) double* alongFraction = nullptr)
{ {
FScopedIslandAttributionTimer connectorTimer(EIslandAttributionBucket::Connector);
const double ax = static_cast<double>(edge.startSubvoxels[0]); const double ax = static_cast<double>(edge.startSubvoxels[0]);
const double ay = static_cast<double>(edge.startSubvoxels[1]); const double ay = static_cast<double>(edge.startSubvoxels[1]);
const double az = static_cast<double>(edge.startSubvoxels[2]); const double az = static_cast<double>(edge.startSubvoxels[2]);
@@ -805,7 +973,37 @@ namespace floating_island_detail
const double t = std::clamp(along / length, 0.0, 1.0); const double t = std::clamp(along / length, 0.0, 1.0);
if (alongFraction) *alongFraction = t; if (alongFraction) *alongFraction = t;
const double deck = az + (static_cast<double>(edge.endSubvoxels[2]) - az) * t; const double deck = az + (static_cast<double>(edge.endSubvoxels[2]) - az) * t;
const std::uint64_t westernEdgeId = StableId(recipe.seed, 0x200000000ull,
0x425249444745ull);
const bool westernSupportConnector=edge.stableId==westernEdgeId;
if(recipe.geometryVersion>=2&&WorldAssemblyEvaluatorVersion>=183
&&westernSupportConnector)
{
const double phase=static_cast<double>(Roll(recipe.seed,edge.stableId,0x5749445448ull)
&0xffffffffull)*(2.0*Pi/4294967296.0);
const double localHalfWidth=std::max(
static_cast<double>(recipe.routeConstraints.capsuleRadiusSubvoxels+Q),
static_cast<double>(edge.halfWidthSubvoxels)
+0.48*static_cast<double>(Q)*std::sin(6.0*Pi*t+phase));
const double localThickness=static_cast<double>(edge.thicknessSubvoxels);
const double rounding=std::min({1.5*static_cast<double>(Q),
localHalfWidth*0.6,localThickness*0.45});
const double roundedX=side-(localHalfWidth-rounding);
const double roundedY=std::abs(along-0.5*length)-(0.5*length-rounding);
const double roundedZ=std::abs(z-(deck-0.5*localThickness))
-(0.5*localThickness-rounding);
const double roundedOutside=std::sqrt(
std::pow(std::max(roundedX,0.0),2)
+std::pow(std::max(roundedY,0.0),2)
+std::pow(std::max(roundedZ,0.0),2));
const double roundedInside=std::min(std::max(roundedX,
std::max(roundedY,roundedZ)),0.0);
if(topSurface)*topSurface=z-deck>=std::max(horizontal,
deck-localThickness-z);
return roundedOutside+roundedInside-rounding;
}
const double arch = recipe.geometryVersion >= 2 const double arch = recipe.geometryVersion >= 2
&& WorldAssemblyEvaluatorVersion < 172
? 2.0 * static_cast<double>(edge.thicknessSubvoxels) * (4.0 * t * (1.0 - t)) ? 2.0 * static_cast<double>(edge.thicknessSubvoxels) * (4.0 * t * (1.0 - t))
: 0.0; : 0.0;
const double upper = z - deck; const double upper = z - deck;
@@ -814,6 +1012,227 @@ namespace floating_island_detail
return std::max({horizontal, upper, lower}); return std::max({horizontal, upper, lower});
} }
struct FScalarRange
{
double minimum=0.0,maximum=0.0;
};
struct FDensityRange
{
double minimum=0.0,maximum=0.0;
};
inline double DistanceToRange(double minimum,double maximum,double value)
{
if(value<minimum)return minimum-value;
if(value>maximum)return value-maximum;
return 0.0;
}
inline double MaximumDistanceToRange(double minimum,double maximum,double value)
{
return std::max(std::abs(minimum-value),std::abs(maximum-value));
}
inline FScalarRange AffineRange(double xMinimum,double xMaximum,double xOffset,
double xScale,double yMinimum,double yMaximum,double yOffset,double yScale)
{
const double xa=(xMinimum-xOffset)*xScale,xb=(xMaximum-xOffset)*xScale;
const double ya=(yMinimum-yOffset)*yScale,yb=(yMaximum-yOffset)*yScale;
return {std::min(xa,xb)+std::min(ya,yb),
std::max(xa,xb)+std::max(ya,yb)};
}
inline FScalarRange AbsoluteRange(double minimum,double maximum)
{
const double minimumAbsolute=minimum<=0.0&&maximum>=0.0
?0.0:std::min(std::abs(minimum),std::abs(maximum));
return {minimumAbsolute,std::max(std::abs(minimum),std::abs(maximum))};
}
inline double RectangleSdf(double x,double y)
{
return std::hypot(std::max(x,0.0),std::max(y,0.0))
+std::min(std::max(x,y),0.0);
}
inline double RoundedBoxSdf(double x,double y,double z)
{
return std::sqrt(std::max(x,0.0)*std::max(x,0.0)
+std::max(y,0.0)*std::max(y,0.0)
+std::max(z,0.0)*std::max(z,0.0))
+std::min(std::max(x,std::max(y,z)),0.0);
}
inline FDensityRange RootLobeRange(const FloatingIslandRecipeV1& recipe,
const FloatingIslandAnchorV1& island,std::int32_t rootIndex,
const std::array<double,3>& minimum,const std::array<double,3>& maximum)
{
const FRootLobeParameters root=RootLobeParametersForBounds(recipe,island,rootIndex);
const double dxMinimum=DistanceToRange(minimum[0],maximum[0],root.RootX);
const double dyMinimum=DistanceToRange(minimum[1],maximum[1],root.RootY);
const double dzMinimum=DistanceToRange(minimum[2],maximum[2],root.CenterZ);
const double dxMaximum=MaximumDistanceToRange(minimum[0],maximum[0],root.RootX);
const double dyMaximum=MaximumDistanceToRange(minimum[1],maximum[1],root.RootY);
const double dzMaximum=MaximumDistanceToRange(minimum[2],maximum[2],root.CenterZ);
const double qMinimum=dxMinimum*dxMinimum/root.RadiusXSquared
+dyMinimum*dyMinimum/root.RadiusYSquared
+dzMinimum*dzMinimum/root.VerticalRadiusSquared;
const double qMaximum=dxMaximum*dxMaximum/root.RadiusXSquared
+dyMaximum*dyMaximum/root.RadiusYSquared
+dzMaximum*dzMaximum/root.VerticalRadiusSquared;
return {(std::sqrt(std::max(qMinimum,0.0))-1.0)*root.MinimumRadius,
(std::sqrt(std::max(qMaximum,0.0))-1.0)*root.MinimumRadius};
}
inline FDensityRange IslandDensityRange(const FloatingIslandRecipeV1& recipe,
const FloatingIslandAnchorV1& island,const std::array<double,3>& minimum,
const std::array<double,3>& maximum)
{
const double radius=static_cast<double>(island.radiusSubvoxels);
const double contour=static_cast<double>(recipe.contourWarpSubvoxels);
const double radialMinimum=std::hypot(
DistanceToRange(minimum[0],maximum[0],
static_cast<double>(island.centerSubvoxels[0])),
DistanceToRange(minimum[1],maximum[1],
static_cast<double>(island.centerSubvoxels[1])));
const double radialMaximum=std::max({
std::hypot(minimum[0]-island.centerSubvoxels[0],minimum[1]-island.centerSubvoxels[1]),
std::hypot(minimum[0]-island.centerSubvoxels[0],maximum[1]-island.centerSubvoxels[1]),
std::hypot(maximum[0]-island.centerSubvoxels[0],minimum[1]-island.centerSubvoxels[1]),
std::hypot(maximum[0]-island.centerSubvoxels[0],maximum[1]-island.centerSubvoxels[1])});
const double top=static_cast<double>(island.topZSubvoxels);
const double topDrop=std::max(0.0,(1.0-Q16ToDouble(recipe.topFlattenQ16))
*static_cast<double>(island.topHalfSubvoxels)*2.0);
const double topDistanceMinimum=minimum[2]-top;
const double topDistanceMaximum=maximum[2]-(top-topDrop);
const double sideDistanceMinimum=radialMinimum-std::max(0.0,radius+contour);
const double sideDistanceMaximum=recipe.geometryVersion<2
?radialMaximum:radialMaximum-std::max(0.0,radius-contour);
double lowerMinimum=0.0,lowerMaximum=0.0;
if(recipe.geometryVersion<2)
{
const double bottom=static_cast<double>(island.bottomZSubvoxels);
lowerMinimum=bottom-maximum[2];
lowerMaximum=bottom-minimum[2];
}
else
{
const double crust=static_cast<double>(IslandCrustBottom(recipe,island));
lowerMinimum=crust-maximum[2];
lowerMaximum=crust+radius*0.16-minimum[2];
}
double structuralMinimum=std::max({topDistanceMinimum,
sideDistanceMinimum,lowerMinimum});
double structuralMaximum=std::max({topDistanceMaximum,
sideDistanceMaximum,lowerMaximum});
if(recipe.geometryVersion>=2)
{
const double rootBlend=std::clamp(radius*0.12,2.0*Q,5.0*Q);
for(std::int32_t root=0;root<RootCount(recipe,island);++root)
{
const FDensityRange rootRange=RootLobeRange(recipe,island,root,minimum,maximum);
structuralMinimum=std::min(structuralMinimum,rootRange.minimum)-rootBlend*0.25;
structuralMaximum=std::min(structuralMaximum,rootRange.maximum);
}
if(recipe.geometryVersion>=5&&recipe.geometryVersion<8
&&island.ordinal==recipe.anchorIslandOrdinal
&&recipe.anchorRootBottomZSubvoxels<island.bottomZSubvoxels)
{
const double rootTop=static_cast<double>(IslandCrustBottom(recipe,island));
const double rootBottom=static_cast<double>(recipe.anchorRootBottomZSubvoxels);
const double rootRadius=static_cast<double>(recipe.anchorRootRadiusSubvoxels);
const double anchorMinimum=std::max({radialMinimum-rootRadius,
rootBottom-maximum[2],minimum[2]-rootTop});
const double anchorMaximum=std::max({radialMaximum-0.92*rootRadius,
rootBottom-minimum[2],maximum[2]-rootTop});
const double anchorDetail=0.75*static_cast<double>(recipe.roughnessSubvoxels);
const double anchorBlend=std::clamp(rootRadius*0.12,3.0*Q,6.0*Q);
structuralMinimum=std::min(structuralMinimum,anchorMinimum-anchorDetail)
-anchorBlend*0.25;
structuralMaximum=std::min(structuralMaximum,anchorMaximum+anchorDetail);
}
}
const double finalDetail=static_cast<double>(recipe.roughnessSubvoxels);
return {structuralMinimum-finalDetail,structuralMaximum+finalDetail};
}
inline FDensityRange ConnectorDensityRange(const FloatingIslandRecipeV1& recipe,
const FloatingIslandConnectorV1& edge,const std::array<double,3>& minimum,
const std::array<double,3>& maximum)
{
const double ax=static_cast<double>(edge.startSubvoxels[0]);
const double ay=static_cast<double>(edge.startSubvoxels[1]);
const double az=static_cast<double>(edge.startSubvoxels[2]);
const double dx=static_cast<double>(edge.endSubvoxels[0])-ax;
const double dy=static_cast<double>(edge.endSubvoxels[1])-ay;
const double dz=static_cast<double>(edge.endSubvoxels[2])-az;
const double length=std::hypot(dx,dy);
if(!(length>0.0))return {static_cast<double>(MaxDensity),static_cast<double>(MaxDensity)};
const double ux=dx/length,uy=dy/length;
const FScalarRange along=AffineRange(minimum[0],maximum[0],ax,ux,
minimum[1],maximum[1],ay,uy);
const FScalarRange signedSide=AffineRange(minimum[0],maximum[0],ax,-uy,
minimum[1],maximum[1],ay,ux);
const FScalarRange side=AbsoluteRange(signedSide.minimum,signedSide.maximum);
const double qxMinimum=side.minimum-static_cast<double>(edge.halfWidthSubvoxels);
const double qxMaximum=side.maximum-static_cast<double>(edge.halfWidthSubvoxels);
const FScalarRange alongCenter=AbsoluteRange(along.minimum-0.5*length,
along.maximum-0.5*length);
const double qyMinimum=alongCenter.minimum-0.5*length;
const double qyMaximum=alongCenter.maximum-0.5*length;
const double horizontalMinimum=RectangleSdf(qxMinimum,qyMinimum);
const double horizontalMaximum=RectangleSdf(qxMaximum,qyMaximum);
const double tMinimum=std::clamp(along.minimum/length,0.0,1.0);
const double tMaximum=std::clamp(along.maximum/length,0.0,1.0);
const double deckAtMinimum=az+dz*tMinimum;
const double deckAtMaximum=az+dz*tMaximum;
const double deckMinimum=std::min(deckAtMinimum,deckAtMaximum);
const double deckMaximum=std::max(deckAtMinimum,deckAtMaximum);
const std::uint64_t westernEdgeId=StableId(recipe.seed,0x200000000ull,
0x425249444745ull);
const bool westernSupportConnector=edge.stableId==westernEdgeId;
if(recipe.geometryVersion>=2&&WorldAssemblyEvaluatorVersion>=183
&&westernSupportConnector)
{
const double minimumHalfWidth=static_cast<double>(recipe.routeConstraints.capsuleRadiusSubvoxels+Q);
const double baseHalfWidth=static_cast<double>(edge.halfWidthSubvoxels);
const double halfWidthMinimum=std::max(minimumHalfWidth,baseHalfWidth-0.48*Q);
const double halfWidthMaximum=std::max(minimumHalfWidth,baseHalfWidth+0.48*Q);
const double roundingMinimum=std::min({1.5*Q,halfWidthMinimum*0.6,
static_cast<double>(edge.thicknessSubvoxels)*0.45});
const double roundingMaximum=std::min({1.5*Q,halfWidthMaximum*0.6,
static_cast<double>(edge.thicknessSubvoxels)*0.45});
const double roundedXMinimum=side.minimum-halfWidthMaximum+roundingMinimum;
const double roundedXMaximum=side.maximum-halfWidthMinimum+roundingMaximum;
const double roundedYMinimum=alongCenter.minimum-0.5*length+roundingMinimum;
const double roundedYMaximum=alongCenter.maximum-0.5*length+roundingMaximum;
const double halfThickness=0.5*static_cast<double>(edge.thicknessSubvoxels);
const double centerMinimum=deckMinimum-halfThickness;
const double centerMaximum=deckMaximum-halfThickness;
const double differenceMinimum=minimum[2]-centerMaximum;
const double differenceMaximum=maximum[2]-centerMinimum;
const FScalarRange absoluteDifference=AbsoluteRange(differenceMinimum,differenceMaximum);
const double roundedZMinimum=absoluteDifference.minimum-halfThickness+roundingMinimum;
const double roundedZMaximum=absoluteDifference.maximum-halfThickness+roundingMaximum;
return {RoundedBoxSdf(roundedXMinimum,roundedYMinimum,roundedZMinimum),
RoundedBoxSdf(roundedXMaximum,roundedYMaximum,roundedZMaximum)};
}
const double thickness=static_cast<double>(edge.thicknessSubvoxels);
const double maximumArch=recipe.geometryVersion>=2&&WorldAssemblyEvaluatorVersion<172
?2.0*thickness:0.0;
const double upperMinimum=minimum[2]-deckMaximum;
const double upperMaximum=maximum[2]-deckMinimum;
const double lowerMinimum=deckMinimum-thickness-maximumArch-maximum[2];
const double lowerMaximum=deckMaximum-thickness-minimum[2];
return {std::max({horizontalMinimum,upperMinimum,lowerMinimum}),
std::max({horizontalMaximum,upperMaximum,lowerMaximum})};
}
inline std::int64_t FieldDensity(const FloatingIslandRecipeV1& recipe, inline std::int64_t FieldDensity(const FloatingIslandRecipeV1& recipe,
const FloatingIslandPlanV1& plan, const FloatingIslandPlanV1& plan,
std::int64_t x, std::int64_t y, std::int64_t z) std::int64_t x, std::int64_t y, std::int64_t z)
@@ -1174,8 +1593,8 @@ namespace floating_island_detail
// Land each connector well inside the broad top so the full capsule footprint remains // Land each connector well inside the broad top so the full capsule footprint remains
// supported across the bridge-to-island transition, including bounded surface roughness. // supported across the bridge-to-island transition, including bounded surface roughness.
// Les connecteurs recouvrent le plateau pour soutenir toute la capsule au raccord. // Les connecteurs recouvrent le plateau pour soutenir toute la capsule au raccord.
// Keep the narrow span exposed to open sky; a smaller fraction buries most of it in the island tops. // Keep the capsule footprint supported while seating L37 connectors just below the native island tops.
// Garder la passerelle etroite visible dans le vide ; une fraction plus faible l'enfouit dans les plateaux. // Garder l'appui de la capsule tout en enfouissant les connecteurs L37 juste sous les plateaux.
const double landingFraction = recipe.geometryVersion >= 2 ? 0.52 : 0.62; const double landingFraction = recipe.geometryVersion >= 2 ? 0.52 : 0.62;
const double fromReach = static_cast<double>(from.radiusSubvoxels) * landingFraction; const double fromReach = static_cast<double>(from.radiusSubvoxels) * landingFraction;
const double toReach = static_cast<double>(to.radiusSubvoxels) * landingFraction; const double toReach = static_cast<double>(to.radiusSubvoxels) * landingFraction;
@@ -1183,7 +1602,19 @@ namespace floating_island_detail
const double sy = from.centerSubvoxels[1] + uy * fromReach; const double sy = from.centerSubvoxels[1] + uy * fromReach;
const double ex = to.centerSubvoxels[0] - ux * toReach; const double ex = to.centerSubvoxels[0] - ux * toReach;
const double ey = to.centerSubvoxels[1] - uy * toReach; const double ey = to.centerSubvoxels[1] - uy * toReach;
const double routeDeckLift = recipe.geometryVersion >= 2 ? 1.0 * Q : 0.0; const bool craterMouthConnector=WorldAssemblyEvaluatorVersion>=174
&&(from.ordinal==recipe.anchorIslandOrdinal
||to.ordinal==recipe.anchorIslandOrdinal);
const bool westernSupportConnector=WorldAssemblyEvaluatorVersion>=178
&&((from.ordinal==0&&to.ordinal==1)||(from.ordinal==1&&to.ordinal==0));
const double routeDeckLift=recipe.geometryVersion>=2
?(WorldAssemblyEvaluatorVersion>=178
?(westernSupportConnector
?(WorldAssemblyEvaluatorVersion>=181?-1.0
:(WorldAssemblyEvaluatorVersion>=180?-1.25
:(WorldAssemblyEvaluatorVersion>=179?-1.5:-1.0))):-2.0)
:(craterMouthConnector?-2.0
:(WorldAssemblyEvaluatorVersion>=172?-1.0:1.0)))*Q:0.0;
const double sz = IslandTopSurface(recipe, from, sx, sy) + routeDeckLift; const double sz = IslandTopSurface(recipe, from, sx, sy) + routeDeckLift;
const double ez = IslandTopSurface(recipe, to, ex, ey) + routeDeckLift; const double ez = IslandTopSurface(recipe, to, ex, ey) + routeDeckLift;
edge.startSubvoxels = {RoundQ(sx), RoundQ(sy), RoundQ(sz)}; edge.startSubvoxels = {RoundQ(sx), RoundQ(sy), RoundQ(sz)};
@@ -1336,6 +1767,7 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
manifest.recipe.anchorMouthOffsetSubvoxels)) manifest.recipe.anchorMouthOffsetSubvoxels))
{error="floating-island geometry version 7 is missing a passage-rooted anchor field";return std::nullopt;} {error="floating-island geometry version 7 is missing a passage-rooted anchor field";return std::nullopt;}
} }
manifest.recipe.terrainFollowingRoute=recipeValue.Get("terrain_following_route").Boolean(false);
if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema) if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema)
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash) || !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|| !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion) || !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion)
@@ -1495,6 +1927,7 @@ std::string FloatingIslandManifestV1::ToJson() const
<< ",\"anchor_island_minimum_radius_q\":" << recipe.anchorIslandMinimumRadiusSubvoxels << ",\"anchor_island_minimum_radius_q\":" << recipe.anchorIslandMinimumRadiusSubvoxels
<< ",\"anchor_root_bottom_z_q\":" << recipe.anchorRootBottomZSubvoxels << ",\"anchor_root_bottom_z_q\":" << recipe.anchorRootBottomZSubvoxels
<< ",\"anchor_mouth_offset_q\":" << recipe.anchorMouthOffsetSubvoxels; << ",\"anchor_mouth_offset_q\":" << recipe.anchorMouthOffsetSubvoxels;
if(recipe.terrainFollowingRoute)out << ",\"terrain_following_route\":true";
out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels
<< ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels << ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels
<< ",\"roughness_q\":" << recipe.roughnessSubvoxels << ",\"roughness_q\":" << recipe.roughnessSubvoxels
@@ -1746,6 +2179,26 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
const std::int64_t y = floating_island_detail::RoundQ( const std::int64_t y = floating_island_detail::RoundQ(
static_cast<double>(a.centerSubvoxels[1]) + dy * t); static_cast<double>(a.centerSubvoxels[1]) + dy * t);
std::int64_t routeX=x,routeY=y; std::int64_t routeX=x,routeY=y;
if(WorldAssemblyEvaluatorVersion>=192&&recipe.terrainFollowingRoute
&&recipe.geometryVersion>=5
&&segment+2==count&&pieces>1)
{
const auto& anchor=islands[recipe.anchorIslandOrdinal];
const std::uint32_t side=static_cast<std::uint32_t>(
floating_island_detail::UnitQ16(recipe.seed,anchor.stableId,
0x414e43484f524d50ull)&1u);
const double length=std::hypot(dx,dy);
const double bend=10.0*floating_island_detail::Q
*std::pow(std::sin(floating_island_detail::Pi*t),2.0);
const double sign=side?-1.0:1.0;
if(length>0.0)
{
routeX=floating_island_detail::RoundQ(
static_cast<double>(routeX)+sign*(-dy/length)*bend);
routeY=floating_island_detail::RoundQ(
static_cast<double>(routeY)+sign*(dx/length)*bend);
}
}
if(recipe.geometryVersion>=5&&segment+2==count&&piece==pieces) if(recipe.geometryVersion>=5&&segment+2==count&&piece==pieces)
{ {
const auto& anchor=islands[recipe.anchorIslandOrdinal]; const auto& anchor=islands[recipe.anchorIslandOrdinal];
@@ -1762,8 +2215,11 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
routeX=floating_island_detail::RoundQ(mouthX); routeX=floating_island_detail::RoundQ(mouthX);
routeY=floating_island_detail::RoundQ(mouthY); routeY=floating_island_detail::RoundQ(mouthY);
} }
const auto surface = floating_island_detail::FindCapsuleFootingTopZ( const auto surface = WorldAssemblyEvaluatorVersion>=199
recipe, manifest.plan, routeX, routeY); &&recipe.terrainFollowingRoute
?floating_island_detail::FindSurfaceZ(recipe,manifest.plan,routeX,routeY)
:floating_island_detail::FindCapsuleFootingTopZ(
recipe,manifest.plan,routeX,routeY);
if (!surface) if (!surface)
{ error = "island planner could not find a full capsule-width support surface along its connector chain"; return std::nullopt; } { error = "island planner could not find a full capsule-width support surface along its connector chain"; return std::nullopt; }
WorldAssemblyRoutePointV1 point; WorldAssemblyRoutePointV1 point;
@@ -1830,14 +2286,19 @@ bool FloatingIslandFieldV1::isValid() const noexcept
std::int64_t FloatingIslandFieldV1::densitySubvoxels(std::int64_t x, std::int64_t FloatingIslandFieldV1::densitySubvoxels(std::int64_t x,
std::int64_t y, std::int64_t z) const noexcept std::int64_t y, std::int64_t z) const noexcept
{ {
floating_island_detail::FScopedIslandAttributionTimer fieldTimer(
floating_island_detail::EIslandAttributionBucket::Field);
// The selected evaluator validates once; do not scan the plan twice per scoped sample. // The selected evaluator validates once; do not scan the plan twice per scoped sample.
// L'evaluateur choisi valide une seule fois; ne pas reparcourir le plan a chaque echantillon. // L'evaluateur choisi valide une seule fois; ne pas reparcourir le plan a chaque echantillon.
std::int64_t density=floating_island_detail::MaxDensity;
if (const auto* list = ScopedWorldAssemblyFeatureQueriesV1::find(recipe_, plan_, x, y, z)) if (const auto* list = ScopedWorldAssemblyFeatureQueriesV1::find(recipe_, plan_, x, y, z))
return densitySubvoxelsForTile(x, y, z, *list); density=densitySubvoxelsForTile(x, y, z, *list);
if (!isValid() || !floating_island_detail::ValidScalar(x) else if (!isValid() || !floating_island_detail::ValidScalar(x)
|| !floating_island_detail::ValidScalar(y) || !floating_island_detail::ValidScalar(z)) || !floating_island_detail::ValidScalar(y) || !floating_island_detail::ValidScalar(z))
return floating_island_detail::MaxDensity; density=floating_island_detail::MaxDensity;
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z); else density=floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
fieldTimer.Stop();
return density;
} }
std::vector<WorldAssemblyFeatureBoundsV1> FloatingIslandFieldV1::solidSupportBounds() const std::vector<WorldAssemblyFeatureBoundsV1> FloatingIslandFieldV1::solidSupportBounds() const
@@ -2036,19 +2497,46 @@ FieldInterval FloatingIslandFieldV1::interval(std::int64_t minX, std::int64_t mi
|| !floating_island_detail::ValidScalar(minX) || !floating_island_detail::ValidScalar(minY) || !floating_island_detail::ValidScalar(minX) || !floating_island_detail::ValidScalar(minY)
|| !floating_island_detail::ValidScalar(minZ) || !floating_island_detail::ValidScalar(maxX) || !floating_island_detail::ValidScalar(minZ) || !floating_island_detail::ValidScalar(maxX)
|| !floating_island_detail::ValidScalar(maxY) || !floating_island_detail::ValidScalar(maxZ)) return result; || !floating_island_detail::ValidScalar(maxY) || !floating_island_detail::ValidScalar(maxZ)) return result;
const std::array<std::int64_t, 3> minimum{minX, minY, minZ}; using namespace floating_island_detail;
const std::array<std::int64_t, 3> maximum{maxX, maxY, maxZ}; if (plan_->islands.empty())
for (const auto& island : plan_->islands) return {true,MaxDensity,MaxDensity};
if (floating_island_detail::Intersects(minimum, maximum,
island.influenceMinimumSubvoxels, island.influenceMaximumSubvoxels)) return result; const std::array<double,3> minimum{static_cast<double>(minX),
for (const auto& edge : plan_->clusterEdges) static_cast<double>(minY),static_cast<double>(minZ)};
if (floating_island_detail::Intersects(minimum, maximum, const std::array<double,3> maximum{static_cast<double>(maxX),
edge.influenceMinimumSubvoxels, edge.influenceMaximumSubvoxels)) return result; static_cast<double>(maxY),static_cast<double>(maxZ)};
// Root is void. A box outside every lobe/taper/blend/connector influence bound proves air. double fieldMinimum=static_cast<double>(MaxDensity);
// La racine est le vide : une boite hors des influences bornees prouve l'air. double fieldMaximum=static_cast<double>(MaxDensity);
result.known = true; for(const auto& island:plan_->islands)
result.minimumDensitySubvoxels = 0; {
result.maximumDensitySubvoxels = floating_island_detail::MaxDensity; FDensityRange group=IslandDensityRange(*recipe_,island,minimum,maximum);
for(const auto& edge:plan_->clusterEdges)
{
if(edge.fromIslandId!=island.stableId)continue;
const FDensityRange connector=ConnectorDensityRange(*recipe_,edge,minimum,maximum);
const double detail=recipe_->geometryVersion>=2
?static_cast<double>(recipe_->roughnessSubvoxels):0.0;
group.minimum=std::min(group.minimum,connector.minimum-detail)
-static_cast<double>(recipe_->blendRadiusSubvoxels)*0.25;
group.maximum=std::min(group.maximum,connector.maximum+detail);
}
fieldMinimum=std::min(fieldMinimum,group.minimum);
fieldMaximum=std::min(fieldMaximum,group.maximum);
}
if(!std::isfinite(fieldMinimum)||!std::isfinite(fieldMaximum))return result;
// These interval operations are shared by native and future generator classifiers: SDF
// intersections take max, unions take min, and smooth unions subtract at most k/4.
// Keep two subvoxels of slack for floating bounds followed by the field's integer rounding.
// Ces bornes servent les generateurs actuels et futurs : intersections=max, unions=min.
const double minimumRounded=std::clamp(std::floor(fieldMinimum)-2.0,
-static_cast<double>(MaxDensity),static_cast<double>(MaxDensity));
const double maximumRounded=std::clamp(std::ceil(fieldMaximum)+2.0,
-static_cast<double>(MaxDensity),static_cast<double>(MaxDensity));
if(minimumRounded>maximumRounded)return result;
result.known=true;
result.minimumDensitySubvoxels=static_cast<std::int64_t>(minimumRounded);
result.maximumDensitySubvoxels=static_cast<std::int64_t>(maximumRounded);
return result; return result;
} }
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+542 -7
View File
@@ -12,10 +12,12 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <set> #include <set>
#include <map>
#include <functional> #include <functional>
#include <thread> #include <thread>
#include <new> #include <new>
#include <stdexcept> #include <stdexcept>
#include <sstream>
namespace vfcore::detail namespace vfcore::detail
{ {
@@ -40,6 +42,34 @@ namespace VoxelForgeCore
namespace namespace
{ {
#if defined(VF_L30_ENABLE_COST_COUNTERS)
void PrintL30CostCounters(const FStandaloneShapeWorld::FClassificationThreadStats& stats)
{
if(stats.BoundaryBandCalls==0&&stats.ColumnZOverlapCalls==0)return;
std::printf("[L30CostTile] boundary_band_calls=%llu transition_window_checks=%llu "
"transition_window_hits=%llu transition_evaluations=%llu passage_fade_calls=%llu "
"passage_fade_mouth_tests=%llu column_z_overlap_calls=%llu column_guard_calls=%llu "
"column_guard_nonpositive_skips=%llu column_guard_radial_rejects=%llu "
"boundary_tile_cull_rejects=%llu boundary_point_cull_rejects=%llu "
"transition_passage_tile_skips=%llu "
"column_sdf_calls=%llu\n",
static_cast<unsigned long long>(stats.BoundaryBandCalls),
static_cast<unsigned long long>(stats.TransitionWindowChecks),
static_cast<unsigned long long>(stats.TransitionWindowHits),
static_cast<unsigned long long>(stats.TransitionEvaluatorCalls),
static_cast<unsigned long long>(stats.TransitionPassageFadeCalls),
static_cast<unsigned long long>(stats.TransitionPassageMouthTests),
static_cast<unsigned long long>(stats.ColumnZOverlapCalls),
static_cast<unsigned long long>(stats.ColumnGuardCalls),
static_cast<unsigned long long>(stats.ColumnGuardNonpositiveSkips),
static_cast<unsigned long long>(stats.ColumnGuardRadialRejects),
static_cast<unsigned long long>(stats.BoundaryTileCullRejects),
static_cast<unsigned long long>(stats.BoundaryPointCullRejects),
static_cast<unsigned long long>(stats.TransitionPassageTileSkips),
static_cast<unsigned long long>(stats.ColumnSignedDistanceCalls));
}
#endif
FIntVector ToNative(Vec3i value) { return {value.x,value.y,value.z}; } FIntVector ToNative(Vec3i value) { return {value.x,value.y,value.z}; }
FVector ToNative(Vec3d value) { return {value.x,value.y,value.z}; } FVector ToNative(Vec3d value) { return {value.x,value.y,value.z}; }
Vec3d FromNative(const FVector& value) { return {value.X,value.Y,value.Z}; } Vec3d FromNative(const FVector& value) { return {value.X,value.Y,value.Z}; }
@@ -188,6 +218,19 @@ namespace VoxelForgeCore
return hash; return hash;
} }
std::uint64_t HashMaterialSamples(std::span<const MeshVertex> vertices)
{
std::uint64_t hash=14695981039346656037ull;
AddWord(hash,vertices.size(),8);
for(const MeshVertex& vertex:vertices)
{
AddWord(hash,vertex.hostMaterialId,2);
for(const std::uint16_t id:vertex.resourceMaterialIds)AddWord(hash,id,2);
for(const std::uint16_t weight:vertex.resourceWeightsQ16)AddWord(hash,weight,2);
}
return hash;
}
std::uint64_t HashBrushTransaction(const EditTransaction& transaction) std::uint64_t HashBrushTransaction(const EditTransaction& transaction)
{ {
std::uint64_t hash=14695981039346656037ull; std::uint64_t hash=14695981039346656037ull;
@@ -961,6 +1004,15 @@ namespace VoxelForgeCore
const ResolvedLayoutSnapshotV3* ResolvedWorldSnapshot::resolvedLayout() const noexcept const ResolvedLayoutSnapshotV3* ResolvedWorldSnapshot::resolvedLayout() const noexcept
{ return data_?data_->resolvedLayout.get():nullptr; } { return data_?data_->resolvedLayout.get():nullptr; }
ResolvedWorldSnapshot ResolvedWorldSnapshot::withResolvedLayout(
const ResolvedLayoutSnapshotV3& layout) const
{
if(!data_)return {};
auto copy=std::make_shared<Data>(*data_);
copy->resolvedLayout=std::make_shared<ResolvedLayoutSnapshotV3>(layout);
return ResolvedWorldSnapshot(std::move(copy));
}
ResolvedWorldSnapshot ResolvedWorldSnapshot::withoutResolvedLayoutPassages() const ResolvedWorldSnapshot ResolvedWorldSnapshot::withoutResolvedLayoutPassages() const
{ {
if(!data_||!data_->resolvedLayout)return {}; if(!data_||!data_->resolvedLayout)return {};
@@ -1018,6 +1070,18 @@ namespace VoxelForgeCore
} }
}; };
struct FScopedIslandColumnCacheInstrumentation
{
floating_island_detail::FIslandColumnCacheInstrumentation* Previous=nullptr;
explicit FScopedIslandColumnCacheInstrumentation(
floating_island_detail::FIslandColumnCacheInstrumentation* Current,bool Enabled)
:Previous(Enabled?floating_island_detail::SetIslandColumnCacheInstrumentation(Current):nullptr),
bEnabled(Enabled){}
~FScopedIslandColumnCacheInstrumentation()
{if(bEnabled)floating_island_detail::SetIslandColumnCacheInstrumentation(Previous);}
bool bEnabled=false;
};
World::World(ResolvedWorldSnapshot snapshot):impl_(std::make_unique<Impl>()) World::World(ResolvedWorldSnapshot snapshot):impl_(std::make_unique<Impl>())
{ {
if(snapshot.data_) if(snapshot.data_)
@@ -1179,6 +1243,7 @@ namespace VoxelForgeCore
const double tileMaximum[3]={static_cast<double>(maximum.X), const double tileMaximum[3]={static_cast<double>(maximum.X),
static_cast<double>(maximum.Y),static_cast<double>(maximum.Z)}; static_cast<double>(maximum.Y),static_cast<double>(maximum.Z)};
for(const auto& passage:layout.passages) for(const auto& passage:layout.passages)
{
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment) for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
{ {
const auto& a=passage.controlPoints[segment-1]; const auto& a=passage.controlPoints[segment-1];
@@ -1200,6 +1265,128 @@ namespace VoxelForgeCore
overlap&=high[axis]>=tileMinimum[axis]&&low[axis]<=tileMaximum[axis]; overlap&=high[axis]>=tileMinimum[axis]&&low[axis]<=tileMaximum[axis];
if(overlap)return true; if(overlap)return true;
} }
// L27 can add a long support column around an unsupported passage span. Its
// radius can exceed the route and bridge bounds above by the column wall and
// flare allowance. The precompile shape-free proof runs before that crossing
// plan exists, so conservatively bound every possible column from the resolved
// route itself. The center is either the passage anchor or an average of route
// samples; in the latter case it lies inside the route's XY bounds.
if(passage.controlPoints.size()>=2)
{
double routeMinimum[3]={std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
double routeMaximum[3]={-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
double maximumBoreRadius=0.0;
for(const auto& point:passage.controlPoints)
{
for(int axis=0;axis<3;++axis)
{
const double value=static_cast<double>(point.positionSubvoxels[axis])/Q;
routeMinimum[axis]=std::min(routeMinimum[axis],value);
routeMaximum[axis]=std::max(routeMaximum[axis],value);
}
maximumBoreRadius=std::max(maximumBoreRadius,
static_cast<double>(point.radiusSubvoxels)/Q);
}
double centerMinimumX=routeMinimum[0],centerMaximumX=routeMaximum[0];
double centerMinimumY=routeMinimum[1],centerMaximumY=routeMaximum[1];
double maximumRouteOffset=std::hypot(
routeMaximum[0]-routeMinimum[0],routeMaximum[1]-routeMinimum[1]);
const auto upper=std::find_if(layout.strates.begin(),layout.strates.end(),
[&passage](const auto& stratum)
{return stratum.strateIndex==passage.upperStrateIndex;});
if(upper!=layout.strates.end()
&&upper->passageCapabilities.usePassageAnchorForColumns)
{
const auto anchor=std::find_if(upper->anchors.begin(),upper->anchors.end(),
[](const auto& value)
{return value.kind=="passage_anchor"&&value.values.size()>=2;});
if(anchor!=upper->anchors.end())
{
const double centerX=static_cast<double>(anchor->values[0])/Q;
const double centerY=static_cast<double>(anchor->values[1])/Q;
centerMinimumX=centerMaximumX=centerX;
centerMinimumY=centerMaximumY=centerY;
maximumRouteOffset=0.0;
for(const auto& point:passage.controlPoints)
maximumRouteOffset=std::max(maximumRouteOffset,std::hypot(
static_cast<double>(point.positionSubvoxels[0])/Q-centerX,
static_cast<double>(point.positionSubvoxels[1])/Q-centerY));
}
}
// The generated maximum column radius is the route offset, bore radius,
// and 13.25 m of wall/irregularity/flare allowance.
const double columnRadius=maximumRouteOffset+maximumBoreRadius
+13.25*voxelsPerMetre;
const double columnMinimum[3]={centerMinimumX-columnRadius,
centerMinimumY-columnRadius,routeMinimum[2]-6.0*voxelsPerMetre};
const double columnMaximum[3]={centerMaximumX+columnRadius,
centerMaximumY+columnRadius,routeMaximum[2]+5.0*voxelsPerMetre};
bool overlap=true;
for(int axis=0;axis<3;++axis)
overlap=overlap&&columnMaximum[axis]>=tileMinimum[axis]
&&columnMinimum[axis]<=tileMaximum[axis];
if(overlap)return true;
}
}
return false;
}
bool ResolvedStampTouchesBox(const ResolvedLayoutSnapshotV3& layout,
const FIntVector& minimum,const FIntVector& maximum,
std::uint32_t coreVoxelSizeMicrons)
{
if(layout.stamps.empty()||coreVoxelSizeMicrons==0)return false;
const double tileMinimum[3]={static_cast<double>(minimum.X),
static_cast<double>(minimum.Y),static_cast<double>(minimum.Z)};
const double tileMaximum[3]={static_cast<double>(maximum.X),
static_cast<double>(maximum.Y),static_cast<double>(maximum.Z)};
for(const auto& stamp:layout.stamps)
{
bool overlap=true;
for(std::size_t axis=0;axis<3;++axis)
{
const double low=static_cast<double>(stamp.boundsMinimumMicrometres[axis])
/coreVoxelSizeMicrons;
const double high=static_cast<double>(stamp.boundsMaximumMicrometres[axis])
/coreVoxelSizeMicrons;
overlap=overlap&&high>=tileMinimum[axis]&&low<=tileMaximum[axis];
}
if(overlap)return true;
}
return false;
}
bool ResolvedCavernGraphTouchesBox(const ResolvedLayoutSnapshotV3& layout,
const FIntVector& minimum,const FIntVector& maximum,
std::uint32_t coreVoxelSizeMicrons)
{
if((layout.cavernRooms.empty()&&layout.cavernEdges.empty())
||coreVoxelSizeMicrons==0)return false;
const double tileMinimum[3]={static_cast<double>(minimum.X),
static_cast<double>(minimum.Y),static_cast<double>(minimum.Z)};
const double tileMaximum[3]={static_cast<double>(maximum.X),
static_cast<double>(maximum.Y),static_cast<double>(maximum.Z)};
const auto overlaps=[&](const auto& feature)
{
bool overlap=true;
for(std::size_t axis=0;axis<3;++axis)
{
const double low=static_cast<double>(feature.boundsMinimumMicrometres[axis])
/coreVoxelSizeMicrons;
const double high=static_cast<double>(feature.boundsMaximumMicrometres[axis])
/coreVoxelSizeMicrons;
overlap=overlap&&high>=tileMinimum[axis]&&low<=tileMaximum[axis];
}
return overlap;
};
for(const auto& room:layout.cavernRooms)if(overlaps(room))return true;
for(const auto& edge:layout.cavernEdges)if(overlaps(edge))return true;
return false; return false;
} }
} }
@@ -1220,7 +1407,8 @@ namespace VoxelForgeCore
static_cast<int32>(std::clamp<std::uint32_t>(request.workerLimit,1,16)),ToNative(request.detail), static_cast<int32>(std::clamp<std::uint32_t>(request.workerLimit,1,16)),ToNative(request.detail),
request.includeFarWorld,error,&scheduler,request.roundTunnels,request.profileDetailBand, request.includeFarWorld,error,&scheduler,request.roundTunnels,request.profileDetailBand,
request.validatePassageRoutes,request.gpuVisualDetailTiles, request.validatePassageRoutes,request.gpuVisualDetailTiles,
request.cpuVisualDetailTiles,impl_->worldAssembly.get(),impl_->resolvedLayout.get()); request.cpuVisualDetailTiles,impl_->worldAssembly.get(),impl_->resolvedLayout.get(),
request.parallelSignEdgeRepair);
if(ok) if(ok)
{ {
impl_->shape=std::move(shape); impl_->shape=std::move(shape);
@@ -1247,13 +1435,31 @@ namespace VoxelForgeCore
const int32 firstSurfaceChunk=FloorDiv(boundsMin.Z,LayoutChunkWidth); const int32 firstSurfaceChunk=FloorDiv(boundsMin.Z,LayoutChunkWidth);
const int32 lastSurfaceChunk=FloorDiv(boundsMax.Z,LayoutChunkWidth); const int32 lastSurfaceChunk=FloorDiv(boundsMax.Z,LayoutChunkWidth);
const WorldAssemblyStratumPlanV1* selectedPlan=nullptr; const WorldAssemblyStratumPlanV1* selectedPlan=nullptr;
const ResolvedLayoutStratumV2* selectedGraphStratum=nullptr;
for(int32 chunkZ=firstSurfaceChunk;chunkZ<=lastSurfaceChunk;++chunkZ) for(int32 chunkZ=firstSurfaceChunk;chunkZ<=lastSurfaceChunk;++chunkZ)
{ {
if(impl_->owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,chunkZ))
!=ECaveGeneratorType::SurfaceWorld)return false;
const int32 strateIndex=impl_->owner->Manager->GetStrateIndex( const int32 strateIndex=impl_->owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(chunkZ)+0.5) static_cast<float>((static_cast<double>(chunkZ)+0.5)
*impl_->resolvedLayout->strateLayoutChunkSizeMicrometres/10000.0)); *impl_->resolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
const auto resolved=std::find_if(impl_->resolvedLayout->strates.begin(),
impl_->resolvedLayout->strates.end(),[strateIndex](const auto& stratum)
{return stratum.strateIndex==strateIndex;});
const bool nativeGraph=resolved!=impl_->resolvedLayout->strates.end()
&&HasNativeGraphField(*resolved)
&&(IsNativeCaveFieldStratum(*resolved)
?resolved->passageCapabilities.hasNativeCaveField
:resolved->passageCapabilities.hasNativeLatticeField);
const auto managerType=impl_->owner->Manager->GetGeneratorTypeForChunk(
FIntVector(0,0,chunkZ));
if(!nativeGraph&&managerType!=ECaveGeneratorType::SurfaceWorld)return false;
if(nativeGraph)
{
if(selectedGraphStratum&&selectedGraphStratum!=&*resolved)return false;
selectedGraphStratum=&*resolved;
if(selectedPlan)return false;
continue;
}
if(selectedGraphStratum)return false;
const auto found=std::find_if(impl_->worldAssembly->plan.strates.begin(), const auto found=std::find_if(impl_->worldAssembly->plan.strates.begin(),
impl_->worldAssembly->plan.strates.end(),[strateIndex,chunkZ](const auto& plan) impl_->worldAssembly->plan.strates.end(),[strateIndex,chunkZ](const auto& plan)
{return plan.strateIndex==strateIndex&&chunkZ<=plan.topChunkZ {return plan.strateIndex==strateIndex&&chunkZ<=plan.topChunkZ
@@ -1262,7 +1468,9 @@ namespace VoxelForgeCore
||(selectedPlan&&selectedPlan!=&*found))return false; ||(selectedPlan&&selectedPlan!=&*found))return false;
selectedPlan=&*found; selectedPlan=&*found;
} }
if(!selectedPlan||!Heightfield2D(impl_->worldAssembly->recipe,*selectedPlan).isValid()) if((!selectedGraphStratum&&(!selectedPlan
||!Heightfield2D(impl_->worldAssembly->recipe,*selectedPlan).isValid()))
||(selectedGraphStratum&&selectedPlan))
return false; return false;
const double coreVoxelsPerOwnerChunk=static_cast<double>( const double coreVoxelsPerOwnerChunk=static_cast<double>(
@@ -1280,10 +1488,17 @@ namespace VoxelForgeCore
tileMinimum[2]*scale}; tileMinimum[2]*scale};
const std::int64_t subMaximum[3]={tileMaximum[0]*scale,tileMaximum[1]*scale, const std::int64_t subMaximum[3]={tileMaximum[0]*scale,tileMaximum[1]*scale,
tileMaximum[2]*scale}; tileMaximum[2]*scale};
if(selectedPlan)
{
for(const WorldAssemblyFeatureV1& feature:selectedPlan->features) for(const WorldAssemblyFeatureV1& feature:selectedPlan->features)
if(IntersectsBox(feature.minimumSubvoxels,feature.maximumSubvoxels, if(IntersectsBox(feature.minimumSubvoxels,feature.maximumSubvoxels,
subMinimum,subMaximum))return false; subMinimum,subMaximum))return false;
if(RouteTouchesBox(*selectedPlan,subMinimum,subMaximum))return false; if(RouteTouchesBox(*selectedPlan,subMinimum,subMaximum))return false;
}
if(ResolvedStampTouchesBox(*impl_->resolvedLayout,boundsMin,boundsMax,
impl_->owner->CoreVoxelSizeMicrons))return false;
if(ResolvedCavernGraphTouchesBox(*impl_->resolvedLayout,boundsMin,boundsMax,
impl_->owner->CoreVoxelSizeMicrons))return false;
if(ResolvedPassageTouchesBox(*impl_->resolvedLayout,boundsMin,boundsMax, if(ResolvedPassageTouchesBox(*impl_->resolvedLayout,boundsMin,boundsMax,
impl_->owner->CoreVoxelSizeMicrons))return false; impl_->owner->CoreVoxelSizeMicrons))return false;
if(OwnerPassageTouchesBox(*impl_->owner,boundsMin,boundsMax))return false; if(OwnerPassageTouchesBox(*impl_->owner,boundsMin,boundsMax))return false;
@@ -1542,6 +1757,62 @@ namespace VoxelForgeCore
float World::densityAt(Vec3d position) const float World::densityAt(Vec3d position) const
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; } { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
MaterialSampleV1 World::materialAt(Vec3d position) const
{
if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape)
return {};
return impl_->shape->MaterialAt(static_cast<float>(position.x),
static_cast<float>(position.y),static_cast<float>(position.z));
}
OreMiningYieldV1 World::measureOreMiningYieldForSphere(Vec3d centerVoxel,
double radiusMetres,double sampleSpacingMetres) const
{
OreMiningYieldV1 result;result.sampleSpacingMetres=sampleSpacingMetres;
if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape
||!std::isfinite(radiusMetres)||!std::isfinite(sampleSpacingMetres)
||radiusMetres<=0.0||sampleSpacingMetres<0.05||sampleSpacingMetres>2.0)
return result;
const std::uint32_t voxelMicrometres=impl_->configuration.voxelSizeMicrons;
if(voxelMicrometres==0)return result;
const double voxelMetres=static_cast<double>(voxelMicrometres)/1000000.0;
const double radiusVoxels=radiusMetres/voxelMetres;
const double spacingVoxels=sampleSpacingMetres/voxelMetres;
const int steps=static_cast<int>(std::ceil(radiusMetres/sampleSpacingMetres));
const double radiusSquared=radiusMetres*radiusMetres;
std::map<std::uint16_t,std::uint64_t> weightedSamples;
for(int z=-steps;z<=steps;++z)
for(int y=-steps;y<=steps;++y)
for(int x=-steps;x<=steps;++x)
{
const double dx=x*sampleSpacingMetres;
const double dy=y*sampleSpacingMetres;
const double dz=z*sampleSpacingMetres;
const double distanceSquared=dx*dx+dy*dy+dz*dz;
if(distanceSquared>radiusSquared)continue;
const Vec3d point={centerVoxel.x+x*spacingVoxels,
centerVoxel.y+y*spacingVoxels,centerVoxel.z+z*spacingVoxels};
const float before=impl_->shape->DensityAt(static_cast<float>(point.x),
static_cast<float>(point.y),static_cast<float>(point.z));
const double carveDensity=radiusVoxels
-std::sqrt(distanceSquared)/voxelMetres;
const double after=std::max(static_cast<double>(before),carveDensity);
if(!(before<0.0f&&after>=0.0))continue;
++result.solidSamplesRemoved;
const MaterialSampleV1 material=materialAt(point);
for(std::size_t slot=0;slot<2;++slot)
if(material.resourceMaterialIds[slot]
&&material.resourceWeightsQ16[slot])
weightedSamples[material.resourceMaterialIds[slot]]
+=material.resourceWeightsQ16[slot];
}
const double sampleVolume=sampleSpacingMetres*sampleSpacingMetres
*sampleSpacingMetres;
result.resources.reserve(weightedSamples.size());
for(const auto& [id,weighted]:weightedSamples)
result.resources.push_back({id,weighted,
static_cast<double>(weighted)/65535.0*sampleVolume});
return result;
}
std::vector<PassageVoidCrossingAuditV1> World::passageVoidCrossings() const std::vector<PassageVoidCrossingAuditV1> World::passageVoidCrossings() const
{ {
std::vector<PassageVoidCrossingAuditV1> result; std::vector<PassageVoidCrossingAuditV1> result;
@@ -1742,6 +2013,10 @@ namespace VoxelForgeCore
FScopedSharedColumnCaches sharedColumnCaches(options.useSharedColumnCache FScopedSharedColumnCaches sharedColumnCaches(options.useSharedColumnCache
?impl_->sharedHeightfieldColumns.get():nullptr, ?impl_->sharedHeightfieldColumns.get():nullptr,
options.useSharedColumnCache?impl_->sharedIslandColumns.get():nullptr); options.useSharedColumnCache?impl_->sharedIslandColumns.get():nullptr);
floating_island_detail::FIslandColumnCacheInstrumentation islandCacheInstrumentation;
islandCacheInstrumentation.tileStartCycles=VoxelForgeCoreShared::ReadCpuCycles();
FScopedIslandColumnCacheInstrumentation islandCacheScope(
&islandCacheInstrumentation,options.instrumentTileDrain);
const auto& world=*impl_->owner; const auto& world=*impl_->owner;
FStandaloneShapeWorld nativeOnlyShape; FStandaloneShapeWorld nativeOnlyShape;
FIntVector nativeOnlyMinimum,nativeOnlyMaximum; FIntVector nativeOnlyMinimum,nativeOnlyMaximum;
@@ -1771,6 +2046,10 @@ namespace VoxelForgeCore
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors; mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups; mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
mesher.bStandaloneEnableGradientMemoization=options.memoizeMcGradients; mesher.bStandaloneEnableGradientMemoization=options.memoizeMcGradients;
mesher.bStandaloneEnableExactMidpointDensityMemo=options.exactMidpointDensityMemo;
mesher.bStandaloneEnableActiveLaneBatching=options.activeLaneBatching;
mesher.bStandaloneProofBlockCells=static_cast<int32>(std::clamp<std::uint32_t>(
options.proofBlockCells,1u,static_cast<std::uint32_t>(std::max(1,CHUNK_SIZE))));
mesher.bGenerateSkirts=world.bGenerateSkirts; mesher.bGenerateSkirts=world.bGenerateSkirts;
mesher.SkirtCells=static_cast<float>(world.SkirtCells) mesher.SkirtCells=static_cast<float>(world.SkirtCells)
*static_cast<float>(world.OwnerVoxelSizeMicrons) *static_cast<float>(world.OwnerVoxelSizeMicrons)
@@ -1881,6 +2160,90 @@ namespace VoxelForgeCore
int64 sampleCount=0;std::vector<float> densityValues; int64 sampleCount=0;std::vector<float> densityValues;
FVoxelMarchingCubesStageTimings McStageTimings; FVoxelMarchingCubesStageTimings McStageTimings;
TileMesh out;out.key=key; TileMesh out;out.key=key;
const auto CopyIslandCacheMetrics=[&]()
{
if(!options.instrumentTileDrain)return;
islandCacheInstrumentation.tileCycles=VoxelForgeCoreShared::ReadCpuCycles()
-islandCacheInstrumentation.tileStartCycles;
out.diagnostics.islandLocalCacheHits=islandCacheInstrumentation.localHits;
out.diagnostics.islandLocalCacheMisses=islandCacheInstrumentation.localMisses;
out.diagnostics.islandLocalCacheConflictReplacements=
islandCacheInstrumentation.localConflictReplacements;
out.diagnostics.islandSharedCacheHits=islandCacheInstrumentation.shared.hits;
out.diagnostics.islandSharedCacheMisses=islandCacheInstrumentation.shared.misses;
out.diagnostics.islandSharedCacheComputes=islandCacheInstrumentation.shared.computes;
out.diagnostics.islandSharedCacheConflictReplacements=
islandCacheInstrumentation.shared.conflictReplacements;
out.diagnostics.islandSharedCacheLockWaitNanoseconds=
islandCacheInstrumentation.shared.lockWaitNanoseconds;
const auto& a=islandCacheInstrumentation.attribution;
const auto Estimate=[](std::uint64_t cycles,std::uint64_t calls,
std::uint64_t samples)->std::uint64_t
{
return samples?static_cast<std::uint64_t>(
static_cast<long double>(cycles)*calls/samples):0;
};
constexpr std::array<const char*,5> CallerNames={
"grid","skirt","boundary","filter","other"};
constexpr std::array<const char*,10> BucketNames={
"field","island","roots","connectors","helix","column",
"local_probe","shared_cache","mouth_filter","boundary"};
std::uint64_t sampledNativeCalls=0;
for(const auto samples:a.sampledNativeCalls)sampledNativeCalls+=samples;
std::ostringstream line;
line<<"[FiFieldAttribution] key=("<<key.coordinate.x<<","<<key.coordinate.y<<","<<key.coordinate.z
<<",lod"<<key.lod<<") tile_cycles="<<islandCacheInstrumentation.tileCycles
<<" sample_period=256 native_calls_est="<<sampledNativeCalls*256
<<" native_samples="<<sampledNativeCalls;
for(std::size_t i=0;i<a.sampledNativeCalls.size();++i)
{
line<<" native_"<<CallerNames[i]<<"="<<a.sampledNativeCalls[i]*256<<"/"
<<a.sampledNativeCalls[i]<<"/"
<<Estimate(a.nativeCycles[i],a.sampledNativeCalls[i]*256,
a.sampledNativeCalls[i]);
line<<" field_"<<CallerNames[i]<<"="<<a.sampledFieldCalls[i]*256<<"/"
<<a.sampledFieldCalls[i]<<"/"
<<Estimate(a.fieldCycles[i],a.sampledFieldCalls[i]*256,
a.sampledFieldCalls[i]);
}
for(std::size_t i=0;i<BucketNames.size();++i)
line<<" comp_"<<BucketNames[i]<<"="
<<Estimate(a.exclusiveCycles[i],a.scopeSamples[i]*256,
a.scopeSamples[i])
<<"/"<<a.scopeSamples[i]<<"/"<<a.scopeSamples[i]*256;
const auto& shared=islandCacheInstrumentation.shared;
line<<" local_probe_calls="<<(islandCacheInstrumentation.localHits
+islandCacheInstrumentation.localMisses)
<<" shared_calls_est="<<shared.sampledInitialReads*128
<<" shared_initial_read="<<shared.sampledInitialReads<<"/"
<<Estimate(shared.sampledInitialReadCycles,shared.sampledInitialReads*128,
shared.sampledInitialReads)
<<" shared_locked_read="<<shared.sampledLockedReads<<"/"
<<Estimate(shared.sampledLockedReadCycles,shared.sampledLockedReads*128,
shared.sampledLockedReads)
<<" shared_lock="<<shared.sampledLocks<<"/"
<<Estimate(shared.sampledLockCycles,shared.sampledLocks*128,
shared.sampledLocks)
<<" shared_build="<<shared.sampledBuilds<<"/"
<<Estimate(shared.sampledBuildCycles,shared.sampledBuilds*128,
shared.sampledBuilds)
<<" trace_sampled="<<a.traceSampledMisses
<<" trace_seen_before="<<a.traceSeenBefore
<<" trace_evictions="<<a.traceEvictions
<<" trace_reused_evictions="<<a.traceReusedEvictions
<<" trace_events="<<a.traceCount;
std::printf("%s\n",line.str().c_str());
for(std::size_t i=0;i<a.traceCount;++i)
std::printf("[FiReuseTrace] tile=(%d,%d,%d,lod%d) set=%llu island=%llu xy=(%lld,%lld) evicted_island=%llu evicted_xy=(%lld,%lld) flags=%u\n",
key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod,
static_cast<unsigned long long>(a.traceSet[i]),
static_cast<unsigned long long>(a.traceIsland[i]),
static_cast<long long>(a.traceX[i]),static_cast<long long>(a.traceY[i]),
static_cast<unsigned long long>(a.traceEvictedIsland[i]),
static_cast<long long>(a.traceEvictedX[i]),
static_cast<long long>(a.traceEvictedY[i]),
static_cast<unsigned>(a.traceFlags[i]));
};
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution; out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
if(options.profileMesherStages) if(options.profileMesherStages)
{ {
@@ -1917,14 +2280,43 @@ namespace VoxelForgeCore
bHasNativeSurfaceHorizonPlan=bHasNativeSurfaceHorizonPlan bHasNativeSurfaceHorizonPlan=bHasNativeSurfaceHorizonPlan
||(TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ); ||(TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ);
} }
constexpr int64 IslandQueryPad=2*WorldAssemblySubvoxelsPerVoxel;
const std::array<int64,3> TileMinimum={
(static_cast<int64>(origin.X)*WorldAssemblySubvoxelsPerVoxel)-IslandQueryPad,
(static_cast<int64>(origin.Y)*WorldAssemblySubvoxelsPerVoxel)-IslandQueryPad,
(static_cast<int64>(origin.Z)*WorldAssemblySubvoxelsPerVoxel)-IslandQueryPad};
const std::array<int64,3> TileMaximum={
((static_cast<int64>(origin.X)+extent)*WorldAssemblySubvoxelsPerVoxel)+IslandQueryPad,
((static_cast<int64>(origin.Y)+extent)*WorldAssemblySubvoxelsPerVoxel)+IslandQueryPad,
((static_cast<int64>(origin.Z)+extent)*WorldAssemblySubvoxelsPerVoxel)+IslandQueryPad};
for(const auto& Entry:impl_->worldAssembly->floatingIslands) for(const auto& Entry:impl_->worldAssembly->floatingIslands)
{ {
const int64 PlanMinimumZ=static_cast<int64>(std::min(Entry.bottomChunkZ,Entry.topChunkZ)) const int64 PlanMinimumZ=static_cast<int64>(std::min(Entry.bottomChunkZ,Entry.topChunkZ))
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel; *impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Entry.bottomChunkZ,Entry.topChunkZ)+1)) const int64 PlanMaximumZ=(static_cast<int64>(std::max(Entry.bottomChunkZ,Entry.topChunkZ)+1))
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel; *impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
if(TileMinimumZ>PlanMaximumZ||TileMaximumZ<PlanMinimumZ)continue;
const auto IntersectsTile=[&](const auto& Feature)
{
for(std::size_t Axis=0;Axis<3;++Axis)
if(Feature.influenceMinimumSubvoxels[Axis]>TileMaximum[Axis]
||Feature.influenceMaximumSubvoxels[Axis]<TileMinimum[Axis])return false;
return true;
};
bool bIslandFeatureOverlap=std::any_of(Entry.manifest.plan.islands.begin(),
Entry.manifest.plan.islands.end(),IntersectsTile)
||std::any_of(Entry.manifest.plan.clusterEdges.begin(),
Entry.manifest.plan.clusterEdges.end(),IntersectsTile);
const auto& Recipe=Entry.manifest.recipe;
const int64 Bottom=Recipe.layoutMinimumZSubvoxels;
const int64 Top=Recipe.layoutMaximumZSubvoxels;
const int64 Seal=Recipe.boundarySealSubvoxels;
const bool bBoundarySealOverlap=(TileMinimum[2]<=Top&&TileMaximum[2]>=Top-Seal)
||(Recipe.geometryVersion<3&&TileMinimum[2]<Bottom+Seal
&&TileMaximum[2]>=Bottom);
bHasIslandPlanInTile=bHasIslandPlanInTile bHasIslandPlanInTile=bHasIslandPlanInTile
||(TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ); ||bIslandFeatureOverlap||bBoundarySealOverlap;
} }
} }
const bool bUseNativeSurfaceHorizon=key.lod>=5&&!bOverlapsPlayerEdit const bool bUseNativeSurfaceHorizon=key.lod>=5&&!bOverlapsPlayerEdit
@@ -1995,6 +2387,9 @@ namespace VoxelForgeCore
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0, out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
Counts.Milliseconds-classificationColumnComputeMilliseconds); Counts.Milliseconds-classificationColumnComputeMilliseconds);
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds; out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
#if defined(VF_L30_ENABLE_COST_COUNTERS)
PrintL30CostCounters(Counts);
#endif
if(options.captureColumnCoordinates) if(options.captureColumnCoordinates)
{ {
out.diagnostics.columnEvaluationWorkerHash=static_cast<std::uint64_t>( out.diagnostics.columnEvaluationWorkerHash=static_cast<std::uint64_t>(
@@ -2002,6 +2397,7 @@ namespace VoxelForgeCore
out.diagnostics.columnCoordinateCaptureComplete=Columns.coordinateCaptureComplete; out.diagnostics.columnCoordinateCaptureComplete=Columns.coordinateCaptureComplete;
out.diagnostics.evaluatedColumnCoordinates=Columns.evaluatedCoordinates; out.diagnostics.evaluatedColumnCoordinates=Columns.evaluatedCoordinates;
} }
CopyIslandCacheMetrics();
out.densitySampleCount=0; out.densitySampleCount=0;
return out; return out;
} }
@@ -2094,6 +2490,88 @@ namespace VoxelForgeCore
RelevantMinimum=std::min(RelevantMinimum,FeatureMinimum); RelevantMinimum=std::min(RelevantMinimum,FeatureMinimum);
RelevantMaximum=std::max(RelevantMaximum,FeatureMaximum); RelevantMaximum=std::max(RelevantMaximum,FeatureMaximum);
} }
// A resolved passage void column can add solid support above or
// below the native heightfield's local Z range. Its finite radial
// and vertical bounds must participate before the mesh band is clipped.
for(const auto& Crossing:tileShape.GetPassageVoidCrossings())
{
if(!Crossing.needsColumn||Crossing.maximumRadiusSubvoxels<=0
||Crossing.topZSubvoxels<=Crossing.bottomZSubvoxels)continue;
const double Radius=static_cast<double>(Crossing.maximumRadiusSubvoxels)
/static_cast<double>(SubvoxelScale);
const double CenterX=static_cast<double>(Crossing.centerXSubvoxels)
/static_cast<double>(SubvoxelScale);
const double CenterY=static_cast<double>(Crossing.centerYSubvoxels)
/static_cast<double>(SubvoxelScale);
if(CenterX+Radius<TileMinimum[0]/static_cast<double>(SubvoxelScale)
||CenterX-Radius>TileMaximum[0]/static_cast<double>(SubvoxelScale)
||CenterY+Radius<TileMinimum[1]/static_cast<double>(SubvoxelScale)
||CenterY-Radius>TileMaximum[1]/static_cast<double>(SubvoxelScale))continue;
RelevantMinimum=std::min(RelevantMinimum,
static_cast<double>(Crossing.bottomZSubvoxels)
/static_cast<double>(SubvoxelScale));
RelevantMaximum=std::max(RelevantMaximum,
static_cast<double>(Crossing.topZSubvoxels)
/static_cast<double>(SubvoxelScale));
}
// The native heightfield band only covers the owner terrain and its
// manifest features. Resolved SDF stamps can carve below that band or
// add a floor there, so include every overlapping stamp's bounded Z
// support before clipping marching-cubes samples.
if(impl_->resolvedLayout&&impl_->owner->CoreVoxelSizeMicrons>0)
{
const double MicronsToVoxels=1.0
/static_cast<double>(impl_->owner->CoreVoxelSizeMicrons);
const double TileMinimumX=static_cast<double>(origin.X-step);
const double TileMinimumY=static_cast<double>(origin.Y-step);
const double TileMaximumX=static_cast<double>(origin.X+extent+step);
const double TileMaximumY=static_cast<double>(origin.Y+extent+step);
for(const auto& Stamp:impl_->resolvedLayout->stamps)
{
if(Stamp.strateIndex!=PlanIt->strateIndex)continue;
const double MinimumX=Stamp.boundsMinimumMicrometres[0]
*MicronsToVoxels;
const double MaximumX=Stamp.boundsMaximumMicrometres[0]
*MicronsToVoxels;
const double MinimumY=Stamp.boundsMinimumMicrometres[1]
*MicronsToVoxels;
const double MaximumY=Stamp.boundsMaximumMicrometres[1]
*MicronsToVoxels;
if(MaximumX<TileMinimumX||MinimumX>TileMaximumX
||MaximumY<TileMinimumY||MinimumY>TileMaximumY)continue;
const double MinimumZ=Stamp.boundsMinimumMicrometres[2]
*MicronsToVoxels;
const double MaximumZ=Stamp.boundsMaximumMicrometres[2]
*MicronsToVoxels;
if(MaximumZ<TileMinimumZ||MinimumZ>TileMaximumZ)continue;
RelevantMinimum=std::min(RelevantMinimum,MinimumZ);
RelevantMaximum=std::max(RelevantMaximum,MaximumZ);
}
const auto includeCavernBounds=[&](const auto& Feature)
{
const double MinimumX=Feature.boundsMinimumMicrometres[0]
*MicronsToVoxels;
const double MaximumX=Feature.boundsMaximumMicrometres[0]
*MicronsToVoxels;
const double MinimumY=Feature.boundsMinimumMicrometres[1]
*MicronsToVoxels;
const double MaximumY=Feature.boundsMaximumMicrometres[1]
*MicronsToVoxels;
if(MaximumX<TileMinimumX||MinimumX>TileMaximumX
||MaximumY<TileMinimumY||MinimumY>TileMaximumY)return;
const double MinimumZ=Feature.boundsMinimumMicrometres[2]
*MicronsToVoxels;
const double MaximumZ=Feature.boundsMaximumMicrometres[2]
*MicronsToVoxels;
if(MaximumZ<TileMinimumZ||MinimumZ>TileMaximumZ)return;
RelevantMinimum=std::min(RelevantMinimum,MinimumZ);
RelevantMaximum=std::max(RelevantMaximum,MaximumZ);
};
for(const auto& Room:impl_->resolvedLayout->cavernRooms)
includeCavernBounds(Room);
for(const auto& Edge:impl_->resolvedLayout->cavernEdges)
includeCavernBounds(Edge);
}
if(std::isfinite(RelevantMinimum)&&std::isfinite(RelevantMaximum)) if(std::isfinite(RelevantMinimum)&&std::isfinite(RelevantMaximum))
{ {
const int32 NativeBandMinimum=FMath::FloorToInt(RelevantMinimum)-step; const int32 NativeBandMinimum=FMath::FloorToInt(RelevantMinimum)-step;
@@ -2211,9 +2689,29 @@ namespace VoxelForgeCore
out.diagnostics.marchingCubesOtherMilliseconds=McStageTimings.OtherMilliseconds; out.diagnostics.marchingCubesOtherMilliseconds=McStageTimings.OtherMilliseconds;
out.diagnostics.marchingCubesGradientFallbackDensityCalls= out.diagnostics.marchingCubesGradientFallbackDensityCalls=
McStageTimings.GradientFallbackDensityCalls; McStageTimings.GradientFallbackDensityCalls;
out.diagnostics.marchingCubesGradientCalls=McStageTimings.GradientCalls;
out.diagnostics.marchingCubesEdgeMidpointDensityCalls= out.diagnostics.marchingCubesEdgeMidpointDensityCalls=
McStageTimings.EdgeMidpointDensityCalls; McStageTimings.EdgeMidpointDensityCalls;
out.diagnostics.marchingCubesEdgeMidpointDensityMemoHits=
McStageTimings.EdgeMidpointDensityMemoHits;
out.diagnostics.marchingCubesGradientMemoHits=McStageTimings.GradientMemoHits; out.diagnostics.marchingCubesGradientMemoHits=McStageTimings.GradientMemoHits;
out.diagnostics.marchingCubesX8DensityCalls=McStageTimings.X8DensityCalls;
out.diagnostics.marchingCubesX8ActiveLanes=McStageTimings.X8ActiveLanes;
out.diagnostics.marchingCubesCandidateCells=McStageTimings.CandidateCells;
out.diagnostics.marchingCubesZBandRejectedCells=McStageTimings.ZBandRejectedCells;
out.diagnostics.marchingCubesCellClassificationCalls=
McStageTimings.CellClassificationCalls;
out.diagnostics.marchingCubesSurfaceCellCalls=McStageTimings.SurfaceCellCalls;
out.diagnostics.marchingCubesProofSkippedSolidCells=
McStageTimings.ProofSkippedSolidCells;
out.diagnostics.marchingCubesProofSkippedAirCells=
McStageTimings.ProofSkippedAirCells;
out.diagnostics.marchingCubesProofSkippedDensitySamples=
McStageTimings.ProofSkippedDensitySamples;
out.diagnostics.marchingCubesSkirtEdgeCandidates=McStageTimings.SkirtEdgeCandidates;
out.diagnostics.marchingCubesSkirtBoundaryCandidates=
McStageTimings.SkirtBoundaryCandidates;
out.diagnostics.marchingCubesSkirtDensityProbes=McStageTimings.SkirtDensityProbes;
} }
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>( const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshingStart).count(); std::chrono::steady_clock::now()-MeshingStart).count();
@@ -2356,6 +2854,9 @@ namespace VoxelForgeCore
if(bHasWorldAssembly) if(bHasWorldAssembly)
{ {
const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats(); const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats();
#if defined(VF_L30_ENABLE_COST_COUNTERS)
PrintL30CostCounters(Counts);
#endif
const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats(); const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
out.diagnostics.fieldCalls=Columns.pointCalls; out.diagnostics.fieldCalls=Columns.pointCalls;
out.diagnostics.floatFieldCalls=Counts.NativeFloatFieldCalls; out.diagnostics.floatFieldCalls=Counts.NativeFloatFieldCalls;
@@ -2408,6 +2909,21 @@ namespace VoxelForgeCore
out.skirtFaceMasks.assign(mesh.TriangleSkirtFaceMasks.begin(), out.skirtFaceMasks.assign(mesh.TriangleSkirtFaceMasks.begin(),
mesh.TriangleSkirtFaceMasks.end()); mesh.TriangleSkirtFaceMasks.end());
out.skirtFaceMasks.resize(mesh.Triangles.Num()/3,0); out.skirtFaceMasks.resize(mesh.Triangles.Num()/3,0);
constexpr std::int64_t MaterialSubvoxelsPerVoxel=WorldAssemblySubvoxelsPerVoxel;
const std::array<std::int64_t,3> materialTileMinimum={
(static_cast<std::int64_t>(origin.X)-step)*MaterialSubvoxelsPerVoxel,
(static_cast<std::int64_t>(origin.Y)-step)*MaterialSubvoxelsPerVoxel,
(static_cast<std::int64_t>(origin.Z)-step)*MaterialSubvoxelsPerVoxel};
const std::array<std::int64_t,3> materialTileMaximum={
(static_cast<std::int64_t>(origin.X)+extent+step)*MaterialSubvoxelsPerVoxel,
(static_cast<std::int64_t>(origin.Y)+extent+step)*MaterialSubvoxelsPerVoxel,
(static_cast<std::int64_t>(origin.Z)+extent+step)*MaterialSubvoxelsPerVoxel};
const auto materialCandidates=tileShape.OreVeinTileCandidates(
materialTileMinimum,materialTileMaximum);
out.diagnostics.materialTileOutsideEveryVein=materialCandidates.empty();
const double geometryVoxelSizeCm=static_cast<double>(world.CoreVoxelSizeMicrons)/10000.0;
std::uint64_t materialQueries=0,materialCandidateChecks=0;
const auto materialQueryStart=std::chrono::steady_clock::now();
out.vertices.reserve(static_cast<std::size_t>(mesh.Vertices.Num())); out.vertices.reserve(static_cast<std::size_t>(mesh.Vertices.Num()));
for(int32 i=0;i<mesh.Vertices.Num();++i) for(int32 i=0;i<mesh.Vertices.Num();++i)
{ {
@@ -2415,9 +2931,25 @@ namespace VoxelForgeCore
const FVector normal=i<mesh.Normals.Num()?mesh.Normals[i]:FVector::ZeroVector; const FVector normal=i<mesh.Normals.Num()?mesh.Normals[i]:FVector::ZeroVector;
const FVector2f uv=i<mesh.UVs.Num()?mesh.UVs[i]:FVector2f(0.0f,0.0f); const FVector2f uv=i<mesh.UVs.Num()?mesh.UVs[i]:FVector2f(0.0f,0.0f);
const FColor color=i<mesh.Colors.Num()?mesh.Colors[i]:FColor(0,0,0,0); const FColor color=i<mesh.Colors.Num()?mesh.Colors[i]:FColor(0,0,0,0);
out.vertices.push_back({{p.X,p.Y,p.Z},{normal.X,normal.Y,normal.Z},{uv.X,uv.Y}, MaterialSampleV1 material;
{color.R,color.G,color.B,color.A}}); if(!materialCandidates.empty())
{
++materialQueries;
material=tileShape.EvaluateMaterialAt(
static_cast<float>(p.X/geometryVoxelSizeCm),
static_cast<float>(p.Y/geometryVoxelSizeCm),
static_cast<float>(p.Z/geometryVoxelSizeCm),materialCandidates,
&materialCandidateChecks);
} }
out.vertices.push_back({{p.X,p.Y,p.Z},{normal.X,normal.Y,normal.Z},{uv.X,uv.Y},
{color.R,color.G,color.B,color.A},material.hostMaterialId,
material.resourceMaterialIds,material.resourceWeightsQ16});
}
out.diagnostics.materialQueries=materialQueries;
out.diagnostics.materialCandidateChecks=materialCandidateChecks;
out.diagnostics.materialQueryMilliseconds=materialQueries
?std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-materialQueryStart).count():0.0;
out.indices.reserve(static_cast<std::size_t>(mesh.Triangles.Num())); out.indices.reserve(static_cast<std::size_t>(mesh.Triangles.Num()));
for(const int32 index:mesh.Triangles)out.indices.push_back(static_cast<std::uint32_t>(index)); for(const int32 index:mesh.Triangles)out.indices.push_back(static_cast<std::uint32_t>(index));
if(options.captureDensitySamples)out.debugDensitySamples=std::move(densityValues); if(options.captureDensitySamples)out.debugDensitySamples=std::move(densityValues);
@@ -2429,6 +2961,7 @@ namespace VoxelForgeCore
{ densityHash^=std::bit_cast<std::uint32_t>(value);densityHash*=1099511628211ull; } { densityHash^=std::bit_cast<std::uint32_t>(value);densityHash*=1099511628211ull; }
out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh); out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh);
out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh); out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh);
out.materialHash=HashMaterialSamples(out.vertices);
if(key.lod>=4) if(key.lod>=4)
{ {
out.meshHash=SaltFarHorizonHash(out.meshHash,options.farHorizonVersion); out.meshHash=SaltFarHorizonHash(out.meshHash,options.farHorizonVersion);
@@ -2436,9 +2969,11 @@ namespace VoxelForgeCore
out.topologyHash=SaltFarHorizonHash(out.topologyHash,options.farHorizonVersion); out.topologyHash=SaltFarHorizonHash(out.topologyHash,options.farHorizonVersion);
} }
out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true; out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true;
out.hasMaterialHash=true;
} }
out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration<double,std::milli>( out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshAssemblyStart).count(); std::chrono::steady_clock::now()-MeshAssemblyStart).count();
CopyIslandCacheMetrics();
return out; return out;
} }
@@ -331,6 +331,28 @@ VF_FIELD_INLINE float VF_FieldPerlin3D(float x,float y,float z)
return VF_FieldPerlinLerp(y0,y1,sw); return VF_FieldPerlinLerp(y0,y1,sw);
} }
VF_FIELD_INLINE float VF_FieldPassageWallRelief(float x,float y,float z,VF_FieldU32 seed)
{
// Use long, non-commensurate rotated scales. The shared passage wall keeps broad,
// uneven rock relief without a short wavelength that can read as repeated rings.
const float broad=VF_FieldValueNoise(x+17.0f,y-23.0f,z+11.0f,43,
seed^0xB5297A4Du);
const float mid=VF_FieldValueNoise(y-7.0f,z+19.0f,x-31.0f,29,
seed^0x63D83595u);
const float fine=VF_FieldValueNoise(z+5.0f,x+29.0f,y-13.0f,19,
seed^0xA511E9B3u);
return 0.50f*broad+0.35f*mid+0.15f*fine;
}
VF_FIELD_INLINE float VF_FieldPassageGroundGrain(float x,float y,VF_FieldU32 seed)
{
const float broad=VF_FieldValueNoise(x+3.25f,y-7.5f,0.0f,8,
seed^0xC2B2AE35u);
const float fine=VF_FieldValueNoise(y+11.75f,x-5.0f,0.0f,5,
seed^0x27D4EB2Fu);
return 0.65f*broad+0.35f*fine;
}
VF_FIELD_INLINE float VF_FieldOwnerFractal(float x,float y,float z,VF_FieldI32 octaves, VF_FIELD_INLINE float VF_FieldOwnerFractal(float x,float y,float z,VF_FieldI32 octaves,
VF_FieldI32 mode) VF_FieldI32 mode)
{ {
+563 -4
View File
@@ -145,6 +145,12 @@ namespace world_assembly_detail
}; };
constexpr std::int64_t Q16 = 1ll << 16; constexpr std::int64_t Q16 = 1ll << 16;
constexpr std::int64_t Q32 = 1ll << 32;
constexpr std::int64_t SurfaceProfileSubvoxelsPerMetre =
WorldAssemblySubvoxelsPerVoxel * 4;
constexpr std::int64_t SurfaceProfileMinimumClearanceSubvoxels = 2253;
constexpr std::int64_t SurfaceProfileFightClearanceSubvoxels =
8 * SurfaceProfileSubvoxelsPerMetre;
constexpr std::int64_t MinimumCliffShelfThicknessSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel; constexpr std::int64_t MinimumCliffShelfThicknessSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll; constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll;
constexpr std::size_t MaxBiomeCount = 8; constexpr std::size_t MaxBiomeCount = 8;
@@ -228,6 +234,114 @@ namespace world_assembly_detail
inline bool OutsideRange(std::int64_t value) inline bool OutsideRange(std::int64_t value)
{ return value < -MaxSerializedMagnitude || value > MaxSerializedMagnitude; } { return value < -MaxSerializedMagnitude || value > MaxSerializedMagnitude; }
inline bool IsNaturalSurfaceProfile(const WorldAssemblySurfaceProfileV1& profile)
{ return profile.version == 0 && profile == WorldAssemblySurfaceProfileV1{}; }
inline bool ValidateSurfaceProfile(const WorldAssemblySurfaceProfileV1& profile,
std::uint32_t seed, std::string& error)
{
if (profile.version == 0)
{
if (!IsNaturalSurfaceProfile(profile))
{ error = "natural SurfaceWorld profile must use the all-zero default payload"; return false; }
return true;
}
if (profile.version != 1
|| !ValidScalar(profile.h0Subvoxels)
|| !ValidScalar(profile.amplitudeXSubvoxels)
|| !ValidScalar(profile.amplitudeYSubvoxels)
|| !ValidScalar(profile.wavelengthXSubvoxels)
|| !ValidScalar(profile.wavelengthYSubvoxels)
|| !ValidScalar(profile.mirrorLambdaQ16)
|| !ValidScalar(profile.minimumArenaClearanceSubvoxels)
|| profile.minimumArenaClearanceSubvoxels < 0
|| profile.minimumArenaClearanceSubvoxels > 32 * SurfaceProfileSubvoxelsPerMetre)
{ error = "SurfaceWorld profile version or fixed-scale value is outside its supported range"; return false; }
constexpr std::int64_t MinimumPeriodAmplitude = SurfaceProfileSubvoxelsPerMetre / 2;
constexpr std::int64_t MaximumPeriodicAmplitude = 3 * SurfaceProfileSubvoxelsPerMetre;
constexpr std::int64_t MaximumMirrorAmplitude = 2 * SurfaceProfileSubvoxelsPerMetre;
constexpr std::int64_t MinimumWavelength = 80 * SurfaceProfileSubvoxelsPerMetre;
constexpr std::int64_t MaximumWavelength = 160 * SurfaceProfileSubvoxelsPerMetre;
const bool hasArenaClearance = profile.minimumArenaClearanceSubvoxels == 0
|| profile.minimumArenaClearanceSubvoxels >= SurfaceProfileFightClearanceSubvoxels;
if (!hasArenaClearance)
{ error = "SurfaceWorld arena clearance must be omitted or at least eight metres"; return false; }
if (profile.kind == WorldAssemblySurfaceProfileKindV1::periodicHills
|| profile.kind == WorldAssemblySurfaceProfileKindV1::mirroredRelief)
{
const std::int64_t maximumAmplitude =
profile.kind == WorldAssemblySurfaceProfileKindV1::periodicHills
? MaximumPeriodicAmplitude : MaximumMirrorAmplitude;
if (profile.amplitudeXSubvoxels < MinimumPeriodAmplitude
|| profile.amplitudeYSubvoxels < MinimumPeriodAmplitude
|| profile.amplitudeXSubvoxels > maximumAmplitude
|| profile.amplitudeYSubvoxels > maximumAmplitude
|| profile.wavelengthXSubvoxels < MinimumWavelength
|| profile.wavelengthYSubvoxels < MinimumWavelength
|| profile.wavelengthXSubvoxels > MaximumWavelength
|| profile.wavelengthYSubvoxels > MaximumWavelength)
{ error = "periodic SurfaceWorld amplitudes or wavelengths are outside the R20 metre ranges"; return false; }
// Round every gradient component upward before applying the 15-degree bound.
// This makes the integer plan-time check conservative around division boundaries.
constexpr std::int64_t TwoPiQ16Ceiling = 411775;
constexpr std::int64_t Tan15Q16Floor = 17560;
const auto component = [](std::int64_t amplitude, std::int64_t wavelength)
{
const std::int64_t numerator = TwoPiQ16Ceiling * amplitude;
return (numerator + wavelength - 1) / wavelength;
};
const std::int64_t gx = component(profile.amplitudeXSubvoxels,
profile.wavelengthXSubvoxels);
const std::int64_t gy = component(profile.amplitudeYSubvoxels,
profile.wavelengthYSubvoxels);
if (gx * gx + gy * gy > Tan15Q16Floor * Tan15Q16Floor)
{ error = "periodic SurfaceWorld maximum walk grade exceeds tan(15 degrees)"; return false; }
if (profile.kind == WorldAssemblySurfaceProfileKindV1::periodicHills)
{
if (profile.mirrorLambdaQ16 != 0
|| profile.phaseXTurnsQ32 != WorldAssemblySurfaceProfileSeededPhaseQ32(seed, 0)
|| profile.phaseYTurnsQ32 != WorldAssemblySurfaceProfileSeededPhaseQ32(seed, 1))
{ error = "periodic-hill phases must be the stable seed-derived Q0.32 phases"; return false; }
}
else if (profile.mirrorLambdaQ16 < 49152 || profile.mirrorLambdaQ16 > Q16
|| profile.phaseXTurnsQ32 != WorldAssemblySurfaceProfileSeededPhaseQ32(seed, 0)
|| profile.phaseYTurnsQ32 != WorldAssemblySurfaceProfileSeededPhaseQ32(seed, 1))
{ error = "mirrored-relief lambda or seeded phases are outside their supported range"; return false; }
}
else if (profile.kind == WorldAssemblySurfaceProfileKindV1::flatField)
{
if (profile.amplitudeXSubvoxels != 0 || profile.amplitudeYSubvoxels != 0
|| profile.wavelengthXSubvoxels != 0 || profile.wavelengthYSubvoxels != 0
|| profile.phaseXTurnsQ32 != 0 || profile.phaseYTurnsQ32 != 0
|| profile.mirrorLambdaQ16 != 0)
{ error = "flatField must not carry periodic or mirrored relief parameters"; return false; }
}
else
{ error = "SurfaceWorld profile kind is unsupported"; return false; }
return true;
}
inline void HashSurfaceProfile(std::uint64_t& hash,
const WorldAssemblySurfaceProfileV1& profile)
{
HashWord(hash, 0x5355524650524f31ull, 8);
HashWord(hash, profile.version, 4);
HashWord(hash, static_cast<std::uint32_t>(profile.kind), 4);
HashWord(hash, static_cast<std::uint64_t>(profile.h0Subvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(profile.amplitudeXSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(profile.amplitudeYSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(profile.wavelengthXSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(profile.wavelengthYSubvoxels), 8);
HashWord(hash, profile.phaseXTurnsQ32, 4);
HashWord(hash, profile.phaseYTurnsQ32, 4);
HashWord(hash, static_cast<std::uint64_t>(profile.mirrorLambdaQ16), 8);
HashWord(hash, static_cast<std::uint64_t>(profile.minimumArenaClearanceSubvoxels), 8);
}
inline bool ValidateRecipe(const WorldAssemblyRecipeV1& recipe, std::string& error) inline bool ValidateRecipe(const WorldAssemblyRecipeV1& recipe, std::string& error)
{ {
if (recipe.schemaVersion != WorldAssemblySchemaVersion if (recipe.schemaVersion != WorldAssemblySchemaVersion
@@ -296,16 +410,57 @@ namespace world_assembly_detail
|| recipe.biomeClimateMoistureWavelengthSubvoxels <= 0 || recipe.biomeClimateMoistureWavelengthSubvoxels <= 0
|| recipe.biomeClimateReliefContrastQ16 < 0)) || recipe.biomeClimateReliefContrastQ16 < 0))
{ error = "world assembly climate parameters are outside the supported fixed-scale range"; return false; } { error = "world assembly climate parameters are outside the supported fixed-scale range"; return false; }
if (!ValidateSurfaceProfile(recipe.surfaceProfile, recipe.seed, error)) return false;
return true;
}
inline bool ValidateSurfaceProfileGeometry(const WorldAssemblySurfaceProfileV1& profile,
const WorldAssemblyStratumPlanV1& stratum, std::int64_t layoutChunkWidthSubvoxels,
std::string& error)
{
if (profile.version == 0) return true;
const std::int64_t relief = profile.amplitudeXSubvoxels + profile.amplitudeYSubvoxels;
const std::int64_t groundMinimum = profile.h0Subvoxels - relief - 2;
const std::int64_t groundMaximum = profile.h0Subvoxels + relief + 2;
std::int64_t capSwing = 0;
if (profile.kind == WorldAssemblySurfaceProfileKindV1::mirroredRelief)
{
const std::int64_t product = relief * profile.mirrorLambdaQ16;
capSwing = (product + Q16 - 1) / Q16 + 2;
}
const std::int64_t capMinimum = stratum.capHeightSubvoxels - capSwing;
const std::int64_t capMaximum = stratum.capHeightSubvoxels + capSwing;
const std::int64_t clearance = capMinimum - groundMaximum;
const std::int64_t requiredClearance = std::max(
SurfaceProfileMinimumClearanceSubvoxels,
profile.minimumArenaClearanceSubvoxels);
if (clearance < requiredClearance)
{ error = "SurfaceWorld profile does not preserve its required floor-to-cap clearance"; return false; }
const std::int64_t stratumMinimum = static_cast<std::int64_t>(stratum.bottomChunkZ)
* layoutChunkWidthSubvoxels;
const std::int64_t stratumMaximum = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
* layoutChunkWidthSubvoxels;
if (groundMinimum <= stratumMinimum || capMaximum >= stratumMaximum)
{ error = "SurfaceWorld profile escapes its finite stratum Z bounds"; return false; }
return true; return true;
} }
inline bool ValidateStratum(const WorldAssemblyStratumPlanV1& stratum, inline bool ValidateStratum(const WorldAssemblyStratumPlanV1& stratum,
std::int64_t layoutChunkWidthSubvoxels, std::string& error) const WorldAssemblyRecipeV1& recipe, std::string& error)
{ {
const std::int64_t layoutChunkWidthSubvoxels = recipe.layoutChunkWidthSubvoxels;
if (stratum.strateIndex < 0 || stratum.topChunkZ < stratum.bottomChunkZ if (stratum.strateIndex < 0 || stratum.topChunkZ < stratum.bottomChunkZ
|| stratum.capWavelengthSubvoxels <= 0 || stratum.capUndulationSubvoxels < 0 || stratum.capWavelengthSubvoxels <= 0 || stratum.capUndulationSubvoxels < 0
|| stratum.features.size() > MaxFeatureCount || stratum.route.size() > MaxRoutePointCount) || stratum.features.size() > MaxFeatureCount || stratum.route.size() > MaxRoutePointCount)
{ error = "world assembly stratum plan has an invalid extent or exceeds its bounded payload"; return false; } { error = "world assembly stratum plan has an invalid extent or exceeds its bounded payload"; return false; }
const WorldAssemblyRecipeV1& effectiveRecipe = stratum.hasRecipeOverride
? stratum.recipeOverride : recipe;
if (!ValidateSurfaceProfile(stratum.surfaceProfile, effectiveRecipe.seed, error)) return false;
if (stratum.surfaceProfile != effectiveRecipe.surfaceProfile)
{ error = "SurfaceWorld recipe and stratum plan profile payloads do not match"; return false; }
if (!ValidateSurfaceProfileGeometry(stratum.surfaceProfile, stratum,
layoutChunkWidthSubvoxels, error)) return false;
if (!ValidScalar(stratum.capHeightSubvoxels) || !ValidScalar(stratum.capUndulationSubvoxels) if (!ValidScalar(stratum.capHeightSubvoxels) || !ValidScalar(stratum.capUndulationSubvoxels)
|| !ValidScalar(stratum.capWavelengthSubvoxels) || !ValidScalar(stratum.capWavelengthSubvoxels)
|| !ValidScalar(stratum.capDetailAmplitudeSubvoxels) || !ValidScalar(stratum.capDetailAmplitudeSubvoxels)
@@ -466,6 +621,8 @@ namespace world_assembly_detail
HashWord(hash, static_cast<std::uint64_t>(value), 8); HashWord(hash, static_cast<std::uint64_t>(value), 8);
} }
} }
if (recipe.surfaceProfile.version != 0)
HashSurfaceProfile(hash, recipe.surfaceProfile);
return hash; return hash;
} }
@@ -525,6 +682,8 @@ namespace world_assembly_detail
for (const auto value : point.positionSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8); for (const auto value : point.positionSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
HashWord(hash, static_cast<std::uint64_t>(point.radiusSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(point.radiusSubvoxels), 8);
} }
if (stratum.surfaceProfile.version != 0)
HashSurfaceProfile(hash, stratum.surfaceProfile);
return hash; return hash;
} }
@@ -627,6 +786,59 @@ namespace world_assembly_detail
return out.str(); return out.str();
} }
inline const char* SurfaceProfileKindName(WorldAssemblySurfaceProfileKindV1 kind)
{
switch (kind)
{
case WorldAssemblySurfaceProfileKindV1::natural: return "natural";
case WorldAssemblySurfaceProfileKindV1::periodicHills: return "periodicHills";
case WorldAssemblySurfaceProfileKindV1::mirroredRelief: return "mirroredRelief";
case WorldAssemblySurfaceProfileKindV1::flatField: return "flatField";
}
return "unsupported";
}
inline bool ParseSurfaceProfile(const VFJson::Value& value,
WorldAssemblySurfaceProfileV1& profile)
{
if (!value.IsObject() || !ReadU32(value.Get("version"), profile.version)) return false;
const auto* kind = std::get_if<std::string>(&value.Get("kind").Data);
if (kind == nullptr) return false;
if (*kind == "periodicHills") profile.kind = WorldAssemblySurfaceProfileKindV1::periodicHills;
else if (*kind == "mirroredRelief") profile.kind = WorldAssemblySurfaceProfileKindV1::mirroredRelief;
else if (*kind == "flatField") profile.kind = WorldAssemblySurfaceProfileKindV1::flatField;
else if (*kind == "natural") profile.kind = WorldAssemblySurfaceProfileKindV1::natural;
else return false;
return ReadInteger(value.Get("h0_q"), profile.h0Subvoxels)
&& ReadInteger(value.Get("amplitude_x_q"), profile.amplitudeXSubvoxels)
&& ReadInteger(value.Get("amplitude_y_q"), profile.amplitudeYSubvoxels)
&& ReadInteger(value.Get("wavelength_x_q"), profile.wavelengthXSubvoxels)
&& ReadInteger(value.Get("wavelength_y_q"), profile.wavelengthYSubvoxels)
&& ReadU32(value.Get("phase_x_turns_q32"), profile.phaseXTurnsQ32)
&& ReadU32(value.Get("phase_y_turns_q32"), profile.phaseYTurnsQ32)
&& ReadInteger(value.Get("mirror_lambda_q16"), profile.mirrorLambdaQ16)
&& ReadInteger(value.Get("minimum_arena_clearance_q"),
profile.minimumArenaClearanceSubvoxels);
}
inline std::string SurfaceProfileJson(const WorldAssemblySurfaceProfileV1& profile)
{
std::ostringstream out;
out << "{\"version\":" << profile.version
<< ",\"kind\":\"" << SurfaceProfileKindName(profile.kind) << "\""
<< ",\"h0_q\":" << profile.h0Subvoxels
<< ",\"amplitude_x_q\":" << profile.amplitudeXSubvoxels
<< ",\"amplitude_y_q\":" << profile.amplitudeYSubvoxels
<< ",\"wavelength_x_q\":" << profile.wavelengthXSubvoxels
<< ",\"wavelength_y_q\":" << profile.wavelengthYSubvoxels
<< ",\"phase_x_turns_q32\":" << profile.phaseXTurnsQ32
<< ",\"phase_y_turns_q32\":" << profile.phaseYTurnsQ32
<< ",\"mirror_lambda_q16\":" << profile.mirrorLambdaQ16
<< ",\"minimum_arena_clearance_q\":"
<< profile.minimumArenaClearanceSubvoxels << '}';
return out.str();
}
inline bool ParseRecipe(const VFJson::Value& value, WorldAssemblyRecipeV1& recipe, inline bool ParseRecipe(const VFJson::Value& value, WorldAssemblyRecipeV1& recipe,
std::string& error) std::string& error)
{ {
@@ -647,6 +859,10 @@ namespace world_assembly_detail
|| !ReadInteger(value.Get("climate_moisture_wavelength_q"), recipe.biomeClimateMoistureWavelengthSubvoxels) || !ReadInteger(value.Get("climate_moisture_wavelength_q"), recipe.biomeClimateMoistureWavelengthSubvoxels)
|| !ReadInteger(value.Get("climate_relief_contrast_q16"), recipe.biomeClimateReliefContrastQ16))) || !ReadInteger(value.Get("climate_relief_contrast_q16"), recipe.biomeClimateReliefContrastQ16)))
{ error = "world assembly recipe is missing evaluator-2 climate parameters"; return false; } { error = "world assembly recipe is missing evaluator-2 climate parameters"; return false; }
const VFJson::Value& surfaceProfileValue = value.Get("surface_profile");
if (!std::holds_alternative<std::nullptr_t>(surfaceProfileValue.Data)
&& !ParseSurfaceProfile(surfaceProfileValue, recipe.surfaceProfile))
{ error = "world assembly recipe has a malformed versioned SurfaceWorld profile"; return false; }
for (const auto& item : value.Get("biomes").AsArray()) for (const auto& item : value.Get("biomes").AsArray())
{ {
WorldAssemblyBiomeProfileV1 biome; WorldAssemblyBiomeProfileV1 biome;
@@ -709,6 +925,8 @@ namespace world_assembly_detail
out << ",\"climate_relief_wavelength_q\":" << recipe.biomeClimateReliefWavelengthSubvoxels out << ",\"climate_relief_wavelength_q\":" << recipe.biomeClimateReliefWavelengthSubvoxels
<< ",\"climate_moisture_wavelength_q\":" << recipe.biomeClimateMoistureWavelengthSubvoxels << ",\"climate_moisture_wavelength_q\":" << recipe.biomeClimateMoistureWavelengthSubvoxels
<< ",\"climate_relief_contrast_q16\":" << recipe.biomeClimateReliefContrastQ16; << ",\"climate_relief_contrast_q16\":" << recipe.biomeClimateReliefContrastQ16;
if (recipe.surfaceProfile.version != 0)
out << ",\"surface_profile\":" << SurfaceProfileJson(recipe.surfaceProfile);
out << ",\"content_hash\":\"" << HashText(recipe.contentHash) << "\",\"biomes\":["; out << ",\"content_hash\":\"" << HashText(recipe.contentHash) << "\",\"biomes\":[";
for (std::size_t i = 0; i < recipe.biomes.size(); ++i) for (std::size_t i = 0; i < recipe.biomes.size(); ++i)
{ {
@@ -797,6 +1015,10 @@ namespace world_assembly_detail
{ error = "world assembly stratum has malformed optional evaluator-2 fields"; return false; } { error = "world assembly stratum has malformed optional evaluator-2 fields"; return false; }
if(hasWaterBoolean)stratum.waterEnabled=waterEnabled.Boolean(false); if(hasWaterBoolean)stratum.waterEnabled=waterEnabled.Boolean(false);
if(hasWaterNumber)stratum.waterEnabled=waterEnabled.Number()!=0.0; if(hasWaterNumber)stratum.waterEnabled=waterEnabled.Number()!=0.0;
const VFJson::Value& surfaceProfileValue = item.Get("surface_profile");
if (!std::holds_alternative<std::nullptr_t>(surfaceProfileValue.Data)
&& !ParseSurfaceProfile(surfaceProfileValue, stratum.surfaceProfile))
{ error = "world assembly stratum has a malformed versioned SurfaceWorld profile"; return false; }
const VFJson::Value& routeConstraintsValue = item.Get("route_constraints"); const VFJson::Value& routeConstraintsValue = item.Get("route_constraints");
if (!std::holds_alternative<std::nullptr_t>(routeConstraintsValue.Data)) if (!std::holds_alternative<std::nullptr_t>(routeConstraintsValue.Data))
{ {
@@ -1367,6 +1589,196 @@ namespace world_assembly_detail
return SmoothQ16(static_cast<std::uint32_t>(std::clamp<std::int64_t>(edge*Q16/feather,0,Q16))); return SmoothQ16(static_cast<std::uint32_t>(std::clamp<std::int64_t>(edge*Q16/feather,0,Q16)));
} }
constexpr std::int64_t Q30 = 1ll << 30;
constexpr std::array<std::int64_t, 30> CordicAtanTurnsQ32{{
536870912,316933406,167458907,85004756,42667331,21354465,10679838,5340245,
2670163,1335087,667544,333772,166886,83443,41722,20861,10430,5215,2608,
1304,652,326,163,81,41,20,10,5,3,1}};
inline std::array<std::int64_t, 2> CordicSinCosQuarterQ30(std::uint32_t angleQ32)
{
if (angleQ32 == 0) return {Q30, 0};
std::int64_t x = 652032874, y = 0;
std::int64_t z = angleQ32;
for (std::size_t i = 0; i < CordicAtanTurnsQ32.size(); ++i)
{
const std::int64_t oldX = x;
if (z >= 0)
{
x -= y >> i;
y += oldX >> i;
z -= CordicAtanTurnsQ32[i];
}
else
{
x += y >> i;
y -= oldX >> i;
z += CordicAtanTurnsQ32[i];
}
}
return {std::clamp<std::int64_t>(x, -Q30, Q30),
std::clamp<std::int64_t>(y, -Q30, Q30)};
}
inline std::int64_t CosineQ30(std::uint32_t phaseTurnsQ32)
{
constexpr std::uint32_t Quarter = 1u << 30;
if (phaseTurnsQ32 == 0) return Q30;
if (phaseTurnsQ32 == Quarter) return 0;
if (phaseTurnsQ32 == 2u * Quarter) return -Q30;
if (phaseTurnsQ32 == 3u * Quarter) return 0;
constexpr std::uint32_t TableBits = 16;
constexpr std::uint32_t TableSize = 1u << TableBits;
constexpr std::uint32_t FractionMask = (1u << (32 - TableBits)) - 1u;
static const std::array<std::int32_t, TableSize> table = []
{
std::array<std::int32_t, TableSize> values{};
constexpr std::uint32_t QuarterTableSize = TableSize / 4;
constexpr std::uint32_t QuarterStepQ32 = 1u << (30 - (TableBits - 2));
std::array<std::int32_t, QuarterTableSize + 1> quarter{};
for (std::uint32_t offset = 0; offset <= QuarterTableSize; ++offset)
{
const std::int64_t cosine = CordicSinCosQuarterQ30(
offset * QuarterStepQ32)[0];
const std::int64_t monotone = offset == 0 ? cosine
: std::min<std::int64_t>(quarter[offset - 1], cosine);
quarter[offset] = static_cast<std::int32_t>(monotone);
}
for (std::uint32_t index = 0; index < TableSize; ++index)
{
const std::uint32_t quadrant = index / QuarterTableSize;
const std::uint32_t offset = index % QuarterTableSize;
const std::uint32_t mirroredOffset = QuarterTableSize - offset;
const std::int32_t value = quadrant == 0 ? quarter[offset]
: quadrant == 1 ? -quarter[mirroredOffset]
: quadrant == 2 ? -quarter[offset] : quarter[mirroredOffset];
values[index] = value;
}
return values;
}();
const std::uint32_t index = phaseTurnsQ32 >> (32 - TableBits);
const std::uint32_t fraction = phaseTurnsQ32 & FractionMask;
const std::int64_t first = table[index];
const std::int64_t second = table[(index + 1u) & (TableSize - 1u)];
const std::int64_t numerator = (second - first) * fraction;
const std::int64_t rounded = numerator >= 0
? numerator + (1ll << (31 - TableBits))
: numerator - (1ll << (31 - TableBits));
// The fixed-point table interpolation is monotone within each quadrant;
// cosine interval bounds therefore still attain extrema at their endpoints
// or at the exact quarter-turn extrema checked below.
return first + rounded / (1ll << (32 - TableBits));
}
inline std::uint32_t SurfaceProfileTurnAtCoordinate(std::int64_t coordinate,
std::int64_t wavelength, std::uint32_t phaseTurnsQ32)
{
const std::uint64_t coordinateTurns = static_cast<std::uint64_t>(
FloorMod(coordinate, wavelength)) * static_cast<std::uint64_t>(Q32)
/ static_cast<std::uint64_t>(wavelength);
return phaseTurnsQ32 + static_cast<std::uint32_t>(coordinateTurns);
}
struct CosineRangeQ30
{
std::int64_t minimum = -Q30;
std::int64_t maximum = Q30;
};
inline CosineRangeQ30 SurfaceProfileCosineRangeQ30(std::int64_t minimumCoordinate,
std::int64_t maximumCoordinate, std::int64_t wavelength,
std::uint32_t phaseTurnsQ32)
{
if (wavelength <= 0 || minimumCoordinate > maximumCoordinate
|| maximumCoordinate - minimumCoordinate >= wavelength)
return {};
CosineRangeQ30 range;
const std::int64_t cosineAtMinimum = CosineQ30(
SurfaceProfileTurnAtCoordinate(minimumCoordinate, wavelength, phaseTurnsQ32));
const std::int64_t cosineAtMaximum = CosineQ30(
SurfaceProfileTurnAtCoordinate(maximumCoordinate, wavelength, phaseTurnsQ32));
range.minimum = std::min(cosineAtMinimum, cosineAtMaximum);
range.maximum = std::max(cosineAtMinimum, cosineAtMaximum);
// Coordinate turns are Q0.32. Test the exact analytic extrema in a common
// rational numerator domain; one Q0.32 unit of endpoint slack accounts for
// the fixed phase conversion used by point().
const std::uint64_t periodNumerator = static_cast<std::uint64_t>(wavelength)
* static_cast<std::uint64_t>(Q32);
const std::uint64_t startNumerator = (
static_cast<std::uint64_t>(FloorMod(minimumCoordinate, wavelength))
* static_cast<std::uint64_t>(Q32)
+ static_cast<std::uint64_t>(phaseTurnsQ32)
* static_cast<std::uint64_t>(wavelength)) % periodNumerator;
const std::uint64_t spanNumerator = static_cast<std::uint64_t>(
maximumCoordinate - minimumCoordinate) * static_cast<std::uint64_t>(Q32);
const auto includesExtremum = [&](std::uint64_t target)
{
const std::uint64_t forward = target >= startNumerator
? target - startNumerator : periodNumerator - startNumerator + target;
const std::uint64_t backward = periodNumerator - forward;
return forward <= spanNumerator + wavelength
|| backward <= wavelength;
};
if (includesExtremum(0)) range.maximum = Q30;
if (includesExtremum(periodNumerator / 2)) range.minimum = -Q30;
return range;
}
inline std::int64_t MulQ30(std::int64_t value, std::int64_t cosineQ30)
{ return (value * cosineQ30) / Q30; }
struct SurfaceProfileColumnBounds
{
std::int64_t groundMinimum = 0;
std::int64_t groundMaximum = 0;
std::int64_t capMinimum = 0;
std::int64_t capMaximum = 0;
};
inline SurfaceProfileColumnBounds ProfileColumnBounds(
const WorldAssemblySurfaceProfileV1& profile,
const WorldAssemblyStratumPlanV1& plan,
std::int64_t minimumX, std::int64_t minimumY,
std::int64_t maximumX, std::int64_t maximumY)
{
SurfaceProfileColumnBounds bounds;
if (profile.kind == WorldAssemblySurfaceProfileKindV1::flatField)
{
bounds.groundMinimum = profile.h0Subvoxels;
bounds.groundMaximum = profile.h0Subvoxels;
bounds.capMinimum = plan.capHeightSubvoxels;
bounds.capMaximum = plan.capHeightSubvoxels;
return bounds;
}
const CosineRangeQ30 x = SurfaceProfileCosineRangeQ30(minimumX, maximumX,
profile.wavelengthXSubvoxels, profile.phaseXTurnsQ32);
const CosineRangeQ30 y = SurfaceProfileCosineRangeQ30(minimumY, maximumY,
profile.wavelengthYSubvoxels, profile.phaseYTurnsQ32);
const std::int64_t groundXMinimum = MulQ30(profile.amplitudeXSubvoxels, x.minimum);
const std::int64_t groundXMaximum = MulQ30(profile.amplitudeXSubvoxels, x.maximum);
const std::int64_t groundYMinimum = MulQ30(profile.amplitudeYSubvoxels, y.minimum);
const std::int64_t groundYMaximum = MulQ30(profile.amplitudeYSubvoxels, y.maximum);
bounds.groundMinimum = profile.h0Subvoxels + groundXMinimum + groundYMinimum - 2;
bounds.groundMaximum = profile.h0Subvoxels + groundXMaximum + groundYMaximum + 2;
if (profile.kind == WorldAssemblySurfaceProfileKindV1::mirroredRelief)
{
const std::int64_t reliefMinimum = groundXMinimum + groundYMinimum;
const std::int64_t reliefMaximum = groundXMaximum + groundYMaximum;
bounds.capMinimum = plan.capHeightSubvoxels
- MulQ16(reliefMaximum, profile.mirrorLambdaQ16) - 2;
bounds.capMaximum = plan.capHeightSubvoxels
- MulQ16(reliefMinimum, profile.mirrorLambdaQ16) + 2;
}
else
{
bounds.capMinimum = plan.capHeightSubvoxels;
bounds.capMaximum = plan.capHeightSubvoxels;
}
return bounds;
}
struct HeightfieldColumnCacheEntry struct HeightfieldColumnCacheEntry
{ {
const WorldAssemblyRecipeV1* recipe = nullptr; const WorldAssemblyRecipeV1* recipe = nullptr;
@@ -1415,6 +1827,61 @@ namespace world_assembly_detail
HeightfieldPointSample out; HeightfieldPointSample out;
if(recipe.evaluatorVersion>=2) if(recipe.evaluatorVersion>=2)
{ {
if (recipe.surfaceProfile.version == 1)
{
const auto& profile = recipe.surfaceProfile;
std::int64_t relief = 0;
if (profile.kind != WorldAssemblySurfaceProfileKindV1::flatField)
{
const std::int64_t cosineX = CosineQ30(SurfaceProfileTurnAtCoordinate(
x, profile.wavelengthXSubvoxels, profile.phaseXTurnsQ32));
const std::int64_t cosineY = CosineQ30(SurfaceProfileTurnAtCoordinate(
y, profile.wavelengthYSubvoxels, profile.phaseYTurnsQ32));
relief = MulQ30(profile.amplitudeXSubvoxels, cosineX)
+ MulQ30(profile.amplitudeYSubvoxels, cosineY);
}
out.groundHeightSubvoxels = profile.h0Subvoxels + relief;
out.capHeightSubvoxels = profile.kind
== WorldAssemblySurfaceProfileKindV1::mirroredRelief
? plan.capHeightSubvoxels - MulQ16(relief, profile.mirrorLambdaQ16)
: plan.capHeightSubvoxels;
out.biomeId = recipe.biomes.front().stableId;
out.neighborBiomeId = out.biomeId;
out.biomeWeightQ16 = static_cast<std::uint32_t>(Q16);
const auto* list = ScopedWorldAssemblyFeatureQueriesV1::find(
&recipe, &plan, x, y, 0, true);
for (const auto& feature : FeatureRange{plan.features, list})
{
if (feature.type == 1)
{
const std::uint32_t fade = FeatureFadeQ16(feature, x, y,
feature.parametersSubvoxels[1]);
if (fade) out.groundHeightSubvoxels += MulQ16(
feature.parametersSubvoxels[0], fade);
}
else if (feature.type == 3)
{
const std::uint32_t fade = FeatureFadeQ16(feature, x, y,
feature.parametersSubvoxels[2]);
const std::int64_t wave = std::max<std::int64_t>(
1, feature.parametersSubvoxels[1]);
if (fade)
{
const std::uint32_t featureSeed = static_cast<std::uint32_t>(feature.stableId);
const std::int64_t nx = NoiseCoordinateQ16(x, wave)
+ SeedOffsetQ16(featureSeed, 1700);
const std::int64_t ny = NoiseCoordinateQ16(y, wave)
+ SeedOffsetQ16(featureSeed, 2900);
const std::int32_t noise = FractalNoiseQ16(nx, ny,
SeedOffsetQ16(featureSeed, 4100), 3);
out.capHeightSubvoxels += MulQ16(MulQ16(noise,
feature.parametersSubvoxels[0]), fade);
}
}
}
out.waterSurfaceSubvoxels = plan.waterSurfaceSubvoxels;
return out;
}
const BiomeSelectionV2 selection=SelectBiomeV2(recipe,x,y); const BiomeSelectionV2 selection=SelectBiomeV2(recipe,x,y);
const auto& primary=recipe.biomes[selection.primary]; const auto& primary=recipe.biomes[selection.primary];
const auto& neighbor=recipe.biomes[selection.neighbor]; const auto& neighbor=recipe.biomes[selection.neighbor];
@@ -1742,6 +2209,16 @@ std::optional<WorldAssemblyManifest> WorldAssemblyManifest::FromJson(
bool WorldAssemblyManifest::finalize(std::string& error) bool WorldAssemblyManifest::finalize(std::string& error)
{ {
ResolveWorldAssemblySurfaceProfilePhasesV1(recipe.surfaceProfile, recipe.seed);
for (auto& stratum : plan.strates)
{
WorldAssemblyRecipeV1& effectiveRecipe = stratum.hasRecipeOverride
? stratum.recipeOverride : recipe;
ResolveWorldAssemblySurfaceProfilePhasesV1(effectiveRecipe.surfaceProfile,
effectiveRecipe.seed);
ResolveWorldAssemblySurfaceProfilePhasesV1(stratum.surfaceProfile,
effectiveRecipe.seed);
}
if (schemaVersion != WorldAssemblySchemaVersion || plan.schemaVersion != WorldAssemblySchemaVersion if (schemaVersion != WorldAssemblySchemaVersion || plan.schemaVersion != WorldAssemblySchemaVersion
|| !world_assembly_detail::ValidateRecipe(recipe, error)) || !world_assembly_detail::ValidateRecipe(recipe, error))
{ if (error.empty()) error = "world assembly manifest or plan version is unsupported"; return false; } { if (error.empty()) error = "world assembly manifest or plan version is unsupported"; return false; }
@@ -1759,7 +2236,7 @@ bool WorldAssemblyManifest::finalize(std::string& error)
{ error = "SurfaceWorld stratum recipe seed does not match the world assembly season seed"; return false; } { error = "SurfaceWorld stratum recipe seed does not match the world assembly season seed"; return false; }
stratum.recipeOverride.contentHash = world_assembly_detail::HashRecipe(stratum.recipeOverride); stratum.recipeOverride.contentHash = world_assembly_detail::HashRecipe(stratum.recipeOverride);
} }
if (!world_assembly_detail::ValidateStratum(stratum, recipe.layoutChunkWidthSubvoxels, error)) return false; if (!world_assembly_detail::ValidateStratum(stratum, recipe, error)) return false;
if (recipe.evaluatorVersion >= 4) if (recipe.evaluatorVersion >= 4)
{ {
if (stratum.routeConstraints.version != 1 || stratum.route.size() < 2) if (stratum.routeConstraints.version != 1 || stratum.route.size() < 2)
@@ -1882,6 +2359,9 @@ std::string WorldAssemblyManifest::ToJson() const
if (stratum.hasRecipeOverride) if (stratum.hasRecipeOverride)
out << ",\"recipe_override\":" out << ",\"recipe_override\":"
<< world_assembly_detail::RecipeJson(stratum.recipeOverride); << world_assembly_detail::RecipeJson(stratum.recipeOverride);
if (stratum.surfaceProfile.version != 0)
out << ",\"surface_profile\":"
<< world_assembly_detail::SurfaceProfileJson(stratum.surfaceProfile);
out out
<< ",\"content_hash\":\"" << world_assembly_detail::HashText(stratum.contentHash) << ",\"content_hash\":\"" << world_assembly_detail::HashText(stratum.contentHash)
<< "\",\"features\":["; << "\",\"features\":[";
@@ -1957,6 +2437,8 @@ bool Heightfield2D::isValid() const noexcept
|| recipe_->biomes.empty() || recipe_->biomes.size() > world_assembly_detail::MaxBiomeCount || recipe_->biomes.empty() || recipe_->biomes.size() > world_assembly_detail::MaxBiomeCount
|| recipe_->biomeCellSizeSubvoxels <= 0 || recipe_->biomeWarpWavelengthSubvoxels <= 0 || recipe_->biomeCellSizeSubvoxels <= 0 || recipe_->biomeWarpWavelengthSubvoxels <= 0
|| recipe_->biomeWarpSubvoxels < 0 || recipe_->biomeBorderBlendSubvoxels < 0 || recipe_->biomeWarpSubvoxels < 0 || recipe_->biomeBorderBlendSubvoxels < 0
|| recipe_->surfaceProfile != plan_->surfaceProfile
|| recipe_->surfaceProfile.version > 1
|| plan_->strateIndex < 0 || plan_->topChunkZ < plan_->bottomChunkZ || plan_->strateIndex < 0 || plan_->topChunkZ < plan_->bottomChunkZ
|| plan_->capWavelengthSubvoxels <= 0 || plan_->capUndulationSubvoxels < 0) || plan_->capWavelengthSubvoxels <= 0 || plan_->capUndulationSubvoxels < 0)
return false; return false;
@@ -1973,6 +2455,32 @@ bool Heightfield2D::isValid() const noexcept
std::array<std::int64_t,2> Heightfield2D::capHeightBounds() const noexcept std::array<std::int64_t,2> Heightfield2D::capHeightBounds() const noexcept
{ {
if(!isValid())return {0,0}; if(!isValid())return {0,0};
if (recipe_->surfaceProfile.version == 1)
{
const auto& profile = recipe_->surfaceProfile;
std::int64_t swing = 0;
if (profile.kind == WorldAssemblySurfaceProfileKindV1::mirroredRelief)
{
const std::int64_t relief = profile.amplitudeXSubvoxels
+ profile.amplitudeYSubvoxels;
swing = (relief * profile.mirrorLambdaQ16 + world_assembly_detail::Q16 - 1)
/ world_assembly_detail::Q16 + 2;
}
std::int64_t featureAmplitude = 0;
for (const auto& feature : plan_->features)
if (feature.type == 3)
featureAmplitude += std::abs(feature.parametersSubvoxels[0]);
std::int64_t minimum = plan_->capHeightSubvoxels - swing - featureAmplitude;
std::int64_t maximum = plan_->capHeightSubvoxels + swing + featureAmplitude;
if (recipe_->evaluatorVersion >= 5)
{
const std::int64_t upperBoundary = (static_cast<std::int64_t>(plan_->topChunkZ) + 1)
* recipe_->layoutChunkWidthSubvoxels;
minimum = std::min(minimum, upperBoundary);
maximum = std::min(maximum, upperBoundary);
}
return {minimum, maximum};
}
long double Minimum=std::numeric_limits<long double>::infinity(); long double Minimum=std::numeric_limits<long double>::infinity();
long double Maximum=-std::numeric_limits<long double>::infinity(); long double Maximum=-std::numeric_limits<long double>::infinity();
const auto IncludeBiome=[&](const WorldAssemblyBiomeProfileV1& Biome) const auto IncludeBiome=[&](const WorldAssemblyBiomeProfileV1& Biome)
@@ -2178,6 +2686,15 @@ HeightfieldBoxClass Heightfield2D::classifyLatticeBox(std::int64_t minimumX,
&&feature.minimumSubvoxels[2]<=maximumZ&&feature.maximumSubvoxels[2]>=minimumZ) &&feature.minimumSubvoxels[2]<=maximumZ&&feature.maximumSubvoxels[2]>=minimumZ)
return HeightfieldBoxClass::mixed; return HeightfieldBoxClass::mixed;
} }
if (recipe_->surfaceProfile.version == 1)
{
const FieldInterval proof = interval(minimumX, minimumY, minimumZ,
maximumX, maximumY, maximumZ);
if (!proof.known) return HeightfieldBoxClass::unknown;
if (proof.minimumDensitySubvoxels > 0) return HeightfieldBoxClass::allAir;
if (proof.maximumDensitySubvoxels < 0) return HeightfieldBoxClass::allSolid;
return HeightfieldBoxClass::mixed;
}
const std::int64_t stride=static_cast<std::int64_t>(stepVoxels) const std::int64_t stride=static_cast<std::int64_t>(stepVoxels)
*WorldAssemblySubvoxelsPerVoxel; *WorldAssemblySubvoxelsPerVoxel;
if((maximumX-minimumX)%stride!=0||(maximumY-minimumY)%stride!=0 if((maximumX-minimumX)%stride!=0||(maximumY-minimumY)%stride!=0
@@ -2220,7 +2737,29 @@ FieldInterval Heightfield2D::interval(std::int64_t minX, std::int64_t minY,
std::int64_t groundMax = std::numeric_limits<std::int64_t>::min(); std::int64_t groundMax = std::numeric_limits<std::int64_t>::min();
std::int64_t capMin=plan_->capHeightSubvoxels-plan_->capUndulationSubvoxels; std::int64_t capMin=plan_->capHeightSubvoxels-plan_->capUndulationSubvoxels;
std::int64_t capMax=plan_->capHeightSubvoxels+plan_->capUndulationSubvoxels; std::int64_t capMax=plan_->capHeightSubvoxels+plan_->capUndulationSubvoxels;
if(recipe_->evaluatorVersion>=2) if (recipe_->surfaceProfile.version == 1)
{
const auto profileBounds = world_assembly_detail::ProfileColumnBounds(
recipe_->surfaceProfile, *plan_, minX, minY, maxX, maxY);
groundMin = profileBounds.groundMinimum;
groundMax = profileBounds.groundMaximum;
capMin = profileBounds.capMinimum;
capMax = profileBounds.capMaximum;
std::int64_t groundFeatureMagnitude = 0;
std::int64_t capFeatureMagnitude = 0;
for (const auto& feature : plan_->features)
{
if (feature.type == 1)
groundFeatureMagnitude += std::abs(feature.parametersSubvoxels[0]);
if (feature.type == 3)
capFeatureMagnitude += std::abs(feature.parametersSubvoxels[0]);
}
groundMin -= groundFeatureMagnitude;
groundMax += groundFeatureMagnitude;
capMin -= capFeatureMagnitude;
capMax += capFeatureMagnitude;
}
else if(recipe_->evaluatorVersion>=2)
{ {
for(const auto& biome:recipe_->biomes) for(const auto& biome:recipe_->biomes)
{ {
@@ -2313,6 +2852,24 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
{ {
FieldDetailEnvelope out; FieldDetailEnvelope out;
if (!isValid()) return out; if (!isValid()) return out;
if (recipe_->surfaceProfile.version == 1)
{
const auto& profile = recipe_->surfaceProfile;
out.maximumAbsoluteDisplacementSubvoxels = profile.amplitudeXSubvoxels
+ profile.amplitudeYSubvoxels;
if (profile.kind == WorldAssemblySurfaceProfileKindV1::mirroredRelief)
out.maximumAbsoluteDisplacementSubvoxels +=
(out.maximumAbsoluteDisplacementSubvoxels * profile.mirrorLambdaQ16
+ world_assembly_detail::Q16 - 1) / world_assembly_detail::Q16;
for (const auto& feature : plan_->features)
if (feature.type == 1 || feature.type == 3)
out.maximumAbsoluteDisplacementSubvoxels +=
std::abs(feature.parametersSubvoxels[0]);
if (profile.kind != WorldAssemblySurfaceProfileKindV1::flatField)
out.minimumFeatureSizeSubvoxels = std::max<std::int64_t>(1,
std::min(profile.wavelengthXSubvoxels, profile.wavelengthYSubvoxels) / 2);
return out;
}
std::int64_t maximum = plan_->capUndulationSubvoxels*5/2 std::int64_t maximum = plan_->capUndulationSubvoxels*5/2
+plan_->capDetailAmplitudeSubvoxels*5/2+plan_->capRidgeAmplitudeSubvoxels; +plan_->capDetailAmplitudeSubvoxels*5/2+plan_->capRidgeAmplitudeSubvoxels;
std::int64_t minimumFeature = std::numeric_limits<std::int64_t>::max(); std::int64_t minimumFeature = std::numeric_limits<std::int64_t>::max();
@@ -2448,6 +3005,7 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
&result.capBoxes,false,"cap"); &result.capBoxes,false,"cap");
} }
bool hasCliffProfile=false; bool hasCliffProfile=false;
if(recipe->surfaceProfile.version==0)
for(const auto& biome:recipe->biomes)hasCliffProfile|=biome.cliffStrengthQ16>0; for(const auto& biome:recipe->biomes)hasCliffProfile|=biome.cliffStrengthQ16>0;
if(hasCliffProfile) if(hasCliffProfile)
{ {
@@ -2488,7 +3046,8 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
// Controle negatif : cette plage unilaterale exclut un echantillon connu; l'audit doit rejeter cette borne mensongere. // Controle negatif : cette plage unilaterale exclut un echantillon connu; l'audit doit rejeter cette borne mensongere.
const FieldInterval deliberatelyWrong{true, controlPoint + 1, controlPoint + 2}; const FieldInterval deliberatelyWrong{true, controlPoint + 1, controlPoint + 2};
result.negativeControlDetected = !world_assembly_detail::InInterval(deliberatelyWrong, controlPoint); result.negativeControlDetected = !world_assembly_detail::InInterval(deliberatelyWrong, controlPoint);
const bool borderCoverage=recipe->biomes.size()<2||recipe->biomeBorderBlendSubvoxels==0 const bool borderCoverage=recipe->surfaceProfile.version!=0
||recipe->biomes.size()<2||recipe->biomeBorderBlendSubvoxels==0
||result.biomeBorderBoxes>0; ||result.biomeBorderBoxes>0;
const bool cliffCoverage=!hasCliffProfile||result.cliffBoxes>0; const bool cliffCoverage=!hasCliffProfile||result.cliffBoxes>0;
const bool exceptionCoverage=std::none_of(plan->features.begin(),plan->features.end(), const bool exceptionCoverage=std::none_of(plan->features.begin(),plan->features.end(),
+6
View File
@@ -83,6 +83,12 @@ Costs below are design estimates, not timings. Low means bounded analytic featur
A passage planner must produce a traversable route or pair a non-walkable visual passage with another guaranteed route. A narrow crack or drop cannot be assumed walkable because its centerline is air. A passage planner must produce a traversable route or pair a non-walkable visual passage with another guaranteed route. A narrow crack or drop cannot be assumed walkable because its centerline is air.
For a SurfaceWorld strate, the supported arrival-to-departure route follows the sampled terrain
ground and stays within the mouth placement bounds. The inter-strate departure passage owns the
descent from that ground mouth through rock and bedrock. Treating the bottom of a tall SurfaceWorld
strate as the route's departure height would ask the walk planner to cross open terrain air; it is
the wrong endpoint for the intra-strate route.
| Passage type | Planner output | Bound and likely cost | | Passage type | Planner output | Bound and likely cost |
|---|---|---| |---|---|---|
| SlopedTunnel | Straight-style path with switchbacks as needed, capsule clearance, supported floor, maximum grade and landing geometry. | Swept capsule bounds plus floor envelope; low–medium. | | SlopedTunnel | Straight-style path with switchbacks as needed, capsule clearance, supported floor, maximum grade and landing geometry. | Swept capsule bounds plus floor envelope; low–medium. |
@@ -0,0 +1,110 @@
# World Assembler L29 — bedrock/ceiling transitions
**Core now honors the upper strate's `EVoxelStrateTransition` at the shared interface.** The owner
world's upper two-chunk Gradient resolves to 16 m at the 8 m layout chunk size. SurfaceWorld's
one-chunk setting remains in the resolved strate record. Core does not blend unrelated generator
parameters. `WorldAssemblyEvaluatorVersion` is 15; recipe versions and resolved-layout schema V3
are unchanged.
## Appearance and safety
| Mode | Result |
|---|---|
| Hard | The original flat, touching boundary. No displacement or overlap. |
| Gradient | Both faces follow one smooth 2D hash surface. On the 16 m interface, the measured peak-to-peak undulation is 3.988 m. |
| Interleaved | A deterministic 3D domain-warped hash field moves both faces inward, producing fingered overlap. Measured peak crossing was 6.487 m in FloatingIslands and 6.952 m in SurfaceWorld, within the declared 16 m. |
Each boundary band keeps at least **two Core voxels** of solid thickness: 0.5 m at the default
25 cm voxel size. The owner bands measured 0.5–1.0 m on the upper side and 6.25 m on the lower
side. Interleaved is capped by both the declared width and the bands' combined thickness, so the
solid intervals still touch or overlap.
The strict interface audit covers a 512 m square: 4,221 columns per interface, with four passage
columns excluded from the hole sweep. Hard, Gradient and Interleaved each report zero hole columns
and zero air samples. Both negative controls reject as expected: crossing one micrometre wider than
declared, and either band one micrometre thinner than the minimum. All six recipe/mode runs pass.
Transition faces return to the nominal seam near passage mouths: the protected radius is the mouth
radius plus 1 m, followed by a 2 m smooth fade. This kept the style-2 FI landing supported; its
final capsule/support audit passes.
## Passage and parity audits
The final matrix has 4 styles × 2 recipes × 2 compilers (16 runs). Capsule and walkability,
open-air, naturalness, no-long-bridge, route bindings, strict-validator controls, L13 route parity,
and L9 proof audits pass. Open-air reports zero violations; validator controls reject 10/10;
route-parity and proof audits report zero mismatches/violations. The L23/L24/L26 negative controls
are detected.
The L28 `FloorProfileBlend` diagnostic still exceeds its 2-voxel threshold in styles 1 and 3:
2.544 and 3.645 voxels. L28 records the same values on both compilers, so this is inherited and
unchanged.
## Hashes
The L25 W1/W8/W12 hash matrix is identical across Clang and GCC for both recipes: full-detail,
whole-view, all LOD rows and exact-query hashes match. Hash order below is density / mesh / payload /
topology.
| Recipe | Full detail (960 tiles) | Whole view (2,370 tiles) | Exact-query density / detail / collision |
|---|---|---|---|
| SurfaceWorld | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `0e8c5af01df96b14 / 70099e89b77811ed / dbce0fdcf6764126 / ddaf81a1228f9b4b` | `c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849` |
| FloatingIslands | `ed550e0c8b182695 / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `a492eefa4f534975 / 141f00f3e411a0df / 93f50c569526c5cb / d83b53c8e53df81c` | `7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf` |
The 24 m moving crossing processed 314 entering tiles per recipe; Clang/GCC crossing hashes match.
SurfaceWorld's away-from-interface probe at z = −400 m also matches L28: all 960 full-detail
tiles and the exact-query density/detail/collision hashes are identical. The broader whole-view
hash differs because its far-LOD coverage also includes the changed interface.
## Cost
Five clean, alternating W12 pairs against
`Saved/Linux/bin/VoxelForgeCoreHeadless-linux28-clang-r28`, hashing off:
| Recipe | L28 whole-view ready | L29 whole-view ready | Change |
|---|---:|---:|---:|
| SurfaceWorld | 1.581 s | 1.644 s | +3.97% |
| FloatingIslands | 16.605 s | 16.814 s | +1.26% |
FloatingIslands is within the ~3% target. SurfaceWorld is about one percentage point above it.
## Captures
All are 1280×720 JPEGs, each under 21 KB (well below 300 KB):
- `captures/fi-floor-gradient-above.jpg` — FI floor from above, inside FI.
- `captures/sw-ceiling-gradient-below.jpg` — SW ceiling from below, inside SW.
- `captures/interface-hard-side-cutaway.jpg`
- `captures/interface-gradient-side-cutaway.jpg`
- `captures/interface-interleaved-side-cutaway.jpg`
## Changes and run history
Core transition evaluation/validation and the shared composition path changed, along with the
headless transition audit, architecture/codemap, and the two Unreal files
`Source/VoxelForge/Public/VoxelStrateTypes.h` and
`Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`. Unreal was unavailable here, so those edits
were not compiled. No commit, push or stash was made.
Failed or retried runs:
- An early Clang build used a nonexistent dispatch helper; corrected to the current query method and rebuilt with Clang and GCC.
- The first Interleaved overlap used the full sum of band thicknesses and opened gaps (1,085 columns / 20,313 air samples). Capping overlap to `min(declared width, combined thickness)` removed the gaps.
- An initial hole sweep sampled a half-open band edge and reported 752 boundary-rounding holes. Sampling half a voxel inside each band removed those edge artifacts; final sweeps report zero holes.
- The first FI style-2 passage audit had 11 unsupported probes after Gradient moved the mouth face. The local mouth taper fixed it; all 16 final passage runs pass.
- A resolved-layout-only command initially omitted required pipeline flags and exited 2; rerun with the required flags passed. Early cutaway camera/clip settings produced blank framing; the final orientation and clip plane produced the captures above.
- One cost folder contained a duplicated timing sequence and was discarded. The five-pair clean run above was sequential. A cache experiment did not improve cost and was removed. A cost-extraction `sed` typo was rerun with Python; benchmark logs were unaffected.
No build or audit exceeded 30 minutes, and no step remained stuck for 15 minutes. All builds and runs
used CPUs 0–11, at most 12 workers, normal priority, and the 6 GiB address-space cap.
## Orchestrator review (Claude)
- Merged: transitions honoured safely (0 holes over 4,221 columns per interface, negative controls reject, all
passage audits pass, deterministic).
- **The look is not verified by the captures:** `interface-interleaved-side-cutaway.jpg` shows flat faces
(black = uncapped solid between faces), with no visible fingers; `sw-ceiling-gradient-below.jpg` is a flat
plane; `fi-floor-gradient-above.jpg` shows **concentric dashed rings** on the FI floor, which looks like an
artifact (possibly the passage-mouth protected-radius fade, or quantised undulation), not a smooth Gradient.
- Cumulative SW cost vs L24 is now ~+8% (L28 +3.7% plus L29 +4%). L30 covers the ring artifact, readable
transition captures, and cost recovery.
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,239 @@
#!/usr/bin/env python3
"""Rasterize upward mesh triangles into a top-down field height map."""
import argparse
import array
import json
import math
import struct
import sys
Q16 = 1 << 16
MASK64 = (1 << 64) - 1
def trunc_div(numerator, denominator):
return numerator // denominator if numerator >= 0 else -((-numerator) // denominator)
def transition_hash(seed, interface, x, y, z, salt):
value = 14695981039346656037
for word in (seed & 0xffffffff, interface & 0xffffffff,
x & MASK64, y & MASK64, z & MASK64, salt):
for byte in range(8):
value ^= (word >> (byte * 8)) & 0xff
value = (value * 1099511628211) & MASK64
value ^= value >> 30
value = (value * 0xbf58476d1ce4e5b9) & MASK64
value ^= value >> 27
value = (value * 0x94d049bb133111eb) & MASK64
value ^= value >> 31
return ((value >> 32) >> 15) - Q16
def transition_fade(value):
value = max(0, min(Q16, value))
square = value * value // Q16
return square * (3 * Q16 - 2 * value) // Q16
def gradient_height_um(layout, x_um, y_um):
upper = next(item for item in layout["strates"] if item["order"] == 0)
lower = next(item for item in layout["strates"] if item["order"] == 1)
width = upper["transition_width_um"]
cell = max(1_000_000, layout["strate_layout_chunk_size_um"] * 4)
ix = x_um // cell
iy = y_um // cell
tx = transition_fade(((x_um - ix * cell) * Q16) // cell)
ty = transition_fade(((y_um - iy * cell) * Q16) // cell)
def at(dx, dy):
noise = transition_hash(0, upper["strate_index"], ix + dx, iy + dy, 0,
0x47524144)
return noise
def lerp(a, b, t):
return a + trunc_div((b - a) * t, Q16)
low = lerp(lerp(at(0, 0), at(1, 0), tx),
lerp(at(0, 1), at(1, 1), tx), ty)
displacement = trunc_div(low * (width // 8), Q16)
seam = upper["bedrock"]["bottom_um"]
thickness = upper["bedrock"]["top_um"] - seam
return seam + displacement + thickness
def read_scene(path):
triangles = []
with open(path, "rb") as stream:
if stream.read(8) != b"VFMSH001":
raise SystemExit(f"{path}: invalid scene header")
version, voxel_um, count = struct.unpack("<3I", stream.read(12))
stream.read(40)
if version != 1 or count > 100000:
raise SystemExit("unsupported scene")
for _ in range(count):
key = struct.unpack("<4i", stream.read(16))
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
positions = array.array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array.array("f")
normals.frombytes(stream.read(vertex_count * 12))
indices = array.array("I")
indices.frombytes(stream.read(index_count * 4))
masks = stream.read(mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
for index in range(0, index_count, 3):
if masks and masks[index // 3]:
continue
a, b, c = indices[index:index + 3]
if max(a, b, c) >= surface_vertices:
continue
nz = (normals[a * 3 + 2] + normals[b * 3 + 2]
+ normals[c * 3 + 2]) / 3.0
if nz < 0.35:
continue
vertices = tuple((positions[v * 3], positions[v * 3 + 1],
positions[v * 3 + 2]) for v in (a, b, c))
triangles.append(vertices)
return voxel_um, triangles
def rasterize(triangles, bounds, width, height):
x0, x1, y0, y1 = bounds
values = [None] * (width * height)
sx, sy = width / (x1 - x0), height / (y1 - y0)
for tri in triangles:
(ax, ay, az), (bx, by, bz), (cx, cy, cz) = tri
den = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
if abs(den) < 1.0e-12:
continue
left = max(0, int(math.floor((min(ax, bx, cx) - x0) * sx)))
right = min(width - 1, int(math.ceil((max(ax, bx, cx) - x0) * sx)))
top = max(0, int(math.floor((y1 - max(ay, by, cy)) * sy)))
bottom = min(height - 1, int(math.ceil((y1 - min(ay, by, cy)) * sy)))
for py in range(top, bottom + 1):
y = y1 - (py + 0.5) / sy
row = py * width
for px in range(left, right + 1):
x = x0 + (px + 0.5) / sx
u = ((by - cy) * (x - cx) + (cx - bx) * (y - cy)) / den
v = ((cy - ay) * (x - cx) + (ax - cx) * (y - cy)) / den
w = 1.0 - u - v
if min(u, v, w) < -1.0e-6:
continue
z = u * az + v * bz + w * cz
idx = row + px
if values[idx] is None or z > values[idx]:
values[idx] = z
return values
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scene")
parser.add_argument("output_ppm")
parser.add_argument("--bounds", nargs=4, type=float, required=True,
metavar=("XMIN", "XMAX", "YMIN", "YMAX"))
parser.add_argument("--size", nargs=2, type=int, default=(640, 640))
parser.add_argument("--range", nargs=2, type=float, default=(-122.0, -118.0),
metavar=("ZMIN", "ZMAX"))
parser.add_argument("--ring-center", nargs=2, type=float,
metavar=("X", "Y"))
parser.add_argument("--layout")
args = parser.parse_args()
voxel_um, triangles = read_scene(args.scene)
width, height = args.size
sx, sy = width / (args.bounds[1] - args.bounds[0]), height / (args.bounds[3] - args.bounds[2])
values = rasterize(triangles, args.bounds, width, height)
finite = sorted(value for value in values if value is not None)
if not finite:
raise SystemExit("no upward-facing field samples in bounds")
low, high = args.range
if high <= low:
raise SystemExit("height range must increase")
stops = ((0.00, (31, 52, 92)), (0.24, (41, 119, 153)),
(0.50, (85, 177, 143)), (0.73, (226, 207, 105)),
(1.00, (181, 71, 49)))
with open(args.output_ppm, "wb") as output:
output.write(f"P6\n{width} {height}\n255\n".encode())
for value in values:
if value is None:
output.write(bytes((18, 22, 30)))
continue
t = max(0.0, min(1.0, (value - low) / (high - low)))
for index in range(len(stops) - 1):
if t <= stops[index + 1][0]:
ta, ca = stops[index]
tb, cb = stops[index + 1]
blend = (t - ta) / (tb - ta)
rgb = tuple(round(ca[c] + blend * (cb[c] - ca[c]))
for c in range(3))
break
else:
rgb = stops[-1][1]
output.write(bytes(rgb))
def quantile(p):
return finite[round((len(finite) - 1) * p)]
print(f"[RingFieldRaster] voxel_um={voxel_um} triangles={len(triangles)} "
f"pixels={len(finite)}/{len(values)} height_min_m={finite[0]:.6f} "
f"height_p05_m={quantile(.05):.6f} height_median_m={quantile(.5):.6f} "
f"height_p95_m={quantile(.95):.6f} height_max_m={finite[-1]:.6f} "
f"height_range_m={finite[-1]-finite[0]:.6f} "
f"map={args.output_ppm}")
if args.ring_center:
cx, cy = args.ring_center
def at_world(x, y):
fx = (x - args.bounds[0]) * sx - 0.5
fy = (args.bounds[3] - y) * sy - 0.5
ix, iy = math.floor(fx), math.floor(fy)
tx, ty = fx - ix, fy - iy
samples = []
for px, py in ((ix, iy), (ix + 1, iy), (ix, iy + 1), (ix + 1, iy + 1)):
if not (0 <= px < width and 0 <= py < height):
return None
value = values[py * width + px]
if value is None:
return None
samples.append(value)
return ((samples[0] * (1 - tx) + samples[1] * tx) * (1 - ty)
+ (samples[2] * (1 - tx) + samples[3] * tx) * ty)
print(f"[RingRadialProbe] center_m=({cx:.3f},{cy:.3f}) "
"radius_m mean_m angular_sd_m min_m max_m samples")
for radius in range(1, 17):
row = [at_world(cx + radius * math.cos(a * math.tau / 32),
cy + radius * math.sin(a * math.tau / 32))
for a in range(32)]
row = [value for value in row if value is not None]
if row:
mean = sum(row) / len(row)
sd = math.sqrt(sum((value - mean) ** 2 for value in row) / len(row))
print(f" {radius:>2} {mean:.6f} {sd:.6f} {min(row):.6f} "
f"{max(row):.6f} {len(row)}")
if args.layout:
layout = json.load(open(args.layout, encoding="utf-8"))
if next(item for item in layout["strates"] if item["order"] == 0)["transition_mode"] != "Gradient":
raise SystemExit("--layout must describe a Gradient owner interface")
errors = []
for index, actual in enumerate(values):
if actual is None:
continue
px, py = index % width, index // width
x = args.bounds[0] + (px + 0.5) / sx
y = args.bounds[3] - (py + 0.5) / sy
expected = gradient_height_um(layout, round(x * 1_000_000),
round(y * 1_000_000)) / 1_000_000.0
errors.append(actual - expected)
absolute = sorted(abs(error) for error in errors)
rms = math.sqrt(sum(error * error for error in errors) / len(errors))
print(f"[RingGradientMatch] samples={len(errors)} rms_m={rms:.6f} "
f"p95_abs_m={absolute[round((len(absolute)-1)*.95)]:.6f} "
f"max_abs_m={absolute[-1]:.6f} "
"expected=owner-resolved-2d-hash-value-noise")
if __name__ == "__main__":
main()
@@ -0,0 +1,28 @@
#include "CoreNamespacePreamble.h"
#include "ResolvedLayoutInternal.h"
int main()
{
VoxelForgeCore::ResolvedLayoutStratumV2 upper, lower;
upper.strateIndex=0;upper.order=0;upper.transitionMode="Gradient";
upper.transitionWidthMicrometres=16000000;
upper.bedrock.bottomMicrometres=-120000000;
upper.bedrock.topMicrometres=-119500000;
upper.bedrock.presentInCurrentField=true;
lower.strateIndex=1;lower.order=1;
lower.ceiling.bottomMicrometres=-126250000;
lower.ceiling.topMicrometres=-120000000;
lower.ceiling.presentInCurrentField=true;
for(const auto& point:std::array<std::array<std::int64_t,2>,5>{
std::array<std::int64_t,2>{64000000,-32000000},
{65000000,-32000000},{64000000,-31000000},{70000000,-32000000},
{80000000,-35000000}})
{
const auto bands=VoxelForgeCore::EvaluateResolvedLayoutTransitionBandsV1(
upper,lower,0,8000000,point[0],point[1]);
std::printf("x_m=%.3f y_m=%.3f top_m=%.6f bottom_m=%.6f\n",
point[0]/1000000.0,point[1]/1000000.0,
bands.upperBedrockTopMicrometres/1000000.0,
bands.upperBedrockBottomMicrometres/1000000.0);
}
}
@@ -0,0 +1,121 @@
# VoxelForge L30
Work is on `assembler/world`. The speed and look patches are saved separately under
`Saved/Linux/l30/`; evaluator version is now 16 for the field change. No Unreal files were edited.
Nothing was committed, pushed, or stashed.
## Ring measurement and fix
I rasterized upward-facing triangles from the L29 mesh (skirt triangles excluded) over the same
409,600 pixels as the after field. At `(64, -32)`, the L29 field stayed almost flat at `-120.063 m` from
radii 1–11 m, then broke at 12 m. The expected Gradient at radius 1 m is `-121.360 m`. This is a
field defect, not shading: the probe is about 67 m from the passage mouth, and the raster has no
skirts or readable-shading tint. The passage column flare is at that distant mouth as well.
The upper FloatingIslands generator could leave native solid density in the air-side gap above a
negatively displaced Gradient face. The constant band densities also snapped the face to Core
voxel levels. The owner Gradient path now clears that gap with positive signed distance and writes
negative signed distance below the face, allowing the mesh to interpolate its height. The after
profile follows the resolved hash surface at every sampled radius. Match error fell from RMS
`0.361765 m`, p95 `1.016178 m`, max `1.304169 m` to RMS `0.110357 m`, p95 `0.218531 m`, max
`0.248818 m` (one Core voxel is `0.25 m`).
Before/after captures use the same color ramp and bounds:
- [L29 height map](captures/fi-floor-gradient-before-heightmap.jpg), [fixed height map](captures/fi-floor-gradient-after-heightmap.jpg)
- [L29 readable render](captures/fi-floor-gradient-before.jpg), [fixed readable render](captures/fi-floor-gradient-after.jpg)
## Transition captures
The maps cover the full 640×640 surface and are all below 300 KB. Gradient is the owner world
without a mode override. Hard and Interleaved use fixture overrides; the resolved layouts record
those modes.
- [Hard](captures/transition-hard-heightmap.jpg) — flat interface.
- [Gradient](captures/transition-gradient-heightmap.jpg) — owner field.
- [Interleaved](captures/transition-interleaved-heightmap.jpg) — fixture override.
## Cost
Five alternating W12 pairs, hashing off and `cache_hits=0`. First table is the separable speed-only
build, whose hashes remain L29-identical. Second table is the final version-16 build.
| Recipe | L24 | L29 | L30 speed-only | vs L24 | L30 final | vs L24 |
|---|---:|---:|---:|---:|---:|---:|
| SurfaceWorld | 1.566 s | 1.653 s | 1.606 s | +2.55% | 1.530 s | +2.68% |
| FloatingIslands | 16.625 s | 16.779 s | 16.470 s | −0.93% | 16.543 s | +0.21% |
Counter-enabled runs on the same plain W12 workload locate the saved work:
| Recipe | Boundary calls | Window checks → evaluations | Fade calls | Boundary tile / point culls | Column Z tests → guards → radial rejects | SDF calls |
|---|---:|---:|---:|---:|---:|---:|
| SurfaceWorld | 19.95 M | 3.54 M → 739 k | 8.2 k | 8.99 M / 8.96 M | 1.87 M → 1.85 M → 1.58 M | 18.4 k |
| FloatingIslands | 66.33 M | 8.00 M → 2.68 M | 8.6 k | 38.25 M / 23.63 M | 8.17 M → 8.14 M → 6.82 M | 83.4 k |
The fade-tile cull skips 731 k SurfaceWorld and 2.67 M FloatingIslands fade evaluations; the
column guard exits on nonpositive field values 272 k and 1.27 M times. Timing comes from
uninstrumented binaries; the counts above use counter logging.
## Hashes and audits
The speed-only v15 build matches all 12 L29 hash records for each recipe/compiler/worker matrix
(W1/W8/W12), and all 12 records for each 24 m crossing matrix. That is 24 exact comparisons;
all pass. The final v16 build is deterministic across both compilers and all worker counts, in the
normal and 24 m crossing runs. Representative W12 hashes (`density / mesh / payload / topology`):
| Recipe | Full detail | Whole view |
|---|---|---|
| SurfaceWorld | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `0e8c5af01df96b14 / 70099e89b77811ed / dbce0fdcf6764126 / ddaf81a1228f9b4b` |
| FloatingIslands | `ed550e0c8b182695 / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `4ee19ea6b6406ca6 / be648f94c899b47e / 310b525f31604f98 / 10366eb6dfce2384` |
Exact-query hashes also match across v16 runs: SurfaceWorld
`c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849`; FloatingIslands
`7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf`.
- Interface audit: all 12 compiler/recipe/mode combinations pass; 4,221 columns each, zero holes
and air samples, both negative controls reject.
- The 16 L29 fixture-matrix replays pass passage safety/open-air, validator controls (10/10), L13
route parity, L9 proof checks (zero violations), and walkability. `FloorProfileBlend` still
reports 2.544 vox in style 1 and 3.645 vox in style 3, unchanged from L29.
- Audit scope caveat: the L29 files named `floating-islands` were run with
`--recipe=surface-world`. With the actual `--recipe=floating-islands` option, the rock-column
audit fails on all four styles on both L29 and L30: connected ends are 1/2, 3/4, 1/2, 3/4.
The open-air audit passes. This is inherited, but means the L29 report's “two recipes” matrix did
not exercise the FloatingIslands CLI recipe.
## Failed and retried runs
- Broad air-gap experiments first lost a SurfaceWorld style-2 support sample. Route/radius masks
and support restoration then caused open-air violations. Widening the fade restored that case,
but measurement placed the ring far outside the fade, so those variants were discarded. The
final owner-only gate and signed-distance face pass the targeted support audit.
- Early speed variants missed the target: tile-only `+4.04%`, Z-point `+3.11%`, scalar path
`+4.33%` vs L24. The final band and fade-tile culls meet the target.
- The first counter-instrumentation compile failed; the counter hook was corrected and rebuilt
with Clang and GCC. One duplicated timing sequence was discarded; only the clean five-pair runs
are reported.
- The first 24 m crossing comparison supplied an extra camera override and mismatched. Rerunning
with the L29 camera setup and only `--camera-step=24,0,0` matches all hashes.
- An early Hard export hit `std::bad_alloc`; retry passed. The first EGL render used an unwritable
cache; retry with `XDG_CACHE_HOME` under `Saved/Linux/l30` passed. The first map conversion also
stopped before the Hard/Interleaved maps; both were rerun individually and have full raster
coverage.
- The first passage-matrix summary script assumed a different marker order and reported failure;
checking each audit marker by name confirms the core matrix passes. A `check=True` true-recipe
control runner stopped on the expected L29 exit code 10; rerunning while recording return codes
confirmed the same four inherited rock-column failures on L29 and L30.
No build or run exceeded 30 minutes or remained stuck for 15 minutes. Builds and runs used CPUs
0–11, at most 12 workers, normal priority, and the 6 GiB address-space cap. No Unreal build was
attempted.
## Orchestrator review (Claude)
- Merged: the ring was a real field defect (air-side gap left solid, face snapped to voxel levels), now fixed
(RMS 0.36 → 0.11 m). Cost back to SW +2.7% / FI +0.2% vs L24. The speed part is bit-identical; final v16 is
deterministic.
- Still visible: `fi-floor-gradient-after.jpg` shows faint concentric lines. Unknown whether that is residual
geometry (≤0.25 m steps) or the readable shader's height-tint banding; L31 measures it.
- **Important finding (thanks to Luna's caveat):** L29's "both recipes" passage matrix ran SurfaceWorld twice. On
the real `--recipe=floating-islands`, the rock-column connected-ends audit fails in all 4 styles (1/2 or 3/4).
Since L27, FI column results were never really tested. L31 priority.
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Interleave five W12 hash-off whole-view cost triplets for L24/L29/L30."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l30"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"l24": ROOT / "Saved/Linux/l24-source/bin/linux/VoxelForgeCoreHeadless-linux24-clang19",
"l29": ROOT / "Saved/Linux/bin/VoxelForgeCoreHeadless-linux29-clang",
"l30": ROOT / "Saved/Linux/l30/bin/VoxelForgeCoreHeadless-linux30-speed-fade",
}
ORDERS = (
("l24", "l29", "l30"),
("l29", "l30", "l24"),
("l30", "l24", "l29"),
("l24", "l30", "l29"),
("l29", "l24", "l30"),
)
WHOLE_READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
rows = []
for recipe in ("surface-world", "floating-islands"):
for pair, order in enumerate(ORDERS, 1):
for build in order:
name = f"cost-speedfade-{recipe}-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
"prlimit", "--as=6442450944", "--", str(BINARIES[build]),
str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
]
started = time.monotonic()
with logfile.open("w", encoding="utf-8") as stream:
subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
check=True, timeout=1800)
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
matches = WHOLE_READY.findall(text)
if len(matches) != 1 or "hash=off" not in text or "cache_hits=0" not in text:
raise SystemExit(f"invalid cost run: {name}; see {logfile}")
ready = float(matches[0])
rows.append((recipe, pair, build, ready, elapsed))
print(f"{recipe} pair={pair} build={build} whole_view_ready={ready:.3f}s "
f"process_elapsed={elapsed:.3f}s", flush=True)
summary = OUT / "cost-runs-speedfade.csv"
summary.write_text("recipe,pair,build,whole_view_ready_seconds,process_elapsed_seconds\n" +
"".join(f"{recipe},{pair},{build},{ready:.6f},{elapsed:.6f}\n"
for recipe, pair, build, ready, elapsed in rows), encoding="utf-8")
print(f"wrote {summary}", flush=True)
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Verify speed-only L30 hashes on the 24 m crossing against L29."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l30"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"clang": ROOT / "Saved/Linux/l30/bin/VoxelForgeCoreHeadless-linux30-speed-fade",
"gcc": ROOT / "Saved/Linux/l30/bin/VoxelForgeCoreHeadless-linux30-speed-fade-gcc",
}
PATTERN = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
passed = True
for compiler, binary in BINARIES.items():
for recipe in ("surface-world", "floating-islands"):
reference_path = ROOT / f"Saved/Linux/linux29-final-crossing24-{compiler}-{recipe}.log"
reference = PATTERN.findall(reference_path.read_text(encoding="utf-8"))
for workers in (1, 8, 12):
name = f"crossing-speedonly-default-{compiler}-{recipe}-w{workers}"
logfile = OUT / f"{name}.log"
command = [
"prlimit", "--as=6442450944", "--", str(binary),
str(ROOT / "Standalone/data/owner_world.json"),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--stream-hash", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", f"--workers={workers}",
"--camera-step=24,0,0",
"--region-crossing", f"--recipe={recipe}",
]
started = time.monotonic()
with logfile.open("w", encoding="utf-8") as stream:
subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
check=True, timeout=1800)
elapsed = time.monotonic() - started
rows = PATTERN.findall(logfile.read_text(encoding="utf-8", errors="replace"))
ok = len(reference) == 12 and rows == reference
passed &= ok
print(f"{compiler} {recipe} W{workers} elapsed={elapsed:.3f}s "
f"hash_rows={len(rows)} {'MATCH' if ok else 'MISMATCH'}", flush=True)
print(f"[CrossingSpeedOnly] runs=12 status={'PASS' if passed else 'FAIL'}", flush=True)
raise SystemExit(0 if passed else 1)
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""Combine the initial and repeat L31 interleaved cost batches."""
import csv
import statistics
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l31"
sources = (
("first", 0, OUT / "cost-first/cost-runs.csv"),
("repeat", 5, OUT / "cost-runs.csv"),
)
rows = []
for batch, offset, path in sources:
with path.open(newline="", encoding="utf-8") as stream:
for row in csv.DictReader(stream):
row["pair"] = str(int(row["pair"]) + offset)
row["source_batch"] = batch
rows.append(row)
with (OUT / "cost-combined.csv").open("w", newline="", encoding="utf-8") as stream:
writer = csv.DictWriter(stream, fieldnames=(
"recipe", "pair", "build", "whole_view_ready_seconds", "process_elapsed_seconds",
"exit_code", "status", "source_batch"))
writer.writeheader()
writer.writerows(rows)
print("recipe pairs median_l30_s median_l31_s median_change_pct paired_median_pct range_pct")
failed = False
for recipe in ("surface-world", "floating-islands"):
by_build = {
build: [float(row["whole_view_ready_seconds"]) for row in rows
if row["recipe"] == recipe and row["build"] == build]
for build in ("l30", "l31")
}
paired = []
for pair in range(1, 11):
times = {
row["build"]: float(row["whole_view_ready_seconds"])
for row in rows if row["recipe"] == recipe and int(row["pair"]) == pair
}
paired.append((times["l31"] / times["l30"] - 1.0) * 100.0)
median_l30 = statistics.median(by_build["l30"])
median_l31 = statistics.median(by_build["l31"])
median_change = (median_l31 / median_l30 - 1.0) * 100.0
print(f"{recipe} 10 {median_l30:.3f} {median_l31:.3f} {median_change:+.2f} "
f"{statistics.median(paired):+.2f} {min(paired):+.2f}..{max(paired):+.2f}")
failed |= len(by_build["l30"]) != 10 or len(by_build["l31"]) != 10
print(f"[L31CostCombined] pairs_per_recipe=10 runs={len(rows)} "
f"status={'FAIL' if failed else 'PASS'} summary={OUT / 'cost-combined.csv'}")
raise SystemExit(1 if failed or len(rows) != 40 else 0)
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
"""Compare L25 and 24 m crossing hash records for matching recipe/compiler/workers."""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l31"
pattern = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
lines = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for compiler in ("clang", "gcc"):
for workers in (1, 8, 12):
left = pattern.findall((OUT / f"l25-{compiler}-{recipe}-w{workers}.log").read_text())
right = pattern.findall((OUT / f"crossing24-{compiler}-{recipe}-w{workers}.log").read_text())
same = bool(left) and len(left) == 12 and left == right
failed |= not same
lines.append(f"{recipe},{compiler},W{workers},{'MATCH' if same else 'MISMATCH'}")
path = OUT / "determinism-crossing-compare.log"
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
print("\n".join(lines))
print(f"[L31CrossingHashCompare] comparisons={len(lines)} "
f"pass={sum(line.endswith('MATCH') for line in lines)} fail={sum(line.endswith('MISMATCH') for line in lines)}")
raise SystemExit(1 if failed else 0)
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""Encode the six requested L31 screenshots as small, pinned JPEGs."""
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
SOURCE = ROOT / "Saved/Linux/l31"
DEST = Path(__file__).resolve().parent / "captures"
DEST.mkdir(parents=True, exist_ok=True)
names = (
"fi-column-top-close-v19",
"fi-column-top-wide-v19",
"fi-column-base-close-v19",
"fi-column-base-wide-v19",
"fi-floor-v19-tint-off",
"fi-floor-v19-tint-on",
)
failed = False
for name in names:
source = SOURCE / f"{name}.png"
destination = DEST / f"{name.removesuffix('-v19').replace('-v19-', '-')}.jpg"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "ffmpeg",
"-y", "-hide_banner", "-loglevel", "error", "-i", str(source),
"-frames:v", "1", "-q:v", "4", str(destination),
]
result = subprocess.run(command, check=False)
size = destination.stat().st_size if destination.exists() else 0
ok = result.returncode == 0 and size <= 300_000
failed |= not ok
print(f"[L31CaptureJPEG] source={source.name} output={destination.name} "
f"bytes={size} status={'PASS' if ok else 'FAIL'}")
raise SystemExit(1 if failed else 0)
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Measure surface heights and normals on an eastward FI floor radial line."""
import array
import math
import struct
import sys
def read_surface_vertices(path):
vertices = []
with open(path, "rb") as stream:
if stream.read(8) != b"VFMSH001":
raise SystemExit(f"{path}: invalid scene header")
version, voxel_um, count = struct.unpack("<3I", stream.read(12))
stream.read(40)
if version != 1 or count > 100000:
raise SystemExit("unsupported scene")
for _ in range(count):
key = struct.unpack("<4i", stream.read(16))
surface_count, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
positions = array.array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array.array("f")
normals.frombytes(stream.read(vertex_count * 12))
indices = array.array("I")
indices.frombytes(stream.read(index_count * 4))
masks = stream.read(mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
used = set()
for tri in range(0, index_count, 3):
if masks and masks[tri // 3]:
continue
for index in indices[tri:tri + 3]:
if index < surface_count:
used.add(index)
for index in used:
offset = index * 3
vertices.append((positions[offset], positions[offset + 1],
positions[offset + 2], normals[offset],
normals[offset + 1], normals[offset + 2]))
return voxel_um, vertices
def main():
scene = sys.argv[1]
center_x, center_y = 64.0, -32.0
voxel_um, vertices = read_surface_vertices(scene)
floor = [v for v in vertices if abs(v[2] + 119.5) < 0.05
and math.hypot(v[0] - center_x, v[1] - center_y) <= 16.5]
if not floor:
raise SystemExit("no floor surface vertices near expected FI bedrock top")
mean = tuple(sum(v[index] for v in floor) / len(floor) for index in (3, 4, 5))
normal_rms = math.sqrt(sum(sum((v[index] - mean[axis]) ** 2
for axis, index in enumerate((3, 4, 5)))
for v in floor) / len(floor))
min_z = min(v[2] for v in floor)
max_z = max(v[2] for v in floor)
print(f"[RingNormalField] voxel_um={voxel_um} center_m=({center_x:.3f},{center_y:.3f}) "
f"vertices={len(floor)} height_min_m={min_z:.6f} height_max_m={max_z:.6f} "
f"mean_normal=({mean[0]:.8f},{mean[1]:.8f},{mean[2]:.8f}) "
f"normal_rms={normal_rms:.9f}")
print("[RingNormalRadialLine] radius_m sampled_radius_m height_m nx ny nz")
for radius in range(1, 17):
x, y = center_x + radius, center_y
vertex = min(floor, key=lambda v: (v[0] - x) ** 2 + (v[1] - y) ** 2)
sampled_radius = math.hypot(vertex[0] - center_x, vertex[1] - center_y)
if math.hypot(vertex[0] - x, vertex[1] - y) > 0.5:
raise SystemExit(f"missing near vertex at radius {radius} m")
print(f"{radius:2d} {sampled_radius:.6f} {vertex[2]:.6f} "
f"{vertex[3]:.8f} {vertex[4]:.8f} {vertex[5]:.8f}")
if __name__ == "__main__":
main()
@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""Rasterize upward mesh triangles into a top-down field height map."""
import argparse
import array
import json
import math
import struct
import sys
Q16 = 1 << 16
MASK64 = (1 << 64) - 1
def trunc_div(numerator, denominator):
return numerator // denominator if numerator >= 0 else -((-numerator) // denominator)
def transition_hash(seed, interface, x, y, z, salt):
value = 14695981039346656037
for word in (seed & 0xffffffff, interface & 0xffffffff,
x & MASK64, y & MASK64, z & MASK64, salt):
for byte in range(8):
value ^= (word >> (byte * 8)) & 0xff
value = (value * 1099511628211) & MASK64
value ^= value >> 30
value = (value * 0xbf58476d1ce4e5b9) & MASK64
value ^= value >> 27
value = (value * 0x94d049bb133111eb) & MASK64
value ^= value >> 31
return ((value >> 32) >> 15) - Q16
def transition_fade(value):
value = max(0, min(Q16, value))
square = value * value // Q16
return square * (3 * Q16 - 2 * value) // Q16
def gradient_height_um(layout, x_um, y_um):
upper = next(item for item in layout["strates"] if item["order"] == 0)
lower = next(item for item in layout["strates"] if item["order"] == 1)
width = upper["transition_width_um"]
cell = max(1_000_000, layout["strate_layout_chunk_size_um"] * 4)
ix = x_um // cell
iy = y_um // cell
tx = transition_fade(((x_um - ix * cell) * Q16) // cell)
ty = transition_fade(((y_um - iy * cell) * Q16) // cell)
def at(dx, dy):
noise = transition_hash(0, upper["strate_index"], ix + dx, iy + dy, 0,
0x47524144)
return noise
def lerp(a, b, t):
return a + trunc_div((b - a) * t, Q16)
low = lerp(lerp(at(0, 0), at(1, 0), tx),
lerp(at(0, 1), at(1, 1), tx), ty)
amplitude_divisor = 256 if upper.get("generator_kind") == "FloatingIslands" else 8
displacement = trunc_div(low * (width // amplitude_divisor), Q16)
seam = upper["bedrock"]["bottom_um"]
thickness = upper["bedrock"]["top_um"] - seam
return seam + displacement + thickness
def read_scene(path):
triangles = []
with open(path, "rb") as stream:
if stream.read(8) != b"VFMSH001":
raise SystemExit(f"{path}: invalid scene header")
version, voxel_um, count = struct.unpack("<3I", stream.read(12))
stream.read(40)
if version != 1 or count > 100000:
raise SystemExit("unsupported scene")
for _ in range(count):
key = struct.unpack("<4i", stream.read(16))
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
positions = array.array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array.array("f")
normals.frombytes(stream.read(vertex_count * 12))
indices = array.array("I")
indices.frombytes(stream.read(index_count * 4))
masks = stream.read(mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
for index in range(0, index_count, 3):
if masks and masks[index // 3]:
continue
a, b, c = indices[index:index + 3]
if max(a, b, c) >= surface_vertices:
continue
nz = (normals[a * 3 + 2] + normals[b * 3 + 2]
+ normals[c * 3 + 2]) / 3.0
if nz < 0.35:
continue
vertices = tuple((positions[v * 3], positions[v * 3 + 1],
positions[v * 3 + 2]) for v in (a, b, c))
triangles.append(vertices)
return voxel_um, triangles
def rasterize(triangles, bounds, width, height):
x0, x1, y0, y1 = bounds
values = [None] * (width * height)
sx, sy = width / (x1 - x0), height / (y1 - y0)
for tri in triangles:
(ax, ay, az), (bx, by, bz), (cx, cy, cz) = tri
den = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
if abs(den) < 1.0e-12:
continue
left = max(0, int(math.floor((min(ax, bx, cx) - x0) * sx)))
right = min(width - 1, int(math.ceil((max(ax, bx, cx) - x0) * sx)))
top = max(0, int(math.floor((y1 - max(ay, by, cy)) * sy)))
bottom = min(height - 1, int(math.ceil((y1 - min(ay, by, cy)) * sy)))
for py in range(top, bottom + 1):
y = y1 - (py + 0.5) / sy
row = py * width
for px in range(left, right + 1):
x = x0 + (px + 0.5) / sx
u = ((by - cy) * (x - cx) + (cx - bx) * (y - cy)) / den
v = ((cy - ay) * (x - cx) + (ax - cx) * (y - cy)) / den
w = 1.0 - u - v
if min(u, v, w) < -1.0e-6:
continue
z = u * az + v * bz + w * cz
idx = row + px
if values[idx] is None or z > values[idx]:
values[idx] = z
return values
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scene")
parser.add_argument("output_ppm")
parser.add_argument("--bounds", nargs=4, type=float, required=True,
metavar=("XMIN", "XMAX", "YMIN", "YMAX"))
parser.add_argument("--size", nargs=2, type=int, default=(640, 640))
parser.add_argument("--range", nargs=2, type=float, default=(-122.0, -118.0),
metavar=("ZMIN", "ZMAX"))
parser.add_argument("--ring-center", nargs=2, type=float,
metavar=("X", "Y"))
parser.add_argument("--probe-max-radius", type=int, default=16)
parser.add_argument("--layout")
args = parser.parse_args()
voxel_um, triangles = read_scene(args.scene)
width, height = args.size
sx, sy = width / (args.bounds[1] - args.bounds[0]), height / (args.bounds[3] - args.bounds[2])
values = rasterize(triangles, args.bounds, width, height)
finite = sorted(value for value in values if value is not None)
if not finite:
raise SystemExit("no upward-facing field samples in bounds")
low, high = args.range
if high <= low:
raise SystemExit("height range must increase")
stops = ((0.00, (31, 52, 92)), (0.24, (41, 119, 153)),
(0.50, (85, 177, 143)), (0.73, (226, 207, 105)),
(1.00, (181, 71, 49)))
with open(args.output_ppm, "wb") as output:
output.write(f"P6\n{width} {height}\n255\n".encode())
for value in values:
if value is None:
output.write(bytes((18, 22, 30)))
continue
t = max(0.0, min(1.0, (value - low) / (high - low)))
for index in range(len(stops) - 1):
if t <= stops[index + 1][0]:
ta, ca = stops[index]
tb, cb = stops[index + 1]
blend = (t - ta) / (tb - ta)
rgb = tuple(round(ca[c] + blend * (cb[c] - ca[c]))
for c in range(3))
break
else:
rgb = stops[-1][1]
output.write(bytes(rgb))
def quantile(p):
return finite[round((len(finite) - 1) * p)]
print(f"[RingFieldRaster] voxel_um={voxel_um} triangles={len(triangles)} "
f"pixels={len(finite)}/{len(values)} height_min_m={finite[0]:.6f} "
f"height_p05_m={quantile(.05):.6f} height_median_m={quantile(.5):.6f} "
f"height_p95_m={quantile(.95):.6f} height_max_m={finite[-1]:.6f} "
f"height_range_m={finite[-1]-finite[0]:.6f} "
f"map={args.output_ppm}")
if args.ring_center:
cx, cy = args.ring_center
def at_world(x, y):
fx = (x - args.bounds[0]) * sx - 0.5
fy = (args.bounds[3] - y) * sy - 0.5
ix, iy = math.floor(fx), math.floor(fy)
tx, ty = fx - ix, fy - iy
samples = []
for px, py in ((ix, iy), (ix + 1, iy), (ix, iy + 1), (ix + 1, iy + 1)):
if not (0 <= px < width and 0 <= py < height):
return None
value = values[py * width + px]
if value is None:
return None
samples.append(value)
return ((samples[0] * (1 - tx) + samples[1] * tx) * (1 - ty)
+ (samples[2] * (1 - tx) + samples[3] * tx) * ty)
print(f"[RingRadialProbe] center_m=({cx:.3f},{cy:.3f}) "
"radius_m mean_m angular_sd_m min_m max_m samples")
for radius in range(1, args.probe_max_radius + 1):
row = [at_world(cx + radius * math.cos(a * math.tau / 32),
cy + radius * math.sin(a * math.tau / 32))
for a in range(32)]
row = [value for value in row if value is not None]
if row:
mean = sum(row) / len(row)
sd = math.sqrt(sum((value - mean) ** 2 for value in row) / len(row))
print(f" {radius:>2} {mean:.6f} {sd:.6f} {min(row):.6f} "
f"{max(row):.6f} {len(row)}")
if args.layout:
layout = json.load(open(args.layout, encoding="utf-8"))
if next(item for item in layout["strates"] if item["order"] == 0)["transition_mode"] != "Gradient":
raise SystemExit("--layout must describe a Gradient owner interface")
errors = []
for index, actual in enumerate(values):
if actual is None:
continue
px, py = index % width, index // width
x = args.bounds[0] + (px + 0.5) / sx
y = args.bounds[3] - (py + 0.5) / sy
expected = gradient_height_um(layout, round(x * 1_000_000),
round(y * 1_000_000)) / 1_000_000.0
errors.append(actual - expected)
absolute = sorted(abs(error) for error in errors)
rms = math.sqrt(sum(error * error for error in errors) / len(errors))
print(f"[RingGradientMatch] samples={len(errors)} rms_m={rms:.6f} "
f"p95_abs_m={absolute[round((len(absolute)-1)*.95)]:.6f} "
f"max_abs_m={absolute[-1]:.6f} "
"expected=owner-resolved-2d-hash-value-noise")
if __name__ == "__main__":
main()
@@ -0,0 +1,107 @@
# Linux31 report
## FI column finding
The earlier L29/L30 passage table ran SurfaceWorld twice. On the real FloatingIslands recipe, the old column check under-counted connected ends because it treated support at the route foot as the column cap. The route foot is near z = −116 m; the actual lower cap is near z = −122 m, inside native bedrock whose top is z = −119.5 m. The upper caps sit inside anchor-island rock near z = −62 to −69 m. There is no floating column end or unintended gap. The open passage mouth is intentional.
The audit now samples each planned cap against the native density field. Negative density is solid rock. Every main column cap overlaps native rock within 0.25 m. The style 3 short lower cap overlaps within 0.50 m.
| FI style | Cap | Z (m) | Native overlap / samples | Minimum native density |
|---:|---|---:|---:|---:|
| 0 | Main upper / anchor island | −65.846 | 5,051 / 10,005 | −14.761719 |
| 0 | Main lower / bedrock | −122.390 | 6,149 / 12,588 | −8.000000 |
| 1 | Short upper | −61.574 | 9,725 / 11,033 | −23.464844 |
| 1 | Short lower | −75.637 | 3,915 / 11,500 | −15.554688 |
| 1 | Main upper / anchor island | −66.485 | 4,497 / 11,118 | −15.613281 |
| 1 | Main lower / bedrock | −122.416 | 6,673 / 13,735 | −8.000000 |
| 2 | Main upper / anchor island | −68.891 | 3,986 / 8,523 | −15.546875 |
| 2 | Main lower / bedrock | −122.382 | 5,396 / 10,885 | −8.000000 |
| 3 | Short upper | −65.805 | 5,095 / 10,064 | −14.671875 |
| 3 | Short lower | −80.054 | 2,498 / 10,413 | −11.910156 |
| 3 | Main upper / anchor island | −69.478 | 3,902 / 9,985 | −15.597656 |
| 3 | Main lower / bedrock | −122.436 | 6,130 / 12,453 | −8.000000 |
The negative control moves the style 0 lower cap into the air gap at z = −96.390 m: 0/3,120 native overlaps, minimum density +22.273438, and `detected=true`. The audit still catches a genuinely disconnected end.
## Recipe-stamped passage matrix
Each row is checked against the requested and resolved recipe stamp. The columns are capsule blocked/unsupported counts and mouth checks, open-air violations, naturalness, longest uncolumned span / 12.5 m limit, connected column ends, route bindings / failures, and walkability samples.
For all eight FI rows, `FloatingIslandRouteAudit` also passes: 78,064 capsule samples, 0 blocked, 0 unsupported, and 0 grade failures. The runner now asserts both capsule audits.
| Requested → actual | Style | Compiler | Capsule | Open air | Natural | Long bridge | Column ends | Bindings | Walkability |
|---|---:|---|---|---:|---:|---:|---:|---:|---:|
| surface-world → surface-world | 0 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 5642/5642 |
| surface-world → surface-world | 0 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 5642/5642 |
| surface-world → surface-world | 1 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1490/1490 |
| surface-world → surface-world | 1 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1490/1490 |
| surface-world → surface-world | 2 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1545/1545 |
| surface-world → surface-world | 2 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1545/1545 |
| surface-world → surface-world | 3 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1495/1495 |
| surface-world → surface-world | 3 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 1 (0 failures) | 1495/1495 |
| floating-islands → floating-islands | 0 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 4.464/12.500 m | 2/2 | 2 (0 failures) | 4000/4000 |
| floating-islands → floating-islands | 0 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 4.464/12.500 m | 2/2 | 2 (0 failures) | 4000/4000 |
| floating-islands → floating-islands | 1 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 3.851/12.500 m | 4/4 | 2 (0 failures) | 1047/1047 |
| floating-islands → floating-islands | 1 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 3.851/12.500 m | 4/4 | 2 (0 failures) | 1047/1047 |
| floating-islands → floating-islands | 2 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 2 (0 failures) | 1135/1135 |
| floating-islands → floating-islands | 2 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 0.000/12.500 m | 2/2 | 2 (0 failures) | 1135/1135 |
| floating-islands → floating-islands | 3 | clang | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 4.243/12.500 m | 4/4 | 2 (0 failures) | 1061/1061 |
| floating-islands → floating-islands | 3 | gcc | 0/0 blocked/unsupported; mouth 2/2 | 0 violations | 1/1 | 4.243/12.500 m | 4/4 | 2 (0 failures) | 1061/1061 |
All 16 rows pass. The runner rejects a recipe mismatch. Its negative control requests SurfaceWorld while the resolved layout is FloatingIslands; the log records `requested=surface-world actual=floating-islands status=FAIL` and the process exits 2. Logs and the machine-readable matrix are under `Saved/Linux/l31/`.
## FI floor lines
The L30 rings persisted with height tint off, so they were geometry. Evaluator v19 reduces FloatingIslands Gradient displacement to `transition_width / 256` (one quarter voxel at the 16 m transition width), and the layout validator uses the same bound. The v18 one-voxel cap still left a 0.251819 m radial height range and a visible contour.
On the east radial line from (64, −32), samples at radii 1–16 m all have height −119.500000 m and normal (0, 0, 1). The extended mesh raster has a 0.000479 m total height range. The readable shader now applies a cubic smooth tint (`t²(3−2t)`). Tint-off and tint-on captures show a flat floor without contour bands.
## Captures and hashes
All six JPEGs are recipe-stamped from FloatingIslands and under 300 KB.
| Capture | Size | SHA-256 |
|---|---:|---|
| [FI column top, close](captures/fi-column-top-close.jpg) | 85,696 B | `6b651655fc2127bb5b3502a1a99c024167db2edc21bfe7241f6335a858bb8575` |
| [FI column top, wide](captures/fi-column-top-wide.jpg) | 67,043 B | `926193affd463492095b97310c62416910e12fc94e75d8548a01ad1f9601b6ec` |
| [FI column bedrock base, close](captures/fi-column-base-close.jpg) | 60,491 B | `3e524e0f6c506413b4b5b29d8835792f8b5240f95787c6a08d12ebe6e46aadd5` |
| [FI column bedrock base, wide](captures/fi-column-base-wide.jpg) | 61,543 B | `27ef1a72874c7f8e257969c5e69d614f336edce84693ee80b98783b9f610a4b2` |
| [FI floor, tint off](captures/fi-floor-tint-off.jpg) | 43,405 B | `41b9180c9696c7025e23009e6efdb2e86ace5900b08c293c92371797f9a8596d` |
| [FI floor, tint on](captures/fi-floor-tint-on.jpg) | 43,405 B | `0672e2ddc93fb6a4fe4f6adfdf4cdee896bdbc90dddfb7cf65050d978f86f32b` |
Evaluator version is 19. Binary SHA-256:
- Clang: `a796d433b7e31fa05abba970dbd486db1a904d13222d9c02b9054c394375e4f7`
- GCC: `c2ed12ddf4db5ce97fcd2d9c8f32e4515a4fba6d36d4d8a4b5fbb07a8ab8ae73`
## Determinism and cost
The L25 command and 24 m crossing each passed 12 cases: Clang and GCC at W1/W8/W12 for both recipes. All 12 hash records per log were stable across compilers and worker counts. SW matches the L30 baseline; FI changes as expected with the v19 geometry. L25 and crossing hashes match for all 12 recipe/compiler/worker combinations.
| Recipe | Full-detail density / mesh / payload / topology | Whole-view density / mesh / payload / topology |
|---|---|---|
| SurfaceWorld | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `0e8c5af01df96b14 / 70099e89b77811ed / dbce0fdcf6764126 / ddaf81a1228f9b4b` |
| FloatingIslands | `ed550e0c8b182695 / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `52e0b9f95da4291e / 7c6e9e18346278a4 / 59160fe3df02a0ae / ef7f17088fe3253e` |
Cost compared the Clang L31 binary with the L30 `linux30-speed-fade` binary: 10 interleaved pairs per recipe, 40 runs total, with zero cache hits. Combined median whole-view time was 1.772 s → 1.784 s for SW (+0.71%) and 17.468 s → 17.482 s for FI (+0.08%). The second five-pair batch was +1.20% SW and +0.01% FI; the larger early SW deltas settled as runs warmed. The paired medians across all 10 were +1.86% SW and +0.55% FI, within the observed run variation.
## Failed and retried runs
- Five low-clip floor exports (`export-fi-floor.log`, `export-fi-floor-probe.log`, `export-fi-floor-without-mesh.log`, `export-fi-floor-probe-retry.log`, `fi-floor-no-camera-probe.log`) stopped with `std::bad_alloc`. Export succeeded with `--max-clip-level=8 --max-level=0`.
- A first radial analyzer invocation omitted the second `--range` value and was rejected by argparse; it was rerun with both bounds.
- A proof/parity probe used custom camera positions and failed SW parity because the sampled tile set differed from the established default-camera audit. That probe was stopped; the default-camera proof and transition suite passed 16/16.
- The first determinism launch ended before completing the matrix. The full 24-case run passed on retry. A first cost batch had an early SW timing outlier, so five additional interleaved pairs per recipe were run and retained in the combined summary.
- Early capture framing probes cropped the FI axis or placed the camera inside rock. A v19 wide floor view exposed only the export skirt edge; the final centered column and square top-down floor views were used.
- The first GCC build attempt was interrupted at 19 objects and retried successfully. One v18 diagnostic build used the default Standalone build path; those artifacts were removed, and final v19 build outputs are under `Saved/Linux/l31/`.
- Two one-line post-processing commands had quoting/pair-index errors; the checked-in `CompareDeterminismL31.py` and `AggregateCostL31.py` helpers produced the final passing summaries.
No run exceeded 30 minutes or remained stuck for 15 minutes. No Unreal files were edited. Nothing was committed, pushed, or stashed.
## Orchestrator review (Claude)
- Merged as an intermediate step: correct FI column-cap audit (with negative control), a recipe-stamped runner that
rejects mismatches, all 16 recipe/style/compiler rows passing, smooth shader tint, determinism.
- **Not accepted as the final floor fix:** v19 caps the FI Gradient displacement at `width/256` (≈6 cm), which
removes the owner's Gradient transition rather than making it smooth; the floor is now flat (radial
height range 0.0005 m). The real fix is a smooth, non-terraced undulation at the L29 amplitude (≈4 m
peak-to-peak). L32 restores it.
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Run the recipe-stamped L31 passage audit matrix and reject mislabeled runs."""
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l31"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-linux31-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-linux31-gcc",
}
MARKERS = (
"[ResolvedPassageAuditSummary]",
"[PassageOpenAirAuditSummary]",
"[PassageNaturalnessSummary]",
"[PassageNoLongBridgeAudit]",
"[PassageRockColumnAuditSummary]",
)
rows = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for style in range(4):
data = ROOT / f"Saved/Linux/linux25-owner-{recipe}-style{style}.json"
for compiler, binary in BINARIES.items():
name = f"passage-{recipe}-style{style}-{compiler}"
logfile = OUT / f"{name}.log"
command = [
str(binary), str(data), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate",
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", "--audit-resolved-layout-passages",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
recipe_ok = stamps == [(recipe, recipe, "PASS")]
markers_ok = all(re.search(re.escape(marker) + r".*status=PASS", text)
for marker in MARKERS)
capsule_ok = bool(re.search(
r"^\[ResolvedPassageAudit\].* blocked=0 support_samples=\d+ "
r"unsupported=0 grade_failures=0 mouth_content=2/2 mouth_radius=2/2 "
r"walkability=PASS$", text, re.M))
island_route_ok = (recipe != "floating-islands" or bool(re.search(
r"^\[FloatingIslandRouteAudit\].* blocked=0 support_samples=\d+ "
r"unsupported=0 grade_failures=0 status=PASS$", text, re.M)))
binding_ok = bool(re.search(
r"\[ResolvedPassageAuditSummary\].*route_bindings=[1-9][0-9]* "
r"route_binding_failures=0 .*status=PASS", text))
walk_ok = "[Walkability] PASS" in text
if recipe == "floating-islands":
control_ok = "[PassageColumnDisconnectedEndControl]" in text and bool(
re.search(r"\[PassageColumnDisconnectedEndControl\].*detected=true", text))
else:
control_ok = True
ok = (result is not None and result.returncode == 0 and recipe_ok
and markers_ok and capsule_ok and island_route_ok and binding_ok
and walk_ok and control_ok)
actual_recipe = stamps[0][1] if len(stamps) == 1 else "MISSING"
rows.append((recipe, actual_recipe, style, compiler, elapsed,
result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s exit={result.returncode if result else 'TIMEOUT'} "
f"recipe={stamps[0][1] if stamps else 'MISSING'} "
f"capsule={'PASS' if capsule_ok and island_route_ok else 'FAIL'} "
f"column_control={'PASS' if control_ok else 'FAIL'} {'PASS' if ok else 'FAIL'}",
flush=True)
if not ok:
failed = True
summary = OUT / "passage-audit-matrix.csv"
summary.write_text(
"requested_recipe,actual_recipe,style,compiler,elapsed_seconds,exit_code,status\n" +
"".join(f"{requested},{actual},{style},{compiler},{elapsed:.3f},{code},"
f"{'PASS' if ok else 'FAIL'}\n"
for requested, actual, style, compiler, elapsed, code, ok in rows), encoding="utf-8")
control_log = OUT / "recipe-mismatch-control.log"
control_owner = OUT / "recipe-mismatch-control-owner.json"
control_owner_data = json.loads(
(ROOT / "Saved/Linux/linux25-owner-surface-world-style0.json").read_text(encoding="utf-8"))
top_owner_stratum = max(control_owner_data["strates"], key=lambda stratum: stratum["top_chunk_z"])
top_owner_stratum["asset"]["GeneratorType"] = 6 # ECaveGeneratorType::FloatingIslands
control_owner.write_text(json.dumps(control_owner_data, separators=(",", ":")), encoding="utf-8")
control_command = [
str(BINARIES["clang"]),
str(control_owner),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", "--recipe=surface-world",
"--audit-resolved-layout-passages",
]
with control_log.open("w", encoding="utf-8") as stream:
control = subprocess.run(control_command, stdout=stream, stderr=subprocess.STDOUT,
timeout=1800, check=False)
control_text = control_log.read_text(encoding="utf-8", errors="replace")
control_ok = (control.returncode == 2 and
"[WorldAssemblerRecipeAudit] requested=surface-world actual=floating-islands "
"top_generator=FloatingIslands " in control_text and
"floating_islands_stratum=present status=FAIL" in control_text)
print(f"[L31RecipeMismatchControl] requested=surface-world actual=floating-islands "
f"exit={control.returncode} detected={'true' if control_ok else 'false'} "
f"{'PASS' if control_ok else 'FAIL'}", flush=True)
print(f"[L31PassageMatrix] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or not control_ok or len(rows) != 16 else 0)
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""Run five interleaved W12 whole-view pairs against the final L30 binary."""
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l31"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"l30": ROOT / "Saved/Linux/l30/bin/VoxelForgeCoreHeadless-linux30-speed-fade",
"l31": OUT / "bin/VoxelForgeCoreHeadless-linux31-clang",
}
ORDERS = (("l30", "l31"), ("l31", "l30"), ("l30", "l31"),
("l31", "l30"), ("l30", "l31"))
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
rows = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for pair, order in enumerate(ORDERS, 1):
for build in order:
name = f"cost-{recipe}-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
str(BINARIES[build]), str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
matches = READY.findall(text)
if build == "l31":
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
actual_recipe = stamps[0][1] if len(stamps) == 1 else "missing"
stamp_ok = stamps == [(recipe, recipe, "PASS")]
else:
configured = re.findall(
r"^\[HeadlessConfig\].*\brecipe=([^\s]+)", text, re.M)
actual_recipe = configured[0] if len(configured) == 1 else "missing"
floating_plan = "[FloatingIslandWorldAssembly]" in text
surface_plan = "[WorldAssemblerPrototype]" in text
plan_matches = ((actual_recipe == "floating-islands" and floating_plan)
or (actual_recipe == "surface-world" and not floating_plan
and surface_plan))
marker = (f"[L31BaselineRecipeAudit] requested={recipe} actual={actual_recipe} "
f"headless_recipe={configured[0] if len(configured) == 1 else 'missing'} "
f"floating_island_plan={'present' if floating_plan else 'absent'} "
f"surface_world_plan={'present' if surface_plan else 'absent'} "
f"status={'PASS' if actual_recipe == recipe and plan_matches else 'FAIL'}\n")
with logfile.open("a", encoding="utf-8") as stream:
stream.write(marker)
text += marker
stamp_ok = (actual_recipe == recipe and plan_matches
and len(configured) == 1)
valid = (result is not None and result.returncode == 0 and len(matches) == 1
and "hash=off" in text and "cache_hits=0" in text)
if build == "l31":
valid = valid and stamp_ok and actual_recipe == recipe
else:
valid = valid and stamp_ok and "status=PASS" in marker
ready = float(matches[0]) if len(matches) == 1 else float("nan")
rows.append((recipe, pair, build, ready, elapsed,
result.returncode if result else "TIMEOUT", valid))
print(f"{recipe} pair={pair} build={build} whole_view_ready={ready:.3f}s "
f"process_elapsed={elapsed:.1f}s {'PASS' if valid else 'FAIL'}", flush=True)
failed |= not valid
summary = OUT / "cost-runs.csv"
summary.write_text(
"recipe,pair,build,whole_view_ready_seconds,process_elapsed_seconds,exit_code,status\n" +
"".join(f"{recipe},{pair},{build},{ready:.6f},{elapsed:.3f},{code},"
f"{'PASS' if ok else 'FAIL'}\n"
for recipe, pair, build, ready, elapsed, code, ok in rows), encoding="utf-8")
for recipe in ("surface-world", "floating-islands"):
by_build = {build: [row[3] for row in rows if row[0] == recipe and row[2] == build]
for build in BINARIES}
l30 = statistics.median(by_build["l30"])
l31 = statistics.median(by_build["l31"])
paired = []
for pair in range(1, 6):
values = {row[2]: row[3] for row in rows if row[0] == recipe and row[1] == pair}
paired.append((values["l31"] - values["l30"]) / values["l30"] * 100.0)
print(f"[L31CostSummary] recipe={recipe} pairs=5 median_l30_s={l30:.3f} "
f"median_l31_s={l31:.3f} median_change_pct={(l31/l30-1)*100.0:+.2f} "
f"paired_change_pct={','.join(f'{value:+.2f}' for value in paired)}", flush=True)
print(f"[L31Cost] runs={len(rows)} status={'FAIL' if failed else 'PASS'} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 20 else 0)
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Re-run the L25 hash command and 24 m crossing on the L31 compiler matrix."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l31"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-linux31-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-linux31-gcc",
}
HASH_PATTERN = re.compile(
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
rows = []
failed = False
candidate_reference = {}
fi_hash_changes = {"l25": False, "crossing24": False}
def hashes(path):
return HASH_PATTERN.findall(path.read_text(encoding="utf-8", errors="replace"))
def run(kind, compiler, recipe, workers):
global failed
name = f"{kind}-{compiler}-{recipe}-w{workers}"
logfile = OUT / f"{name}.log"
command = [
str(BINARIES[compiler]), str(ROOT / "Standalone/data/owner_world.json"),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--stream-hash", "--force-generate", "--clip-radius=3", "--max-clip-level=8",
"--far-horizon-lod8-step=0", f"--workers={workers}", f"--recipe={recipe}",
]
if kind == "crossing24":
command.extend(("--camera-step=24,0,0", "--region-crossing"))
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
current = hashes(logfile)
# The normal L30 log stem is ringfix-hash; crossing uses ringfix-crossing24.
baseline = ROOT / "Saved/Linux/l30" / (
f"ringfix-crossing24-{compiler}-{recipe}-w{workers}.log" if kind == "crossing24"
else f"ringfix-hash-{compiler}-{recipe}-w{workers}.log")
reference = hashes(baseline) if baseline.is_file() else []
key = (kind, recipe)
prior = candidate_reference.get(key)
stable = prior is None or current == prior
if prior is None and current:
candidate_reference[key] = current
baseline_match = bool(reference) and current == reference
baseline_changed = bool(reference) and current != reference
if recipe == "surface-world":
baseline_ok = baseline_match
comparison = "MATCH" if baseline_match else "MISMATCH"
else:
baseline_ok = baseline_changed
comparison = "EXPECTED_CHANGE" if baseline_changed else "UNCHANGED_OR_MISSING"
fi_hash_changes[kind] |= baseline_changed
ok = (result is not None and result.returncode == 0
and stamps == [(recipe, recipe, "PASS")]
and "hash=on" in text and len(current) == 12 and stable and baseline_ok)
rows.append((kind, recipe, compiler, workers, elapsed,
result.returncode if result else "TIMEOUT", len(current), ok))
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
f"hashes={len(current)} "
f"L30={comparison} candidate_stable={'yes' if stable else 'no'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
if not ok:
failed = True
for kind in ("l25", "crossing24"):
for recipe in ("surface-world", "floating-islands"):
for compiler in ("clang", "gcc"):
for workers in (1, 8, 12):
run("crossing24" if kind == "crossing24" else "l25", compiler, recipe, workers)
summary = OUT / "determinism.csv"
summary.write_text(
"kind,recipe,compiler,workers,elapsed_seconds,exit_code,hash_rows,status\n" +
"".join(f"{kind},{recipe},{compiler},{workers},{elapsed:.3f},{code},{count},"
f"{'PASS' if ok else 'FAIL'}\n"
for kind, recipe, compiler, workers, elapsed, code, count, ok in rows),
encoding="utf-8")
for kind, changed in fi_hash_changes.items():
if not changed:
failed = True
print(f"[L31ExpectedGeometryHashChange] kind={kind} detected=false FAIL", flush=True)
else:
print(f"[L31ExpectedGeometryHashChange] kind={kind} detected=true PASS", flush=True)
print(f"[L31Determinism] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 24 else 0)
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Run L31 route/proof and transition invariants with recipe-stamped logs."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l31"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-linux31-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-linux31-gcc",
}
rows = []
failed = False
def run(name, binary, data, recipe, extra, checks):
global failed
logfile = OUT / f"{name}.log"
command = [
str(binary), str(data), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate",
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", *extra,
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
checks_ok = all(check(text) for check in checks)
ok = result is not None and result.returncode == 0 and stamp_ok and checks_ok
rows.append((name, recipe, elapsed, result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
f"stamp={'PASS' if stamp_ok else 'FAIL'} checks={'PASS' if checks_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
failed |= not ok
owner = ROOT / "Standalone/data/owner_world.json"
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
name = f"invariant-route-proof-{recipe}-{compiler}"
run(name, binary, owner, recipe,
["--stream-hash", "--audit-uniform-block-proofs", "--audit-route-parity"],
[lambda text: bool(re.search(
r"^\[RouteParityAudit\].*status=PASS$", text, re.M)),
lambda text: bool(re.search(
r"^\[UniformBlockProofAudit\].*violations=0(?:\s|$)", text, re.M))])
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
for mode in ("hard", "gradient", "interleaved"):
name = f"invariant-transition-{recipe}-{mode}-{compiler}"
run(name, binary, owner, recipe,
[f"--transition-mode={mode}", "--audit-strate-transitions"],
[lambda text: bool(re.search(
r"^\[StrateTransitionAuditSummary\].*hole_columns=0 air_samples=0 .*status=PASS$",
text, re.M)),
lambda text: bool(re.search(
r"^\[StrateTransitionNegativeControls\].*status=PASS$", text, re.M))])
summary = OUT / "invariant-audits.csv"
summary.write_text(
"run,recipe,elapsed_seconds,exit_code,status\n" +
"".join(f"{name},{recipe},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
for name, recipe, elapsed, code, ok in rows),
encoding="utf-8")
print(f"[L31InvariantAudits] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 16 else 0)
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""Extract the validated recipe-stamped passage matrix into a compact table."""
import csv
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l31"
rows = []
for recipe in ("surface-world", "floating-islands"):
for style in range(4):
for compiler in ("clang", "gcc"):
name = f"passage-{recipe}-style{style}-{compiler}"
path = OUT / f"{name}.log"
text = path.read_text(encoding="utf-8", errors="replace")
def match(pattern):
found = re.search(pattern, text, re.M)
if not found:
raise SystemExit(f"{path}: missing metric {pattern}")
return found
stamp = match(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=(PASS|FAIL)$")
capsule = match(
r"^\[ResolvedPassageAudit\].*capsule_samples=(\d+) blocked=(\d+) "
r"support_samples=(\d+) unsupported=(\d+) grade_failures=(\d+) "
r"mouth_content=([^ ]+) mouth_radius=([^ ]+) .*$")
air = match(r"^\[PassageOpenAirAuditSummary\].*violations=(\d+).*status=(PASS|FAIL)$")
natural = match(r"^\[PassageNaturalnessSummary\] passed=(\d+)/(\d+).*$")
bridge = match(
r"^\[PassageNoLongBridgeAudit\].*longest_uncolumned_m=([0-9.]+) "
r"threshold_m=([0-9.]+).*$")
column = match(
r"^\[PassageRockColumnAuditSummary\] columns=(\d+) connected_ends=([^ ]+) "
r"short_spans=(\d+) wall_or_support_failures=(\d+).*status=(PASS|FAIL)$")
binding = match(
r"^\[ResolvedPassageAuditSummary\].*route_bindings=(\d+) "
r"route_binding_failures=(\d+).*status=(PASS|FAIL)$")
walk = match(r"^\[Walkability\] PASS (\d+)/(\d+) passage centerline samples")
rows.append({
"requested": stamp.group(1), "actual": stamp.group(2), "stamp": stamp.group(3),
"style": style, "compiler": compiler,
"capsule_samples": int(capsule.group(1)), "blocked": int(capsule.group(2)),
"support_samples": int(capsule.group(3)),
"unsupported": int(capsule.group(4)), "grade_failures": int(capsule.group(5)),
"mouth_content": capsule.group(6), "mouth_radius": capsule.group(7),
"air_violations": int(air.group(1)), "air_status": air.group(2),
"natural_passed": natural.group(1),
"natural_total": natural.group(2),
"longest_uncolumned_m": bridge.group(1), "bridge_threshold_m": bridge.group(2),
"column_count": int(column.group(1)), "connected_ends": column.group(2),
"short_spans": int(column.group(3)), "column_failures": int(column.group(4)),
"route_bindings": int(binding.group(1)),
"binding_failures": int(binding.group(2)),
"walk_samples": int(walk.group(1)), "walk_total": int(walk.group(2)),
"log": path.name,
})
with (OUT / "passage-audit-metrics.csv").open("w", newline="", encoding="utf-8") as stream:
writer = csv.DictWriter(stream, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
lines = [
"| Requested → actual | Style | Compiler | Capsule | Open air | Natural | Long bridge | Column ends | Bindings | Walkability |",
"|---|---:|---|---|---:|---:|---:|---:|---:|---:|",
]
for row in rows:
ok = (row["requested"] == row["actual"] and row["stamp"] == "PASS"
and row["blocked"] == row["unsupported"] == row["grade_failures"] == 0
and row["mouth_content"] == row["mouth_radius"] == "2/2"
and row["air_violations"] == 0 and row["air_status"] == "PASS"
and row["natural_passed"] == row["natural_total"] == "1"
and float(row["longest_uncolumned_m"]) <= float(row["bridge_threshold_m"])
and row["connected_ends"] == f"{2 * row['column_count']}/{2 * row['column_count']}"
and row["column_failures"] == 0 and row["binding_failures"] == 0
and row["route_bindings"] > 0 and row["walk_samples"] == row["walk_total"])
if not ok:
raise SystemExit(f"{row['log']}: metric audit failed")
cells = (
f"{row['blocked']}/{row['unsupported']} blocked/unsupported; mouth 2/2",
f"{row['air_violations']} violations",
f"{row['natural_passed']}/{row['natural_total']}",
f"{float(row['longest_uncolumned_m']):.3f}/{row['bridge_threshold_m']} m",
row["connected_ends"],
f"{row['route_bindings']} (0 failures)",
f"{row['walk_samples']}/{row['walk_total']}",
)
lines.append(f"| {row['requested']} → {row['actual']} | {row['style']} | {row['compiler']} | "
+ " | ".join(cells) + " |")
table = OUT / "passage-audit-table.md"
table.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"[L31PassageAuditTable] rows={len(rows)} pass={len(rows)} "
f"csv={OUT / 'passage-audit-metrics.csv'} markdown={table}")
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Audit the displaced floor across the passage-mouth fade edge."""
import argparse
import math
import resource
import sys
import AuditRingTerraceL32 as ring
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scene")
parser.add_argument("--center", nargs=2, type=float, required=True)
parser.add_argument("--lod", type=int, default=0)
parser.add_argument("--inner-radius-m", type=float, default=5.5)
parser.add_argument("--outer-radius-m", type=float, default=9.5)
parser.add_argument("--step-m", type=float, default=0.125)
parser.add_argument("--height-min-m", type=float, default=-122.5)
parser.add_argument("--height-max-m", type=float, default=-117.5)
parser.add_argument("--preferred-height-m", type=float, default=-119.5)
parser.add_argument("--highest-surface", action="store_true",
help="choose the topmost upward face inside the height window")
parser.add_argument("--plateau-min-m", type=float, default=0.5)
parser.add_argument("--step-threshold-m", type=float, default=0.1)
parser.add_argument("--normal-jump-max-deg", type=float, default=20.0)
args = parser.parse_args()
if (args.inner_radius_m < 0.0 or args.outer_radius_m <= args.inner_radius_m
or args.step_m <= 0.0):
parser.error("invalid radial interval or sample spacing")
cx, cy = args.center
preferred_height = None if args.highest_surface else args.preferred_height_m
bounds = (cx - args.outer_radius_m, cx + args.outer_radius_m,
cy - args.outer_radius_m, cy + args.outer_radius_m)
voxel_um, triangles, selected_tiles = ring.read_scene(
args.scene, args.lod, args.height_min_m, args.height_max_m, bounds)
bucket_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << args.lod))
buckets = ring.build_buckets(triangles, True, bounds, bucket_size)
count = round((args.outer_radius_m - args.inner_radius_m) / args.step_m)
lines = []
for angle_degrees in (135.0, 180.0, 225.0, 270.0, 315.0):
angle = math.radians(angle_degrees)
points = [(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
for radius in (args.inner_radius_m + i * args.step_m
for i in range(count + 1))]
lines.append((f"radial-{angle_degrees:.0f}deg", points))
# Tangential sections stay outside the open mouth while crossing the outer
# fade edge on both sides of the 6.5 m center radius.
tangent_count = round(8.0 / args.step_m)
tangent_values = [-4.0 + i * args.step_m for i in range(tangent_count + 1)]
for angle_degrees in (225.0, 315.0):
angle = math.radians(angle_degrees)
normal = (math.cos(angle), math.sin(angle))
tangent = (-normal[1], normal[0])
points = [(cx + 6.5 * normal[0] + t * tangent[0],
cy + 6.5 * normal[1] + t * tangent[1])
for t in tangent_values]
lines.append((f"straight-tangent-{angle_degrees:.0f}deg", points))
failed = False
max_plateau = 0.0
max_step = 0.0
max_normal = 0.0
min_coverage = 1.0
for name, points in lines:
profile = ring.audit_profile(points, buckets, True, args.plateau_min_m,
0.0005, args.step_threshold_m, bucket_size, preferred_height)
samples = [ring.sample_surface(buckets, x, y, True, bucket_size,
preferred_height)
for x, y in points]
for index in range(1, len(samples)):
prior, current = samples[index - 1], samples[index]
if prior is None or current is None:
continue
dot = max(-1.0, min(1.0, sum(a * b for a, b in zip(prior[1], current[1]))))
delta = math.degrees(math.acos(dot))
if delta > args.normal_jump_max_deg:
px, py = points[index - 1]
x, y = points[index]
print(f"[PassageFadeNormalJump] line={name} "
f"radius_m={math.hypot(x-cx,y-cy):.3f} "
f"from_m=({px:.3f},{py:.3f},{prior[0]:.3f}) "
f"to_m=({x:.3f},{y:.3f},{current[0]:.3f}) "
f"normal_delta_deg={delta:.3f}")
coverage = profile["coverage"]
min_coverage = min(min_coverage, coverage)
plateau = max((run[0] for run in profile["plateaus"]), default=0.0)
step = max((run[1] for run in profile["plateaus"]), default=0.0)
angle = profile["max_angle"]
max_plateau = max(max_plateau, plateau)
max_step = max(max_step, step)
max_normal = max(max_normal, angle)
ok = (profile["valid"] and plateau < args.plateau_min_m
and angle <= args.normal_jump_max_deg)
failed |= not ok
print(f"[PassageFadeProfile] line={name} coverage={coverage:.3f} "
f"height_range_m={profile.get('height_range', 0.0):.6f} "
f"flat_step_runs={len(profile['plateaus'])} "
f"max_normal_delta_deg={angle:.6f} status={'PASS' if ok else 'FAIL'}")
passed = not failed and min_coverage >= 0.95
print(f"[PassageFadeRingAudit] scene={args.scene} lod={args.lod} "
f"center_m=({cx:.6f},{cy:.6f}) mouth_radius_m=4.500 "
f"fade_m=5.500..7.500 radial_m={args.inner_radius_m:.3f}.."
f"{args.outer_radius_m:.3f} lines={len(lines)} selected_tiles={selected_tiles} "
f"coverage_min={min_coverage:.3f} longest_flat_run_m={max_plateau:.6f} "
f"following_step_m={max_step:.6f} max_normal_delta_deg={max_normal:.6f} "
f"thresholds=(coverage>=0.950,plateau<0.500,step>=0.100,normal<=20.000) "
f"status={'PASS' if passed else 'FAIL'}")
print(f"[PassageFadeRSS] peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
raise SystemExit(0 if passed else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
"""Audit several bounded face windows and combine their height range."""
import argparse
import gc
import importlib.util
from pathlib import Path
import resource
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scene")
parser.add_argument("--surface", choices=("floor", "ceiling"), required=True)
parser.add_argument("--center", nargs=2, type=float, action="append", required=True,
help="repeat for each bounded local ring")
parser.add_argument("--radius", type=float, default=8.0)
parser.add_argument("--lod", type=int, required=True)
parser.add_argument("--height-min-m", type=float, required=True)
parser.add_argument("--height-max-m", type=float, required=True)
parser.add_argument("--preferred-height-m", type=float,
help="lock overlapping-face selection near this expected surface height")
parser.add_argument("--step", type=float, default=0.125)
parser.add_argument("--plateau-min-m", type=float, default=0.5)
parser.add_argument("--plateau-epsilon-m", type=float, default=0.0005)
parser.add_argument("--step-threshold-m", type=float, default=0.1)
parser.add_argument("--normal-jump-max-deg", type=float, default=20.0)
parser.add_argument("--amplitude-min-m", type=float, default=3.0)
parser.add_argument("--min-radial-lines-per-center", type=int, default=4)
parser.add_argument("--min-straight-lines-per-center", type=int, default=2)
args = parser.parse_args()
audit_path = Path(__file__).with_name("AuditRingTerraceL32.py")
spec = importlib.util.spec_from_file_location("ring_audit_l32_set", audit_path)
audit = importlib.util.module_from_spec(spec)
spec.loader.exec_module(audit)
upward = args.surface == "floor"
preferred_height = args.preferred_height_m
if preferred_height is None:
preferred_height = args.height_max_m if upward else args.height_min_m
all_ranges = []
overall_max_normal = 0.0
overall_max_plateau = 0.0
overall_max_step = 0.0
overall_min_coverage = 1.0
total_valid_lines = 0
total_lines = 0
failed = False
for center in args.center:
bounds = (center[0] - args.radius, center[0] + args.radius,
center[1] - args.radius, center[1] + args.radius)
voxel_um, triangles, selected_tiles = audit.read_scene(
args.scene, args.lod, args.height_min_m, args.height_max_m, bounds)
max_lod = max((triangle[0] for triangle in triangles), default=0)
cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << max_lod))
buckets = audit.build_buckets(triangles, upward, bounds, cell_size)
results = []
for name, points in audit.make_lines(*center, args.radius, args.step):
result = audit.audit_profile(points, buckets, upward,
args.plateau_min_m, args.plateau_epsilon_m,
args.step_threshold_m, cell_size, preferred_height)
results.append((name, points, result))
radial = [row for row in results if row[0].startswith("radial-") and row[2]["valid"]]
straight = [row for row in results if row[0].startswith("straight-") and row[2]["valid"]]
valid = [row for row in results if row[2]["valid"]]
local_min = min((row[2]["height_min"] for row in valid), default=float("inf"))
local_max = max((row[2]["height_max"] for row in valid), default=float("-inf"))
local_amplitude = local_max - local_min
local_plateau = max((run[0] for row in valid for run in row[2]["plateaus"]), default=0.0)
local_step = max((run[1] for row in valid for run in row[2]["plateaus"]), default=0.0)
local_normal = max((row[2]["max_angle"] for row in valid), default=float("inf"))
local_coverage = min((row[2]["coverage"] for row in results), default=0.0)
local_ok = (len(valid) == len(results)
and len(radial) >= args.min_radial_lines_per_center
and len(straight) >= args.min_straight_lines_per_center
and local_plateau < args.plateau_min_m
and local_normal <= args.normal_jump_max_deg)
all_ranges.extend((row[2]["height_min"], row[2]["height_max"])
for row in valid)
overall_max_normal = max(overall_max_normal, local_normal)
overall_max_plateau = max(overall_max_plateau, local_plateau)
overall_max_step = max(overall_max_step, local_step)
overall_min_coverage = min(overall_min_coverage, local_coverage)
total_valid_lines += len(valid)
total_lines += len(results)
print(f"[RingSetWindow] center_m=({center[0]:.3f},{center[1]:.3f}) "
f"radius_m={args.radius:.3f} tiles={selected_tiles} lines={len(valid)}/"
f"{len(results)} radial={len(radial)} straight={len(straight)} "
f"coverage_min={local_coverage:.3f} height_m={local_min:.6f}.."
f"{local_max:.6f} amplitude_m={local_amplitude:.6f} "
f"longest_flat_m={local_plateau:.6f} following_step_m={local_step:.6f} "
f"max_normal_delta_deg={local_normal:.6f} "
f"status={'PASS' if local_ok else 'FAIL'}")
for name, _, result in results:
print(f"[RingSetProfile] center_m=({center[0]:.3f},{center[1]:.3f}) "
f"line={name} coverage={result['coverage']:.3f} "
f"range_m={result.get('height_range', 0.0):.6f} "
f"flat_step_runs={len(result['plateaus'])} "
f"max_normal_delta_deg={result['max_angle']:.6f}")
failed |= not local_ok
del triangles, buckets, results, radial, straight, valid
gc.collect()
if all_ranges:
sampled_min = min(value[0] for value in all_ranges)
sampled_max = max(value[1] for value in all_ranges)
amplitude = sampled_max - sampled_min
else:
sampled_min = sampled_max = amplitude = 0.0
passed = not failed and amplitude >= args.amplitude_min_m
print(f"[RingSetAudit] scene={args.scene} surface={args.surface} lod={args.lod} "
f"windows={len(args.center)} radius_m={args.radius:.3f} "
f"height_window_m={args.height_min_m:.3f}..{args.height_max_m:.3f} "
f"sampled_height_m={sampled_min:.6f}..{sampled_max:.6f} "
f"amplitude_range_m={amplitude:.6f} lines={total_valid_lines}/{total_lines} "
f"coverage_min={overall_min_coverage:.3f} "
f"longest_flat_run_m={overall_max_plateau:.6f} "
f"max_step_m={overall_max_step:.6f} "
f"max_normal_delta_deg={overall_max_normal:.6f} "
f"thresholds=(amplitude>={args.amplitude_min_m:.3f},"
f"plateau<{args.plateau_min_m:.3f},"
f"normal<={args.normal_jump_max_deg:.3f}) "
f"status={'PASS' if passed else 'FAIL'}")
print(f"[RingSetRSS] peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
raise SystemExit(0 if passed else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,265 @@
#!/usr/bin/env python3
"""Fail on contour terraces, stepped profiles, or insufficient Gradient amplitude."""
import argparse
import array
import math
import resource
import struct
import sys
from collections import defaultdict
def read_scene(path, wanted_lod, height_min, height_max, bounds):
triangles = []
selected_tiles = 0
x_min, x_max, y_min, y_max = bounds
with open(path, "rb") as stream:
if stream.read(8) != b"VFMSH001":
raise SystemExit(f"{path}: invalid mesh scene header")
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
stream.read(40)
if version != 1 or tile_count > 100000:
raise SystemExit(f"{path}: unsupported mesh scene")
for _ in range(tile_count):
key = struct.unpack("<4i", stream.read(16))
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
tile_extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
tile_x0, tile_y0 = key[0] * tile_extent, key[1] * tile_extent
tile_z0 = key[2] * tile_extent
if (wanted_lod is not None and key[3] != wanted_lod) or \
tile_x0 > x_max or tile_x0 + tile_extent < x_min or \
tile_y0 > y_max or tile_y0 + tile_extent < y_min or \
tile_z0 > height_max or tile_z0 + tile_extent < height_min:
stream.seek(vertex_count * 24 + index_count * 4 + mask_count, 1)
continue
selected_tiles += 1
positions = array.array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array.array("f")
normals.frombytes(stream.read(vertex_count * 12))
indices = array.array("I")
indices.frombytes(stream.read(index_count * 4))
masks = stream.read(mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
for offset in range(0, index_count, 3):
if masks and masks[offset // 3]:
continue
ids = indices[offset:offset + 3]
if max(ids) >= surface_count:
continue
verts = []
for index in ids:
p, n = index * 3, index * 3
verts.append(((positions[p], positions[p + 1], positions[p + 2]),
(normals[n], normals[n + 1], normals[n + 2])))
average_nz = sum(vertex[1][2] for vertex in verts) / 3.0
z_values = [vertex[0][2] for vertex in verts]
# Select the requested surface branch by its face height. An
# overlap or centroid test admits a giant coarse triangle that
# merely clips this window and can belong to the neighboring face.
mean_z = sum(z_values) / 3.0
vertex_margin = voxel_um / 1_000_000.0 * 0.5
if (mean_z < height_min or mean_z > height_max or
min(z_values) < height_min - vertex_margin or
max(z_values) > height_max + vertex_margin):
continue
if (max(vertex[0][0] for vertex in verts) < x_min or
min(vertex[0][0] for vertex in verts) > x_max or
max(vertex[0][1] for vertex in verts) < y_min or
min(vertex[0][1] for vertex in verts) > y_max):
continue
triangles.append((key[3], average_nz, tuple(verts)))
return voxel_um, triangles, selected_tiles
def build_buckets(triangles, upward, bounds, cell_size=2.0):
buckets = defaultdict(list)
x_min, x_max, y_min, y_max = bounds
for triangle in triangles:
_, average_nz, verts = triangle
if (upward and average_nz < 0.35) or (not upward and average_nz > -0.35):
continue
xs = [v[0][0] for v in verts]
ys = [v[0][1] for v in verts]
x0 = max(math.floor(min(xs) / cell_size), math.floor(x_min / cell_size))
x1 = min(math.floor(max(xs) / cell_size), math.floor(x_max / cell_size))
y0 = max(math.floor(min(ys) / cell_size), math.floor(y_min / cell_size))
y1 = min(math.floor(max(ys) / cell_size), math.floor(y_max / cell_size))
for iy in range(y0, y1 + 1):
for ix in range(x0, x1 + 1):
buckets[(ix, iy)].append(verts)
return buckets
def sample_surface(buckets, x, y, upward, cell_size, preferred_height=None):
found = None
for verts in buckets.get((math.floor(x / cell_size), math.floor(y / cell_size)), ()):
(a, na), (b, nb), (c, nc) = verts
denominator = (b[1] - c[1]) * (a[0] - c[0]) + (c[0] - b[0]) * (a[1] - c[1])
if abs(denominator) < 1.0e-12:
continue
u = ((b[1] - c[1]) * (x - c[0]) + (c[0] - b[0]) * (y - c[1])) / denominator
v = ((c[1] - a[1]) * (x - c[0]) + (a[0] - c[0]) * (y - c[1])) / denominator
w = 1.0 - u - v
if min(u, v, w) < -1.0e-6:
continue
z = u * a[2] + v * b[2] + w * c[2]
normal = tuple(u * na[axis] + v * nb[axis] + w * nc[axis] for axis in range(3))
length = math.sqrt(sum(value * value for value in normal))
if length < 1.0e-9:
continue
normal = tuple(value / length for value in normal)
if (normal[2] > 0) != upward:
continue
if found is None or (abs(z - preferred_height) < abs(found[0] - preferred_height)
if preferred_height is not None else z > found[0]):
found = (z, normal)
return found
def make_lines(cx, cy, radius, step):
lines = []
for index in range(8):
angle = index * math.tau / 8.0
count = round(radius / step)
points = [(cx + i * step * math.cos(angle), cy + i * step * math.sin(angle))
for i in range(count + 1)]
lines.append((f"radial-{index}", points))
count = round(2.0 * radius / step)
t_values = [-radius + i * step for i in range(count + 1)]
for name, points in (
("straight-x", [(cx + t, cy) for t in t_values]),
("straight-y", [(cx, cy + t) for t in t_values]),
("straight-diagonal", [(cx + t / math.sqrt(2.0), cy + t / math.sqrt(2.0))
for t in t_values]),
):
lines.append((name, points))
return lines
def audit_profile(points, buckets, upward, plateau_min_m, plateau_epsilon_m,
step_threshold_m, cell_size, preferred_height=None):
samples = [sample_surface(buckets, x, y, upward, cell_size, preferred_height)
for x, y in points]
valid = [(index, value) for index, value in enumerate(samples) if value is not None]
if len(valid) < max(8, len(points) * 0.75):
return {"coverage": len(valid) / len(points), "plateaus": [], "max_angle": math.inf,
"height_range": 0.0, "valid": False}
heights = [value[0] for _, value in valid]
plateau_runs = []
run_start = valid[0][0]
run_start_height = valid[0][1][0]
sample_spacing = math.hypot(points[1][0] - points[0][0],
points[1][1] - points[0][1])
for cursor in range(1, len(valid)):
prior_index, prior = valid[cursor - 1]
index, current = valid[cursor]
if index != prior_index + 1:
run_start = index
run_start_height = current[0]
continue
if abs(current[0] - run_start_height) <= plateau_epsilon_m:
continue
run_length = (prior_index - run_start + 1) * sample_spacing
if run_length >= plateau_min_m and abs(current[0] - prior[0]) >= step_threshold_m:
plateau_runs.append((run_length, abs(current[0] - prior[0]), run_start,
prior_index, index, prior[0], current[0]))
run_start = index
run_start_height = current[0]
max_angle = 0.0
for cursor in range(1, len(valid)):
prior_index, prior = valid[cursor - 1]
index, current = valid[cursor]
if index != prior_index + 1:
continue
dot = max(-1.0, min(1.0, sum(a * b for a, b in zip(prior[1], current[1]))))
max_angle = max(max_angle, math.degrees(math.acos(dot)))
return {"coverage": len(valid) / len(points), "plateaus": plateau_runs,
"max_angle": max_angle, "height_range": max(heights) - min(heights),
"height_min": min(heights), "height_max": max(heights),
"valid": True}
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scene")
parser.add_argument("--surface", choices=("floor", "ceiling"), default="floor")
parser.add_argument("--center", nargs=2, type=float, default=(64.0, -32.0))
parser.add_argument("--radius", type=float, default=16.0)
parser.add_argument("--lod", required=True,
help="audit one LOD (0..8) or the streamed composite (`all`)")
parser.add_argument("--step", type=float, default=0.125)
parser.add_argument("--height-min-m", type=float, default=-130.0)
parser.add_argument("--height-max-m", type=float, default=-110.0)
parser.add_argument("--plateau-min-m", type=float, default=0.5)
parser.add_argument("--plateau-epsilon-m", type=float, default=0.0005)
parser.add_argument("--step-threshold-m", type=float, default=0.1)
parser.add_argument("--normal-jump-max-deg", type=float, default=20.0)
parser.add_argument("--amplitude-min-m", type=float, default=3.0)
args = parser.parse_args()
try:
wanted_lod = None if args.lod == "all" else int(args.lod)
except ValueError:
parser.error("--lod must be 0..8 or all")
if wanted_lod is not None and not 0 <= wanted_lod <= 8:
parser.error("--lod must be 0..8 or all")
bounds = (args.center[0] - args.radius, args.center[0] + args.radius,
args.center[1] - args.radius, args.center[1] + args.radius)
voxel_um, triangles, selected_tiles = read_scene(args.scene, wanted_lod,
args.height_min_m, args.height_max_m, bounds)
upward = args.surface == "floor"
max_lod = max((triangle[0] for triangle in triangles), default=0)
cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << max_lod))
buckets = build_buckets(triangles, upward, bounds, cell_size)
results = []
# The requested face is the outer boundary of its authored band: highest Z
# for a floor, lowest Z for a ceiling. Selecting the band midpoint can jump
# between two overlapping downward/upward sheets at a cave edge.
preferred_height = args.height_max_m if upward else args.height_min_m
for name, points in make_lines(*args.center, args.radius, args.step):
results.append((name, points, audit_profile(points, buckets, upward,
args.plateau_min_m, args.plateau_epsilon_m,
args.step_threshold_m, cell_size, preferred_height)))
valid = [result for _, _, result in results if result["valid"]]
if not valid:
raise SystemExit("no sampled surface profiles; check scene, center, orientation and LOD")
amplitude = max(result["height_max"] for result in valid) - min(
result["height_min"] for result in valid)
sampled_min = min(result["height_min"] for result in valid)
sampled_max = max(result["height_max"] for result in valid)
longest_plateau = max((run[0] for result in valid for run in result["plateaus"]),
default=0.0)
largest_step = max((run[1] for result in valid for run in result["plateaus"]),
default=0.0)
max_normal_jump = max(result["max_angle"] for result in valid)
min_coverage = min(result["coverage"] for _, _, result in results)
passed = (amplitude >= args.amplitude_min_m and longest_plateau < args.plateau_min_m
and max_normal_jump <= args.normal_jump_max_deg and min_coverage >= 0.75)
print(f"[RingTerraceAudit] scene={args.scene} surface={args.surface} lod={args.lod} "
f"max_lod={max_lod} "
f"height_window_m={args.height_min_m:.3f}..{args.height_max_m:.3f} "
f"voxel_um={voxel_um} triangles={len(triangles)} lines={len(results)} "
f"selected_tiles={selected_tiles} bucket_cell_m={cell_size:.3f} "
f"coverage_min={min_coverage:.3f} sampled_height_m={sampled_min:.6f}.."
f"{sampled_max:.6f} amplitude_range_m={amplitude:.6f} "
f"longest_flat_run_m={longest_plateau:.6f} following_step_m={largest_step:.6f} "
f"max_normal_delta_deg={max_normal_jump:.6f} status={'PASS' if passed else 'FAIL'}")
for name, points, result in results:
print(f"[RingTerraceProfile] line={name} coverage={result['coverage']:.3f} "
f"range_m={result['height_range']:.6f} flat_step_runs={len(result['plateaus'])} "
f"max_normal_delta_deg={result['max_angle']:.6f}")
for run in result["plateaus"]:
print(f"[RingTerraceViolation] line={name} flat_start_m=({points[run[2]][0]:.3f},"
f"{points[run[2]][1]:.3f}) flat_run_m={run[0]:.6f} "
f"step_m={run[1]:.6f} from_height_m={run[5]:.6f} "
f"to_height_m={run[6]:.6f}")
print(f"[RingTerraceRSS] peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
raise SystemExit(0 if passed else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Encode the near/far tint-on/off L32 captures as compact JPEGs."""
import resource
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
SOURCE = ROOT / "Saved/Linux/l32"
DEST = Path(__file__).resolve().parent / "captures"
DEST.mkdir(parents=True, exist_ok=True)
captures = {
"fi-floor-near-tint-on": "cap-final-fi-near-on",
"fi-floor-near-tint-off": "cap-final-fi-near-off",
"fi-floor-far-tint-on": "cap-final-fi-far-on",
"fi-floor-far-tint-off": "cap-final-fi-far-off",
"sw-ceiling-near-tint-on": "cap-final-sw-near-on",
"sw-ceiling-near-tint-off": "cap-final-sw-near-off",
"sw-ceiling-far-tint-on": "cap-final-sw-far-on",
"sw-ceiling-far-tint-off": "cap-final-sw-far-off",
}
failed = False
for output_name, input_name in captures.items():
source = SOURCE / f"{input_name}.png"
destination = DEST / f"{output_name}.jpg"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "ffmpeg",
"-y", "-hide_banner", "-loglevel", "error", "-i", str(source),
"-frames:v", "1", "-q:v", "4", str(destination),
]
result = subprocess.run(command, check=False)
size = destination.stat().st_size if destination.exists() else 0
ok = source.exists() and result.returncode == 0 and size <= 300_000
failed |= not ok
print(f"[L32CaptureJPEG] source={source.name} output={destination.name} "
f"bytes={size} status={'PASS' if ok else 'FAIL'}", flush=True)
print(f"[L32CaptureJPEG] peak_rss_kib="
f"{resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}", flush=True)
raise SystemExit(1 if failed else 0)
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Copy only tiles for one LOD and spatial window from a VFMSH scene."""
import argparse
import resource
import shutil
import struct
def main():
parser = argparse.ArgumentParser()
parser.add_argument("source")
parser.add_argument("destination")
parser.add_argument("--lod", type=int, required=True)
parser.add_argument("--bounds", nargs=6, type=float, required=True,
metavar=("XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"))
args = parser.parse_args()
if not 0 <= args.lod <= 8:
parser.error("--lod must be between 0 and 8")
x_min, x_max, y_min, y_max, z_min, z_max = args.bounds
if x_max <= x_min or y_max <= y_min or z_max <= z_min:
parser.error("bounds must have positive extents")
selected = []
with open(args.source, "rb") as src:
prefix = src.read(60)
if len(prefix) != 60 or prefix[:8] != b"VFMSH001":
raise SystemExit("source has an invalid VFMSH001 header")
version, voxel_um, tile_count = struct.unpack_from("<3I", prefix, 8)
if version != 1 or voxel_um == 0 or tile_count > 1_000_000:
raise SystemExit("source has an unsupported scene header")
for _ in range(tile_count):
header = src.read(36)
if len(header) != 36:
raise SystemExit("truncated tile header")
key = struct.unpack_from("<4i", header)
_, _, vertex_count, index_count, mask_count = struct.unpack_from("<5I", header, 16)
payload_offset = src.tell()
payload_size = vertex_count * 24 + index_count * 4 + mask_count
extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
tx0, ty0, tz0 = key[0] * extent, key[1] * extent, key[2] * extent
intersects = (key[3] == args.lod and tx0 <= x_max and tx0 + extent >= x_min
and ty0 <= y_max and ty0 + extent >= y_min
and tz0 <= z_max and tz0 + extent >= z_min)
if intersects:
selected.append((header, payload_offset, payload_size))
src.seek(payload_size, 1)
if src.read(1):
raise SystemExit("source scene has trailing bytes")
if not selected:
raise SystemExit("no tiles matched the requested LOD and bounds")
with open(args.source, "rb") as src, open(args.destination, "wb") as dst:
prefix = bytearray(src.read(60))
struct.pack_into("<I", prefix, 16, len(selected))
dst.write(prefix)
for header, payload_offset, payload_size in selected:
dst.write(header)
src.seek(payload_offset)
remaining = payload_size
while remaining:
block = src.read(min(1 << 20, remaining))
if not block:
raise SystemExit("truncated tile payload")
dst.write(block)
remaining -= len(block)
print(f"[VFMeshLodWindow] source={args.source} lod={args.lod} "
f"selected_tiles={len(selected)} output={args.destination} "
f"output_bytes={__import__('os').path.getsize(args.destination)} "
f"peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss} status=PASS")
if __name__ == "__main__":
main()
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""Stream VFMSH tiles and retain a focused, near-horizontal surface patch."""
import argparse
import math
import resource
import struct
def main():
parser = argparse.ArgumentParser()
parser.add_argument("source")
parser.add_argument("destination")
parser.add_argument("--center", nargs=2, type=float, required=True, metavar=("X", "Y"))
parser.add_argument("--radius-m", type=float, required=True)
parser.add_argument("--height-m", nargs=2, type=float, required=True, metavar=("MIN", "MAX"))
parser.add_argument("--min-abs-normal-z", type=float, default=0.7)
args = parser.parse_args()
if args.radius_m <= 0 or args.height_m[1] <= args.height_m[0]:
parser.error("radius and height interval must be positive")
if not 0.0 <= args.min_abs_normal_z <= 1.0:
parser.error("--min-abs-normal-z must be in [0,1]")
cx, cy = args.center
r2 = args.radius_m * args.radius_m
zmin, zmax = args.height_m
input_tiles = kept_tiles = kept_triangles = 0
with open(args.source, "rb") as src, open(args.destination, "wb+") as dst:
prefix = bytearray(src.read(60))
if len(prefix) != 60 or prefix[:8] != b"VFMSH001":
raise SystemExit("source has an invalid VFMSH001 header")
version, voxel_um, tile_count = struct.unpack_from("<3I", prefix, 8)
if version != 1 or voxel_um == 0 or tile_count > 1_000_000:
raise SystemExit("source has an unsupported scene header")
struct.pack_into("<I", prefix, 16, 0)
dst.write(prefix)
for _ in range(tile_count):
header = src.read(36)
if len(header) != 36:
raise SystemExit("truncated tile header")
key = struct.unpack_from("<4i", header)
surface_vertices, skirt_count, vertex_count, index_count, mask_count = \
struct.unpack_from("<5I", header, 16)
if vertex_count > 2_000_000 or index_count > 12_000_000:
raise SystemExit("tile exceeds the focused patch memory limit")
vertex_bytes = src.read(vertex_count * 24)
index_bytes = src.read(index_count * 4)
masks = src.read(mask_count)
if (len(vertex_bytes) != vertex_count * 24
or len(index_bytes) != index_count * 4 or len(masks) != mask_count):
raise SystemExit("truncated tile payload")
input_tiles += 1
positions = list(struct.iter_unpack("<3f", vertex_bytes[:vertex_count * 12]))
indices = struct.unpack(f"<{index_count}I", index_bytes) if index_count else ()
face_masks = masks if mask_count else bytes(index_count // 12)
filtered = []
for tri in range(index_count // 3):
i0, i1, i2 = indices[tri * 3:tri * 3 + 3]
if i0 >= vertex_count or i1 >= vertex_count or i2 >= vertex_count:
raise SystemExit("tile index points outside its vertex array")
if face_masks[tri] != 0:
continue
p0, p1, p2 = positions[i0], positions[i1], positions[i2]
mx = (p0[0] + p1[0] + p2[0]) / 3.0
my = (p0[1] + p1[1] + p2[1]) / 3.0
mz = (p0[2] + p1[2] + p2[2]) / 3.0
if (mx - cx) ** 2 + (my - cy) ** 2 > r2 or not zmin <= mz <= zmax:
continue
ab = (p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2])
ac = (p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2])
nz = ab[0] * ac[1] - ab[1] * ac[0]
length = math.sqrt((ab[1] * ac[2] - ab[2] * ac[1]) ** 2
+ (ab[2] * ac[0] - ab[0] * ac[2]) ** 2 + nz * nz)
if length == 0 or abs(nz) / length < args.min_abs_normal_z:
continue
filtered.extend((i0, i1, i2))
if not filtered:
continue
tile_header = struct.pack("<4i5I", *key, surface_vertices, 0,
vertex_count, len(filtered), 0)
dst.write(tile_header)
dst.write(vertex_bytes)
dst.write(struct.pack(f"<{len(filtered)}I", *filtered))
kept_tiles += 1
kept_triangles += len(filtered) // 3
dst.seek(16)
dst.write(struct.pack("<I", kept_tiles))
if kept_tiles == 0:
raise SystemExit("no surface triangles matched the requested patch")
print(f"[VFMeshSurfacePatch] source={args.source} input_tiles={input_tiles} "
f"tiles={kept_tiles} triangles={kept_triangles} center=({cx:.3f},{cy:.3f}) "
f"radius_m={args.radius_m:.3f} height_m={zmin:.3f}..{zmax:.3f} "
f"peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss} status=PASS")
if __name__ == "__main__":
main()
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""Find VFMSH tiles intersecting a small region without reading mesh payloads."""
import argparse
import glob
import os
import resource
import struct
def main():
parser = argparse.ArgumentParser()
parser.add_argument("directory")
parser.add_argument("--lod", type=int, required=True)
parser.add_argument("--bounds", nargs=6, type=float, required=True,
metavar=("XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"))
args = parser.parse_args()
xmin, xmax, ymin, ymax, zmin, zmax = args.bounds
matches = 0
for path in sorted(glob.glob(os.path.join(args.directory, "*.vfmesh"))):
with open(path, "rb") as stream:
if stream.read(8) != b"VFMSH001":
continue
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
stream.seek(40, 1)
if version != 1:
continue
for _ in range(tile_count):
key = struct.unpack("<4i", stream.read(16))
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
x0, y0, z0 = (key[axis] * extent for axis in range(3))
intersects = (key[3] == args.lod and index_count > 0
and x0 <= xmax and x0 + extent >= xmin
and y0 <= ymax and y0 + extent >= ymin
and z0 <= zmax and z0 + extent >= zmin)
if intersects:
matches += 1
print(f"[VFMeshTileWindow] scene={os.path.basename(path)} key={key} "
f"vertices={vertex_count} indices={index_count} "
f"extent_m={extent:.3f}")
stream.seek(vertex_count * 24 + index_count * 4 + mask_count, 1)
print(f"[VFMeshTileWindowSummary] matching_tiles={matches} "
f"peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
if __name__ == "__main__":
main()
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""Stream selected-LOD tiles and find face windows that pass the ring audit."""
import argparse
import array
import importlib.util
from pathlib import Path
import resource
import struct
import sys
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scene")
parser.add_argument("--lod", type=int, required=True)
parser.add_argument("--surface", choices=("floor", "ceiling"), required=True)
parser.add_argument("--height-min-m", type=float, required=True)
parser.add_argument("--height-max-m", type=float, required=True)
parser.add_argument("--preferred-height-m", type=float,
help="lock overlapping-face selection near this expected surface height")
parser.add_argument("--step-m", type=float, default=0.5)
parser.add_argument("--tile-key", nargs=3, type=int,
help="audit only this tile's x/y/z key (LOD is supplied separately)")
args = parser.parse_args()
audit_path = Path(__file__).with_name("AuditRingTerraceL32.py")
spec = importlib.util.spec_from_file_location("ring_audit_l32", audit_path)
audit = importlib.util.module_from_spec(spec)
spec.loader.exec_module(audit)
want_up = args.surface == "floor"
candidates = []
with open(args.scene, "rb") as stream:
if stream.read(8) != b"VFMSH001":
raise SystemExit("invalid VFMSH001 scene")
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
stream.seek(40, 1)
if version != 1:
raise SystemExit("unsupported mesh scene")
for _ in range(tile_count):
header = stream.read(36)
key = struct.unpack("<4i", header[:16])
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
"<5I", header[16:])
payload = vertex_count * 24 + index_count * 4 + mask_count
if key[3] != args.lod or (args.tile_key and tuple(key[:3]) != tuple(args.tile_key)):
stream.seek(payload, 1)
continue
positions = array.array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array.array("f")
normals.frombytes(stream.read(vertex_count * 12))
indices = array.array("I")
indices.frombytes(stream.read(index_count * 4))
masks = stream.read(mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
triangles = []
vertex_margin = voxel_um / 1_000_000.0 * 0.5
for offset in range(0, index_count, 3):
if masks and masks[offset // 3]:
continue
ids = indices[offset:offset + 3]
if max(ids) >= surface_count:
continue
verts = []
for index in ids:
p = index * 3
verts.append(((positions[p], positions[p + 1], positions[p + 2]),
(normals[p], normals[p + 1], normals[p + 2])))
average_nz = sum(vertex[1][2] for vertex in verts) / 3.0
if (want_up and average_nz < 0.35) or (not want_up and average_nz > -0.35):
continue
z_values = [vertex[0][2] for vertex in verts]
mean_z = sum(z_values) / 3.0
if (mean_z < args.height_min_m or mean_z > args.height_max_m or
min(z_values) < args.height_min_m - vertex_margin or
max(z_values) > args.height_max_m + vertex_margin):
continue
triangles.append((key[3], average_nz, tuple(verts)))
if not triangles:
continue
extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
cx, cy = (key[0] + 0.5) * extent, (key[1] + 0.5) * extent
radius = extent * 0.5
bounds = (cx - radius, cx + radius, cy - radius, cy + radius)
cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << key[3]))
buckets = audit.build_buckets(triangles, want_up, bounds, cell_size)
profiles = []
preferred_height = args.preferred_height_m
if preferred_height is None:
preferred_height = args.height_max_m if want_up else args.height_min_m
for _, points in audit.make_lines(cx, cy, radius, args.step_m):
profiles.append(audit.audit_profile(points, buckets, want_up,
0.5, 0.0005, 0.1, cell_size, preferred_height))
valid = [profile for profile in profiles if profile["valid"]]
coverage = min(profile["coverage"] for profile in profiles)
amplitude = max((profile["height_max"] for profile in valid), default=0.0) - \
min((profile["height_min"] for profile in valid), default=0.0)
longest = max((run[0] for profile in valid for run in profile["plateaus"]),
default=0.0)
max_normal = max((profile["max_angle"] for profile in valid), default=float("inf"))
passed = (len(valid) == len(profiles) and coverage >= 0.75 and amplitude >= 3.0
and longest < 0.5 and max_normal <= 20.0)
candidates.append((passed, amplitude, -max_normal, key, cx, cy,
len(triangles), coverage, longest, max_normal))
stream.seek(payload - (vertex_count * 24 + index_count * 4 + mask_count), 1)
candidates.sort(reverse=True, key=lambda item: (item[0], item[1], item[2]))
print(f"[RingWindowSearch] lod={args.lod} surface={args.surface} tiles={len(candidates)} "
f"height_window_m={args.height_min_m:.3f}..{args.height_max_m:.3f} "
f"step_m={args.step_m:.3f}")
for passed, amplitude, neg_normal, key, cx, cy, count, coverage, longest, normal in candidates[:40]:
print(f"[RingWindowCandidate] status={'PASS' if passed else 'FAIL'} key={key} "
f"center_m=({cx:.3f},{cy:.3f}) triangles={count} coverage={coverage:.3f} "
f"amplitude_m={amplitude:.6f} longest_flat_m={longest:.6f} "
f"normal_jump_deg={normal:.6f}")
print(f"[RingWindowRSS] peak_rss_kib="
f"{resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
if __name__ == "__main__":
main()
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""Find open SurfaceWorld columns below the resolved transition ceiling."""
import argparse
import csv
import subprocess
from pathlib import Path
def main():
parser = argparse.ArgumentParser()
parser.add_argument("probe")
parser.add_argument("owner")
parser.add_argument("output")
parser.add_argument("--z", type=float, default=-130.0)
parser.add_argument("--minimum", type=float, default=-96.0)
parser.add_argument("--maximum", type=float, default=96.0)
parser.add_argument("--spacing", type=float, default=16.0)
args = parser.parse_args()
probe = str(Path(args.probe).resolve())
owner = str(Path(args.owner).resolve())
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
rows = []
count = round((args.maximum - args.minimum) / args.spacing)
for iy in range(count + 1):
y = args.minimum + iy * args.spacing
for ix in range(count + 1):
x = args.minimum + ix * args.spacing
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
probe, owner, "surface-world", str(x), str(y),
str(args.z - 2.0), str(args.z + 2.0), "0.25"]
result = subprocess.run(command, capture_output=True, text=True, timeout=1800,
check=False)
samples = [line for line in result.stdout.splitlines()
if line.startswith(f"{args.z:.6f},")]
density = float(samples[0].split(",")[1]) if result.returncode == 0 and samples else float("nan")
rows.append((x, y, args.z, density, result.returncode))
print(f"[SurfaceWorldAirScan] x_m={x:.1f} y_m={y:.1f} z_m={args.z:.2f} "
f"density_vox={density:.6f} exit={result.returncode}", flush=True)
with output.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow(("x_m", "y_m", "z_m", "density_vox", "exit_code"))
writer.writerows(rows)
open_samples = [row for row in rows if row[3] > 0.0]
print(f"[SurfaceWorldAirScanSummary] samples={len(rows)} open={len(open_samples)} "
f"output={output} status={'PASS' if open_samples else 'FAIL'}")
return 0 if open_samples else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,118 @@
#include "Json.h"
#include "OwnerWorldLoader.h"
#include "StandaloneCoreScheduler.h"
#include "WorldAssemblyPrototype.h"
#include "StandaloneSnapshotAdapter.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iterator>
#include <memory>
#include <string>
int main(int argc,char** argv)
{
if(argc!=8)
{
std::fprintf(stderr,"usage: %s owner.json surface-world|floating-islands x_m y_m zmin_m zmax_m step_m\n",argv[0]);
return 2;
}
const std::string recipe=argv[2];
if(recipe!="surface-world"&&recipe!="floating-islands")return 2;
const double x=std::strtod(argv[3],nullptr),y=std::strtod(argv[4],nullptr);
const double zmin=std::strtod(argv[5],nullptr),zmax=std::strtod(argv[6],nullptr);
const double step=std::strtod(argv[7],nullptr);
if(!std::isfinite(x)||!std::isfinite(y)||!std::isfinite(zmin)||!std::isfinite(zmax)
||!std::isfinite(step)||zmax<=zmin||step<=0.0)return 2;
VFJson::Value root;
std::string error;
{
std::ifstream input(argv[1],std::ios::binary);
if(!input){std::fprintf(stderr,"owner JSON open failed\n");return 2;}
std::string text{std::istreambuf_iterator<char>(input),std::istreambuf_iterator<char>{}};
if(!VFJson::Parser(std::move(text)).Parse(root,error))
{std::fprintf(stderr,"owner JSON parse failed: %s\n",error.c_str());return 2;}
}
if(recipe=="floating-islands"
&&!StandaloneWorldAssemblyPrototype::SelectTopStrateAsFloatingIslands(root,error))
{std::fprintf(stderr,"FI selection failed: %s\n",error.c_str());return 2;}
auto owner=std::make_shared<FStandaloneOwnerWorld>();
if(!StandaloneOwnerLoader::Resolve(root,*owner,error))
{std::fprintf(stderr,"owner resolve failed: %s\n",error.c_str());return 2;}
VoxelForgeCore::WorldAssemblyManifest assembly;
if(!StandaloneWorldAssemblyPrototype::AttachRound1WorldAssemblyPrototype(
*owner,root,error,{},&assembly))
{std::fprintf(stderr,"assembly setup failed: %s\n",error.c_str());return 2;}
if(recipe=="floating-islands"
&&!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly(
*owner,root,assembly,error))
{std::fprintf(stderr,"FI setup failed: %s\n",error.c_str());return 2;}
VoxelForgeCore::ResolvedLayoutSnapshotV3 resolvedLayout;
if(!StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot(
*owner,assembly,recipe,resolvedLayout,error))
{std::fprintf(stderr,"resolved layout build failed: %s\n",error.c_str());return 2;}
auto* rootObject=std::get_if<VFJson::Value::Object>(&root.Data);
if(!rootObject)return 2;
VFJson::Value layoutJson;
layoutJson.Data=resolvedLayout.ToJson();
(*rootObject)["resolved_layout_json"]=std::move(layoutJson);
auto snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(
root,owner->Seed,3,8,false,false,error);
if(!snapshot)
{std::fprintf(stderr,"snapshot resolve failed: %s\n",error.c_str());return 2;}
const auto* layout=snapshot.resolvedLayout();
if(!layout){std::fprintf(stderr,"resolved layout missing\n");return 2;}
for(const auto& stratum:layout->strates)
std::printf("[DensityStratum] order=%d strate=%d generator=%s mode=%s width_um=%lld bedrock_um=%lld..%lld ceiling_um=%lld..%lld\n",
stratum.order,stratum.strateIndex,stratum.generatorKind.c_str(),
stratum.transitionMode.c_str(),static_cast<long long>(stratum.transitionWidthMicrometres),
static_cast<long long>(stratum.bedrock.bottomMicrometres),
static_cast<long long>(stratum.bedrock.topMicrometres),
static_cast<long long>(stratum.ceiling.bottomMicrometres),
static_cast<long long>(stratum.ceiling.topMicrometres));
const auto interface=std::find_if(layout->strates.begin(),layout->strates.end(),
[](const auto& stratum){return stratum.order==0;});
if(interface==layout->strates.end())return 2;
VoxelForgeCore::World world(std::move(snapshot));
VoxelForgeCore::ShapeBuildRequest request;
request.minVoxel={static_cast<std::int32_t>(std::floor((x-32.0)*4.0)),
static_cast<std::int32_t>(std::floor((y-32.0)*4.0)),
static_cast<std::int32_t>(std::floor((zmin-4.0)*4.0))};
request.maxVoxel={static_cast<std::int32_t>(std::ceil((x+32.0)*4.0)),
static_cast<std::int32_t>(std::ceil((y+32.0)*4.0)),
static_cast<std::int32_t>(std::ceil((zmax+4.0)*4.0))};
request.clipMinVoxel=request.minVoxel;
request.clipMaxVoxel=request.maxVoxel;
request.workerLimit=12;
request.includeFarWorld=false;
FStandaloneCoreScheduler scheduler;
if(!world.compile(request,scheduler,error))
{std::fprintf(stderr,"shape compile failed: %s\n",error.c_str());return 2;}
std::printf("[DensityLine] recipe=%s mode=%s width_um=%lld seam_um=%lld band_top_um=%lld band_bottom_um=%lld xy_m=%.6f,%.6f core_voxel_m=0.25 status=PASS\n",
recipe.c_str(),interface->transitionMode.c_str(),
static_cast<long long>(interface->transitionWidthMicrometres),
static_cast<long long>(interface->bedrock.bottomMicrometres),
static_cast<long long>(interface->bedrock.topMicrometres),
static_cast<long long>(interface->bedrock.bottomMicrometres),x,y);
std::printf("z_m,density_vox,lattice_vox,unclamped_vox,dispatch\n");
const auto count=static_cast<std::size_t>(std::floor((zmax-zmin)/step+1.0e-9));
for(std::size_t i=0;i<=count;++i)
{
const double z=zmin+static_cast<double>(i)*step;
const VoxelForgeCore::Vec3d p{x*4.0,y*4.0,z*4.0};
const VoxelForgeCore::Vec3i lattice{
static_cast<std::int32_t>(std::llround(x*4.0)),
static_cast<std::int32_t>(std::llround(y*4.0)),
static_cast<std::int32_t>(std::llround(z*4.0))};
std::printf("%.6f,%.9f,%.9f,%.9f,%s\n",z,world.densityAt(p),
world.densityAtLattice(lattice),world.unclampedDensityAt(p),
world.densityDispatchPathAt(p).c_str());
}
return 0;
}
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""Dump a bounded surface line from a VFMSH scene using the ring-audit sampler."""
import argparse
import importlib.util
from pathlib import Path
import resource
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scene")
parser.add_argument("--surface", choices=("floor", "ceiling"), required=True)
parser.add_argument("--center", nargs=2, type=float, required=True)
parser.add_argument("--radius", type=float, required=True)
parser.add_argument("--lod", type=int, required=True)
parser.add_argument("--height-min-m", type=float, required=True)
parser.add_argument("--height-max-m", type=float, required=True)
parser.add_argument("--line", choices=("x", "y", "diagonal"), default="x")
parser.add_argument("--stride-m", type=float, default=8.0)
args = parser.parse_args()
audit_path = Path(__file__).with_name("AuditRingTerraceL32.py")
spec = importlib.util.spec_from_file_location("ring_audit_l32", audit_path)
audit = importlib.util.module_from_spec(spec)
spec.loader.exec_module(audit)
cx, cy = args.center
bounds = (cx - args.radius, cx + args.radius,
cy - args.radius, cy + args.radius)
voxel_um, triangles, tiles = audit.read_scene(args.scene, args.lod,
args.height_min_m, args.height_max_m, bounds)
cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << args.lod))
buckets = audit.build_buckets(triangles, args.surface == "floor", bounds, cell_size)
count = round(2.0 * args.radius / args.stride_m)
print(f"[SurfaceProfile] lod={args.lod} surface={args.surface} tiles={tiles} "
f"triangles={len(triangles)} line={args.line} stride_m={args.stride_m}")
print("index,x_m,y_m,z_m,nx,ny,nz")
for index in range(count + 1):
t = -args.radius + index * args.stride_m
if args.line == "x":
x, y = cx + t, cy
elif args.line == "y":
x, y = cx, cy + t
else:
x, y = cx + t / 2.0**0.5, cy + t / 2.0**0.5
preferred_height = args.height_max_m if args.surface == "floor" else args.height_min_m
hit = audit.sample_surface(buckets, x, y, args.surface == "floor", cell_size,
preferred_height)
if hit is None:
print(f"{index},{x:.3f},{y:.3f},nan,nan,nan,nan")
else:
z, normal = hit
print(f"{index},{x:.3f},{y:.3f},{z:.6f}," +
",".join(f"{value:.7f}" for value in normal))
print(f"[SurfaceProfileRSS] peak_rss_kib="
f"{resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
if __name__ == "__main__":
main()
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Compare the v20 transition-column cache with the uncached v20 binary."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l32"
BINARIES = {
"base": OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20",
"cache": OUT / "bin/transition-cache/VoxelForgeCoreHeadless-l32-transition-cache",
}
ORDER = (("base", "cache"), ("cache", "base"), ("base", "cache"),
("cache", "base"), ("base", "cache"))
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
rows = []
failed = False
for pair, builds in enumerate(ORDER, 1):
for build in builds:
name = f"cache-compare-sw-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(BINARIES[build]), str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", "--recipe=surface-world",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
matches = READY.findall(text)
valid = result is not None and result.returncode == 0 and len(matches) == 1
ready = float(matches[0]) if len(matches) == 1 else float("nan")
rows.append((pair, build, ready, elapsed,
result.returncode if result else "TIMEOUT", valid))
print(f"pair={pair} build={build} whole_view_ready={ready:.3f}s "
f"process_elapsed={elapsed:.1f}s {'PASS' if valid else 'FAIL'}", flush=True)
failed |= not valid
csv = OUT / "transition-cache-compare.csv"
csv.write_text(
"pair,build,whole_view_ready_seconds,process_elapsed_seconds,exit_code,status\n" +
"".join(f"{pair},{build},{ready:.6f},{elapsed:.3f},{code},"
f"{'PASS' if ok else 'FAIL'}\n"
for pair, build, ready, elapsed, code, ok in rows), encoding="utf-8")
for build in BINARIES:
values = [row[2] for row in rows if row[1] == build]
values.sort()
median = values[len(values) // 2]
print(f"[TransitionCacheCost] build={build} median_whole_view_ready_s={median:.3f}")
print(f"[TransitionCacheCost] runs={len(rows)} status={'FAIL' if failed else 'PASS'}")
raise SystemExit(1 if failed or len(rows) != 10 else 0)
@@ -0,0 +1,68 @@
# VoxelForge L32 report
## Cause and fix
The old Gradient transition wrote constant solid-density values per band. Those values are not signed distance to the displaced boundary, so Marching Cubes crossed band levels as contour-like terraces. The hash surface quantization was not the cause: sampling across the final FI floor gives density -0.53345 at z=-121.50 m and +0.46655 at -121.25 m; the SW ceiling gives +0.01514 at -126.50 m and -0.98486 at -126.25 m. Both cross linearly at about 4 density units/m.
L32 restores the width/8 Gradient displacement (2 m each side, about 4 m peak-to-peak at 16 m owner width). Density is now signed distance to the union of the displaced solid intervals, continuous across band boundaries. Cap samples follow the same displaced faces. Passage-mouth fades use cubic smoothstep across a 2 m fade from the protected mouth radius; this preserves the face while blending the column flare. Version/evaluator is v20.
## Surface and interface audits
Branch-locked radial and straight profiles pass on both faces at LOD0 through LOD8. Every tested LOD has at least 3.15 m height range; no repeated-height plateau or following step was found. LOD0/1 profiles use 0.125 m samples (SW LOD1 also checked at 0.0625 m); LOD2–8 use 0.5 m samples. The largest adjacent-normal change was 17.50° on the fine SW LOD1 scan, below the 20° audit limit. Peak RSS for the final Python ring scans was 373,596 KiB (FI LOD0); the passage-fade scans peaked at 52,128 KiB. Mesh scans use tile/chunk processing.
Interleaved also passes 11 radial/straight profiles at each LOD0–8: all selected windows have full coverage, no flats or steps, and at most 0.367° adjacent-normal change. Their local height ranges are 0.032–3.174 m; the 3 m minimum is the Gradient amplitude check, not an Interleaved requirement. Peak RSS for this tile-streamed sweep was 24,468 KiB.
Both ring negative controls fail as intended: L30 has 0.742 m range, a 1.875 m flat run followed by a 0.241 m step, and a 27.34° normal jump; L31's flat fix has only 0.000046 m range. The passage-mouth audit passes on both overlapping faces: upper max normal change 15.06°, lower 7.96°, with full coverage and no flats or steps.
Hole/interface and minimum-band audits pass for Hard, Gradient and Interleaved, for both recipes and compilers: zero hole columns and zero air samples. Wider-than-declared and thinner-than-minimum controls are rejected. Minimum bands are 0.5 m FI, 1.0 m SW and 6.25 m lower band; maximum Interleaved crossing is 6.487 m against the 16 m bound. Passage audit passes all 16 recipe-stamped rows; recipe-mismatch and disconnected-end controls are detected.
Determinism passes 24/24: the L25 command and 24 m crossing, both recipes, Clang and GCC, workers 1/8/12.
## Captures and hashes
Tint-on and tint-off JPEGs are in `captures/`; all are under 30 KB (limit 300 KB):
- `fi-floor-near-tint-on.jpg`, `fi-floor-near-tint-off.jpg`
- `fi-floor-far-tint-on.jpg`, `fi-floor-far-tint-off.jpg`
- `sw-ceiling-near-tint-on.jpg`, `sw-ceiling-near-tint-off.jpg`
- `sw-ceiling-far-tint-on.jpg`, `sw-ceiling-far-tint-off.jpg`
They show the FI floor and SW ceiling from inside each stratum, with grazing light, at LOD0 and LOD8. Capture SHA256 values:
| Capture | SHA256 |
|---|---|
| fi-floor-far-tint-off.jpg | `239b6be70c177e267d2db95f206fdcdc8834779a7948fdaaddd136b4163d1f34` |
| fi-floor-far-tint-on.jpg | `3ecefd555275036a7045a541f02bb1873e22302495009dbd80b4ac58ce716b58` |
| fi-floor-near-tint-off.jpg | `847f10acfb420c2bbded61bdee7f578cde19469777b2ef4c5a0eba08b406611c` |
| fi-floor-near-tint-on.jpg | `4a5d3e3f8e8254639fa1840a2fb4497f2ab19f64bb4734e13abe3c3cfcfb4008` |
| sw-ceiling-far-tint-off.jpg | `f5ec099076c507bed5a703302d89b665e139804a8f8895a68c1d5705aabf9f61` |
| sw-ceiling-far-tint-on.jpg | `b82618814c8320be2de1ac53ba10e9ab4469299a8b53b776be32c6ca9a659988` |
| sw-ceiling-near-tint-off.jpg | `5789ce59a7748aa278c1b95edae45aec87721721260c1a3aefe6c4cc1e47cd7a` |
| sw-ceiling-near-tint-on.jpg | `aa4351c5effc5e93fc0879257fe056b3f4625e7842f5118f28588fb2c5c1afe2` |
Final Core binary SHA256:
- Clang: `052694a569c92ef407c25bd9a534c7123ef800f46733e5c428e965c32d905fdc`
- GCC: `c8656901ce18861f12168849165785937a2495233604314df9e005ed1e92afdd`
## Cost and edits
Five interleaved pairs per recipe against L31. Median whole-view-ready time was SW 1.723 s → 1.756 s (+1.92%) and FI 16.963 s → 17.221 s (+1.52%); paired results were mixed and within observed run noise. No Unreal files changed. The five edited files are the Core public header, `FarShellsInternal.h`, `ResolvedLayoutInternal.h`, `ShapeWorldInternal.h` and `LinuxOffscreenCapture.cpp`; Unreal compilation was not available here.
## Failed and retried runs
- The initial multi-LOD Python audit was stopped by system RAM pressure (reported peak unavailable). Subsequent mesh audits ran with CPU/address-space caps and streamed or selected tiles; peak RSS is recorded above. The first whole-mesh FI LOD8 candidate scan was interrupted and replaced by a targeted tile scan.
- Early ring-window searches and narrow-height audits selected the wrong one of two overlapping faces. The FI LOD3/4 branch probes measured 1.423 m amplitude / a 1.0 m flat run; the SW LOD1 low-face probe reached 20.19° normal change. Face-locked, broad-window scans replaced them and pass. The exploratory `ring-*`, `ringset-*`, `search-*` and `window-search-*` logs preserve rejected window candidates. The L30/L31 failures above are intentional negative controls.
- The first Interleaved layout/export probe omitted the runner's required streaming flags and exited with usage; the retry produced the LOD0–8 export. Its broad 0.5 m candidate scan applied the Gradient 3 m window heuristic and rejected LOD0–4 (one LOD1 candidate switched overlapping faces at 62.92°); selected branch-locked profiles at those LODs and passing LOD5–8 windows show no plateaus or steps. An unsupported `--help` probe also returned usage with exit 2.
- The exact-source passage audit at midpoint preference switched faces and reported a 47.79° jump; upper- and lower-face locked reruns pass. An early wrong-camera export selected zero tiles; the exact camera export was rerun. Initial multi-LOD capture meshes mixed neighboring shells, so the final captures use focused LOD0/LOD8 surface patches.
- One fine-normal probe had a tuple-index error and was corrected. A SW far-mesh filter had an argument/layout parsing error and was corrected. The density CSV was overwritten by verbose output; the probe was rebuilt from final Core objects and rerun.
- The transition-cache build failed on a nested `Shape` initializer; the retry built, but the cache showed no gain and was removed. `/usr/bin/time` was unavailable for one capture attempt; the capped renderer was rerun directly. The first determinism run was interrupted and resumed; the final matrix passes 24/24. One log-search command used unavailable `rg`; a capped streaming-Python scan replaced it.
- Performance experiments were rejected or superseded: initial transition path +36.11% SW/+7.18% FI, planar query +14.89%/+2.28%, 3x3 coverage +6.06%/+1.65%, 5-point coverage +4.04%/+2.03%, single-transition fast path +3.52%/+1.39%; cache trial was slower (2.188 s vs 2.171 s). The retained hash-prefix version is the five-pair result above. Logs and exports are under `Saved/Linux/l32/`.
## Orchestrator review (Claude)
- Merged. Captures checked by me: the FI floor (near, tint off) is a smooth rolling surface with no rings or terraces;
the SW ceiling and far views are smooth. Gradient is restored at width/8 (about 4 m peak-to-peak) with signed-distance
density; the ring audit rejects both L30 (rings) and L31 (flat). The memory-pressure kill mid-round was resumed;
audits now run capped and tile-streamed.
- Cumulative SW cost vs L24 is roughly +5% after L28–L32 (features: columns, transitions, smooth faces). FI is ≈ +2%.
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Run the recipe-stamped L31 passage audit matrix and reject mislabeled runs."""
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l32"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l32-gcc-v20",
}
MARKERS = (
"[ResolvedPassageAuditSummary]",
"[PassageOpenAirAuditSummary]",
"[PassageNaturalnessSummary]",
"[PassageNoLongBridgeAudit]",
"[PassageRockColumnAuditSummary]",
)
rows = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for style in range(4):
data = ROOT / f"Saved/Linux/linux25-owner-{recipe}-style{style}.json"
for compiler, binary in BINARIES.items():
name = f"passage-{recipe}-style{style}-{compiler}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", str(binary), str(data), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate",
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", "--audit-resolved-layout-passages",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
recipe_ok = stamps == [(recipe, recipe, "PASS")]
markers_ok = all(re.search(re.escape(marker) + r".*status=PASS", text)
for marker in MARKERS)
capsule_ok = bool(re.search(
r"^\[ResolvedPassageAudit\].* blocked=0 support_samples=\d+ "
r"unsupported=0 grade_failures=0 mouth_content=2/2 mouth_radius=2/2 "
r"walkability=PASS$", text, re.M))
island_route_ok = (recipe != "floating-islands" or bool(re.search(
r"^\[FloatingIslandRouteAudit\].* blocked=0 support_samples=\d+ "
r"unsupported=0 grade_failures=0 status=PASS$", text, re.M)))
binding_ok = bool(re.search(
r"\[ResolvedPassageAuditSummary\].*route_bindings=[1-9][0-9]* "
r"route_binding_failures=0 .*status=PASS", text))
walk_ok = "[Walkability] PASS" in text
if recipe == "floating-islands":
control_ok = "[PassageColumnDisconnectedEndControl]" in text and bool(
re.search(r"\[PassageColumnDisconnectedEndControl\].*detected=true", text))
else:
control_ok = True
ok = (result is not None and result.returncode == 0 and recipe_ok
and markers_ok and capsule_ok and island_route_ok and binding_ok
and walk_ok and control_ok)
actual_recipe = stamps[0][1] if len(stamps) == 1 else "MISSING"
rows.append((recipe, actual_recipe, style, compiler, elapsed,
result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s exit={result.returncode if result else 'TIMEOUT'} "
f"recipe={stamps[0][1] if stamps else 'MISSING'} "
f"capsule={'PASS' if capsule_ok and island_route_ok else 'FAIL'} "
f"column_control={'PASS' if control_ok else 'FAIL'} {'PASS' if ok else 'FAIL'}",
flush=True)
if not ok:
failed = True
summary = OUT / "passage-audit-matrix.csv"
summary.write_text(
"requested_recipe,actual_recipe,style,compiler,elapsed_seconds,exit_code,status\n" +
"".join(f"{requested},{actual},{style},{compiler},{elapsed:.3f},{code},"
f"{'PASS' if ok else 'FAIL'}\n"
for requested, actual, style, compiler, elapsed, code, ok in rows), encoding="utf-8")
control_log = OUT / "recipe-mismatch-control.log"
control_owner = OUT / "recipe-mismatch-control-owner.json"
control_owner_data = json.loads(
(ROOT / "Saved/Linux/linux25-owner-surface-world-style0.json").read_text(encoding="utf-8"))
top_owner_stratum = max(control_owner_data["strates"], key=lambda stratum: stratum["top_chunk_z"])
top_owner_stratum["asset"]["GeneratorType"] = 6 # ECaveGeneratorType::FloatingIslands
control_owner.write_text(json.dumps(control_owner_data, separators=(",", ":")), encoding="utf-8")
control_command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", str(BINARIES["clang"]),
str(control_owner),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", "--recipe=surface-world",
"--audit-resolved-layout-passages",
]
with control_log.open("w", encoding="utf-8") as stream:
control = subprocess.run(control_command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
control_text = control_log.read_text(encoding="utf-8", errors="replace")
control_ok = (control.returncode == 2 and
"[WorldAssemblerRecipeAudit] requested=surface-world actual=floating-islands "
"top_generator=FloatingIslands " in control_text and
"floating_islands_stratum=present status=FAIL" in control_text)
print(f"[L32RecipeMismatchControl] requested=surface-world actual=floating-islands "
f"exit={control.returncode} detected={'true' if control_ok else 'false'} "
f"{'PASS' if control_ok else 'FAIL'}", flush=True)
print(f"[L32PassageMatrix] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or not control_ok or len(rows) != 16 else 0)
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Run five interleaved whole-view pairs against the L31 binary."""
import os
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l32"
OUT.mkdir(parents=True, exist_ok=True)
RUN_TAG = os.environ.get("VOXELFORGE_COST_TAG", "").strip()
BINARIES = {
"l31": ROOT / "Saved/Linux/l31/bin/VoxelForgeCoreHeadless-linux31-clang",
"l32": Path(os.environ.get("VOXELFORGE_L32_COST_BINARY",
str(OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20"))),
}
ORDERS = (("l31", "l32"), ("l32", "l31"), ("l31", "l32"),
("l32", "l31"), ("l31", "l32"))
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
rows = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for pair, order in enumerate(ORDERS, 1):
for build in order:
tag = f"-{RUN_TAG}" if RUN_TAG else ""
name = f"cost{tag}-{recipe}-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(BINARIES[build]), str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
matches = READY.findall(text)
if build == "l32":
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
actual_recipe = stamps[0][1] if len(stamps) == 1 else "missing"
stamp_ok = stamps == [(recipe, recipe, "PASS")]
else:
configured = re.findall(r"^\[HeadlessConfig\].*\brecipe=([^\s]+)", text, re.M)
actual_recipe = configured[0] if len(configured) == 1 else "missing"
floating_plan = "[FloatingIslandWorldAssembly]" in text
surface_plan = "[WorldAssemblerPrototype]" in text
plan_matches = ((actual_recipe == "floating-islands" and floating_plan)
or (actual_recipe == "surface-world" and not floating_plan
and surface_plan))
marker = (f"[L32BaselineRecipeAudit] requested={recipe} actual={actual_recipe} "
f"headless_recipe={configured[0] if len(configured) == 1 else 'missing'} "
f"floating_island_plan={'present' if floating_plan else 'absent'} "
f"surface_world_plan={'present' if surface_plan else 'absent'} "
f"status={'PASS' if actual_recipe == recipe and plan_matches else 'FAIL'}\n")
with logfile.open("a", encoding="utf-8") as stream:
stream.write(marker)
text += marker
stamp_ok = actual_recipe == recipe and plan_matches and len(configured) == 1
valid = (result is not None and result.returncode == 0 and len(matches) == 1
and "hash=off" in text and "cache_hits=0" in text and stamp_ok)
ready = float(matches[0]) if len(matches) == 1 else float("nan")
rows.append((recipe, pair, build, ready, elapsed,
result.returncode if result else "TIMEOUT", valid))
print(f"{recipe} pair={pair} build={build} whole_view_ready={ready:.3f}s "
f"process_elapsed={elapsed:.1f}s {'PASS' if valid else 'FAIL'}", flush=True)
failed |= not valid
summary = OUT / (f"cost-runs-{RUN_TAG}.csv" if RUN_TAG else "cost-runs.csv")
summary.write_text(
"recipe,pair,build,whole_view_ready_seconds,process_elapsed_seconds,exit_code,status\n" +
"".join(f"{recipe},{pair},{build},{ready:.6f},{elapsed:.3f},{code},"
f"{'PASS' if ok else 'FAIL'}\n"
for recipe, pair, build, ready, elapsed, code, ok in rows), encoding="utf-8")
for recipe in ("surface-world", "floating-islands"):
by_build = {build: [row[3] for row in rows if row[0] == recipe and row[2] == build]
for build in BINARIES}
l31 = statistics.median(by_build["l31"])
l32 = statistics.median(by_build["l32"])
paired = []
for pair in range(1, 6):
values = {row[2]: row[3] for row in rows if row[0] == recipe and row[1] == pair}
paired.append((values["l32"] - values["l31"]) / values["l31"] * 100.0)
print(f"[L32CostSummary] recipe={recipe} pairs=5 median_l31_s={l31:.3f} "
f"median_l32_s={l32:.3f} median_change_pct={(l32/l31-1)*100.0:+.2f} "
f"paired_change_pct={','.join(f'{value:+.2f}' for value in paired)}")
print(f"[L32Cost] runs={len(rows)} status={'FAIL' if failed else 'PASS'} summary={summary}",
flush=True)
raise SystemExit(1 if failed or len(rows) != 20 else 0)
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Check the L25 hash command and 24 m crossing across compilers and workers."""
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l32"
OUT.mkdir(parents=True, exist_ok=True)
RUN_TAG = os.environ.get("VOXELFORGE_DETERMINISM_TAG", "").strip()
RUN_RESUME = os.environ.get("VOXELFORGE_DETERMINISM_RESUME", "0") == "1"
BINARIES = {
"clang": Path(os.environ.get("VOXELFORGE_L32_CLANG_BINARY",
str(OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20"))),
"gcc": Path(os.environ.get("VOXELFORGE_L32_GCC_BINARY",
str(OUT / "bin/VoxelForgeCoreHeadless-l32-gcc-v20"))),
}
HASH_PATTERN = re.compile(
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
candidate_reference = {}
rows = []
failed = False
prior_elapsed = {}
if RUN_RESUME and RUN_TAG:
prior_log = OUT / f"determinism-{RUN_TAG}-runner.log"
if prior_log.exists():
for line in prior_log.read_text(encoding="utf-8", errors="replace").splitlines():
match = re.match(r"^(\S+) elapsed=([0-9.]+)s .* PASS$", line)
if match:
prior_elapsed[match.group(1)] = float(match.group(2))
def run(kind, compiler, recipe, workers):
global failed
tag = f"-{RUN_TAG}" if RUN_TAG else ""
name = f"{kind}{tag}-{compiler}-{recipe}-w{workers}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(BINARIES[compiler]), str(ROOT / "Standalone/data/owner_world.json"),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--stream-hash", "--force-generate", "--clip-radius=3", "--max-clip-level=8",
"--far-horizon-lod8-step=0", f"--workers={workers}", f"--recipe={recipe}",
]
if kind == "crossing24":
command.extend(("--camera-step=24,0,0", "--region-crossing"))
if RUN_RESUME and name in prior_elapsed and logfile.exists():
result = subprocess.CompletedProcess(command, 0)
elapsed = prior_elapsed[name]
else:
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
hashes = HASH_PATTERN.findall(text)
key = (kind, recipe)
reference = candidate_reference.get(key)
stable = reference is None or hashes == reference
if reference is None and hashes:
candidate_reference[key] = hashes
ok = (result is not None and result.returncode == 0
and stamps == [(recipe, recipe, "PASS")]
and "hash=on" in text and len(hashes) == 12 and stable)
rows.append((kind, recipe, compiler, workers, elapsed,
result.returncode if result else "TIMEOUT", len(hashes), ok))
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
f"hashes={len(hashes)} stable_across_matrix={'yes' if stable else 'no'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
failed |= not ok
for kind in ("l25", "crossing24"):
for recipe in ("surface-world", "floating-islands"):
for compiler in ("clang", "gcc"):
for workers in (1, 8, 12):
run(kind, compiler, recipe, workers)
summary = OUT / (f"determinism-{RUN_TAG}.csv" if RUN_TAG else "determinism.csv")
summary.write_text(
"kind,recipe,compiler,workers,elapsed_seconds,exit_code,hash_rows,status\n" +
"".join(f"{kind},{recipe},{compiler},{workers},{elapsed:.3f},{code},{count},"
f"{'PASS' if ok else 'FAIL'}\n"
for kind, recipe, compiler, workers, elapsed, code, count, ok in rows),
encoding="utf-8")
print(f"[L32Determinism] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 24 else 0)
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Run L31 route/proof and transition invariants with recipe-stamped logs."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l32"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l32-clang-v20",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l32-gcc-v20",
}
rows = []
failed = False
def run(name, binary, data, recipe, extra, checks):
global failed
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", str(binary), str(data), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate",
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", *extra,
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
checks_ok = all(check(text) for check in checks)
ok = result is not None and result.returncode == 0 and stamp_ok and checks_ok
rows.append((name, recipe, elapsed, result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
f"stamp={'PASS' if stamp_ok else 'FAIL'} checks={'PASS' if checks_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
failed |= not ok
owner = ROOT / "Standalone/data/owner_world.json"
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
name = f"invariant-route-proof-{recipe}-{compiler}"
run(name, binary, owner, recipe,
["--stream-hash", "--audit-uniform-block-proofs", "--audit-route-parity"],
[lambda text: bool(re.search(
r"^\[RouteParityAudit\].*status=PASS$", text, re.M)),
lambda text: bool(re.search(
r"^\[UniformBlockProofAudit\].*violations=0(?:\s|$)", text, re.M))])
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
for mode in ("hard", "gradient", "interleaved"):
name = f"invariant-transition-{recipe}-{mode}-{compiler}"
run(name, binary, owner, recipe,
[f"--transition-mode={mode}", "--audit-strate-transitions"],
[lambda text: bool(re.search(
r"^\[StrateTransitionAuditSummary\].*hole_columns=0 air_samples=0 .*status=PASS$",
text, re.M)),
lambda text: bool(re.search(
r"^\[StrateTransitionNegativeControls\].*status=PASS$", text, re.M))])
summary = OUT / "invariant-audits.csv"
summary.write_text(
"run,recipe,elapsed_seconds,exit_code,status\n" +
"".join(f"{name},{recipe},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
for name, recipe, elapsed, code, ok in rows),
encoding="utf-8")
print(f"[L32InvariantAudits] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 16 else 0)
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Stream one VFMSH tile at a time to locate LOD/interface patches."""
import argparse
import array
import resource
import struct
import sys
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scene")
parser.add_argument("--lod", type=int, required=True)
parser.add_argument("--surface", choices=("floor", "ceiling"), required=True)
parser.add_argument("--height-min-m", type=float, required=True)
parser.add_argument("--height-max-m", type=float, required=True)
args = parser.parse_args()
candidates = []
scanned = 0
with open(args.scene, "rb") as stream:
if stream.read(8) != b"VFMSH001":
raise SystemExit("invalid VFMSH001 scene")
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
stream.seek(40, 1)
if version != 1:
raise SystemExit("unsupported mesh scene")
for _ in range(tile_count):
key = struct.unpack("<4i", stream.read(16))
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
payload = vertex_count * 24 + index_count * 4 + mask_count
if key[3] != args.lod:
stream.seek(payload, 1)
continue
scanned += 1
positions = array.array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array.array("f")
normals.frombytes(stream.read(vertex_count * 12))
indices = array.array("I")
indices.frombytes(stream.read(index_count * 4))
masks = stream.read(mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
hits = 0
sum_x = sum_y = sum_z = 0.0
min_z, max_z = float("inf"), float("-inf")
want_up = args.surface == "floor"
for offset in range(0, index_count, 3):
if masks and masks[offset // 3]:
continue
a, b, c = indices[offset:offset + 3]
if max(a, b, c) >= surface_count:
continue
nz = (normals[a * 3 + 2] + normals[b * 3 + 2] + normals[c * 3 + 2]) / 3.0
if (nz > 0) != want_up:
continue
za, zb, zc = positions[a * 3 + 2], positions[b * 3 + 2], positions[c * 3 + 2]
if ((za + zb + zc) / 3.0 < args.height_min_m
or (za + zb + zc) / 3.0 > args.height_max_m):
continue
hits += 1
sum_x += (positions[a * 3] + positions[b * 3] + positions[c * 3]) / 3.0
sum_y += (positions[a * 3 + 1] + positions[b * 3 + 1] + positions[c * 3 + 1]) / 3.0
z = (za + zb + zc) / 3.0
sum_z += z
min_z, max_z = min(min_z, za, zb, zc), max(max_z, za, zb, zc)
if hits:
extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0
candidates.append((hits, key, sum_x / hits, sum_y / hits,
sum_z / hits, min_z, max_z, extent))
candidates.sort(reverse=True, key=lambda item: item[0])
print(f"[VFMeshTileScan] lod={args.lod} surface={args.surface} scanned_tiles={scanned} "
f"candidate_tiles={len(candidates)} peak_rss_kib="
f"{resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}")
for hits, key, x, y, z, low, high, extent in candidates[:40]:
print(f"[VFMeshTile] key={key} triangles={hits} centroid_m=({x:.3f},{y:.3f},{z:.3f}) "
f"z_m={low:.3f}..{high:.3f} extent_m={extent:.3f}")
if __name__ == "__main__":
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Build owner-like FloatingIslands -> SurfaceWorld -> TunnelNetwork fixtures."""
import copy
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
SOURCE = ROOT / "Standalone/data/owner_world.json"
OUT = ROOT / "Saved/Linux/l33/fixtures"
def make_fixture(style: int) -> Path:
world = json.loads(SOURCE.read_text(encoding="utf-8"))
source_tunnel, source_surface = world["strates"]
floating = copy.deepcopy(source_tunnel)
floating.update(index=0, top_chunk_z=0, bottom_chunk_z=-15, height_chunks=16)
floating["asset"]["StrateName"] = "Linux33 Upper FloatingIslands"
floating["asset"]["StrateHeightInChunks"] = 16
floating["asset"]["GeneratorType"] = 6
surface = copy.deepcopy(source_surface)
surface.update(index=1, top_chunk_z=-16, bottom_chunk_z=-79, height_chunks=64)
surface["asset"]["StrateName"] = "Linux33 Middle SurfaceWorld"
surface["asset"]["StrateHeightInChunks"] = 64
surface["asset"]["PassageConfig"]["Style"] = style
surface["passage_config"]["Style"] = style
tunnel = copy.deepcopy(source_tunnel)
tunnel.update(index=2, top_chunk_z=-80, bottom_chunk_z=-143, height_chunks=64)
tunnel["asset"]["StrateName"] = "Linux33 Lower TunnelNetwork"
tunnel["asset"]["StrateHeightInChunks"] = 64
world["strates"] = [floating, surface, tunnel]
world["total_strates"] = 3
world["settings_asset"]["TotalStrates"] = 3
world["settings_asset"]["StratePool"] = [
"/Standalone/Linux33/UpperFloatingIslands",
"/Standalone/Linux33/MiddleSurfaceWorld",
"/Standalone/Linux33/LowerTunnelNetwork",
]
world["passages"] = [] # The L23 legacy coordinates are not part of this resolved stack.
world["source"] = "VoxelForge Linux33 owner-like SurfaceWorld-upper fixture"
OUT.mkdir(parents=True, exist_ok=True)
path = OUT / f"sw-upper-tunnel-network-style{style}.json"
path.write_text(json.dumps(world, separators=(",", ":")), encoding="utf-8")
return path
if __name__ == "__main__":
for style in range(4):
path = make_fixture(style)
print(f"style={style} fixture={path}")
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Export and render two bounded SurfaceWorld departure views."""
import os
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l33"
FIXTURE = OUT / "fixtures/sw-upper-tunnel-network-style0.json"
HEADLESS = OUT / "bin/VoxelForgeCoreHeadless-clang-l33-final"
CAPTURE = OUT / "bin/VoxelForgeOffscreenCapture-clang-l33-final"
PNG_DIR = OUT / "capture-png"
DEST = Path(__file__).resolve().parent / "captures"
PNG_DIR.mkdir(parents=True, exist_ok=True)
DEST.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
views = (
{
"name": "sw-departure-mouth",
"export_camera": "18.75,-12,-499,0,-22",
"render_camera": "18.75,0,-490",
"yaw": "0",
"pitch": "-90",
"fov": "38",
"height_center": "-503.166",
"height_range": "4",
"light": "0,-1,0.2",
"headlamp": "off",
"clip": None,
},
{
"name": "sw-departure-rock-entry",
"export_camera": "26,-16,-499,64,-16",
"render_camera": "14,-4,-500",
"yaw": "78",
"pitch": "-16",
"fov": "32",
"height_center": "-503.166",
"height_range": "4",
"light": "0,-1,0.2",
"headlamp": "off",
"clip": "1,0,0,-18.75",
},
)
def run(name, command):
logfile = OUT / f"capture-{name}.log"
wrapped = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *command,
]
started = time.monotonic()
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, env=env, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
print(f"[L33CaptureRun] step={name} elapsed={time.monotonic()-started:.1f}s "
f"exit={result.returncode} log={logfile}", flush=True)
if result.returncode != 0:
raise SystemExit(f"{name} failed; see {logfile}")
for view in views:
name = view["name"]
mesh = OUT / f"{name}.vfmesh"
png = PNG_DIR / f"{name}.png"
jpg = DEST / f"{name}.jpg"
run(f"{name}-export", [
str(HEADLESS), str(FIXTURE), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=2",
"--max-clip-level=0", "--max-level=0", "--render-distance-chunks=0",
"--recipe=floating-islands",
f"--camera={view['export_camera']}", f"--mesh-export={mesh}",
])
command = [
str(CAPTURE), str(mesh), str(png), "--size=1600x900", f"--fov-deg={view['fov']}",
f"--camera-m={view['render_camera']}", f"--yaw-deg={view['yaw']}",
f"--pitch-deg={view['pitch']}", "--shade=readable",
f"--headlamp={view['headlamp']}", f"--light-direction={view['light']}",
"--height-tint=on", f"--height-center-m={view['height_center']}",
f"--height-range-m={view['height_range']}", "--tile-edges=off",
]
if view["clip"]:
command.append(f"--clip-plane={view['clip']}")
run(f"{name}-render", command)
run(f"{name}-jpeg", [
"ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-y",
"-i", str(png), "-frames:v", "1", "-q:v", "4", str(jpg),
])
size = jpg.stat().st_size if jpg.exists() else 0
ok = 0 < size <= 300_000
print(f"[L33CaptureJPEG] file={jpg.relative_to(ROOT)} bytes={size} "
f"status={'PASS' if ok else 'FAIL'}", flush=True)
if not ok:
raise SystemExit(f"{jpg.name} is missing or exceeds 300 KB")
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Check representative owner hashes against L32 before the full matrix."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l33"
L32 = ROOT / "Saved/Linux/l32"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-clang-l33-final",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-gcc-l33-final",
}
HASH_PATTERN = re.compile(
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
failed = False
for compiler, binary in BINARIES.items():
for recipe in ("surface-world", "floating-islands"):
name = f"owner-hash-preflight-{compiler}-{recipe}-w12"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(binary), str(ROOT / "Standalone/data/owner_world.json"),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--stream-hash", "--force-generate", "--clip-radius=3", "--max-clip-level=8",
"--far-horizon-lod8-step=0", "--workers=12", f"--recipe={recipe}",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
text = logfile.read_text(encoding="utf-8", errors="replace")
hashes = HASH_PATTERN.findall(text)
baseline_path = L32 / f"l25-settled-final-{compiler}-{recipe}-w12.log"
baseline = HASH_PATTERN.findall(baseline_path.read_text(
encoding="utf-8", errors="replace")) if baseline_path.exists() else []
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
same = len(hashes) == 12 and hashes == baseline
ok = (result is not None and result.returncode == 0 and same
and stamps == [(recipe, recipe, "PASS")])
failed |= not ok
print(f"{name} elapsed={time.monotonic()-started:.1f}s hashes={len(hashes)}/12 "
f"same_as_l32={'yes' if same else 'no'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
raise SystemExit(1 if failed else 0)
@@ -0,0 +1,58 @@
# Linux33 report
## Upper SurfaceWorld route
The old fixed candidate set used 64 m chords, 2 m waypoint spacing and 16 m centre spacing. On the refreshed upper-stack fixture, the legacy replay measured 366/648 candidates above the 15° grade limit; its worst segment was 79.971° at XY (−84,−48) to (−82,−48). These were long paths over steep terrain relief near the outer search area. L23’s earlier fixture reported 648/648 grade rejects; the refreshed fixture gives a different count. The route Z samples are terrain-ground heights (about −503 m at the chosen mouth), not a target at the bottom of the 499 m strate. There was no unit or bedrock-reference error.
The planner now caps chords at 58.5 m for the 24 m arrival and 37.5 m departure radii, searches six lengths and places route centres on an 8 m grid, with a proximity cost favouring the origin. It found a 21.5 m walk from (−2.75,0) to departure (18.75,0), average grade about 4.2°. The SW passage starts at the ground floor (−503.166 m), with a 1.5 m portal blending to a 3.5 m tunnel over 9 m, then descends through SW rock. The first tunnel point is at (32.5,0), floor −505.637 m for style 0.
## Upper-stack fixture audits
Fixture: FloatingIslands → SurfaceWorld → TunnelNetwork; recipe stamp `floating-islands`; layout identity `resolved-layout-v3-fnv64:fc4e73619037f88c`. Clang and GCC passed every row for every style.
| Passage style | Floor chord deviation (voxels) | Passage and route audits | Open air | Naturalness | Bridge / columns | Bindings / walkability |
|---:|---:|---|---|---|---|---|
| 0 | 2.081 | 2/2 each pass | 0 violations | 2/2 pass | pass / 0 failures | 0 failures / pass |
| 1 | 10.901 | 2/2 each pass | 0 violations | 2/2 pass | pass / 0 failures | 0 failures / pass |
| 2 | 14.822 | 2/2 each pass | 0 violations | 2/2 pass | pass / 0 failures | 0 failures / pass |
| 3 | 16.374 | 2/2 each pass | 0 violations | 2/2 pass | pass / 0 failures | 0 failures / pass |
All rows are recipe-stamped and passed on both compilers. Capsule blocking, unsupported samples and grade failures were zero; mouth-content and mouth-radius checks were 2/2. This includes open-air, naturalness, no-long-bridge, rock-column wall/support, route binding and walkability audits.
## FloorProfileBlend
The old 2-voxel check measured maximum curve-to-straight-chord deviation, which grows with valid curvature and does not measure a seam. The check now compares endpoint height and tangent residuals. All styles have 0.00000000 voxel height residual and 0.00000000 voxel/m tangent residual (limits 0.010 and 0.001). Negative controls detected a 3-voxel step and a 0.25 voxel/m tangent step. Verdict: check corrected; no floor geometry change was needed.
## Captures
Both 1600×900 JPEGs are under 300 KB. The first shows the SW ground mouth; the second is a cutaway through the rock entry.
Sizes and SHA-256: mouth 26,939 B, `561d3a1e334ea3a3115fb781a1130ec850d8777b74c1be3704c70b865878640a`; rock entry 37,572 B, `f85d150bf44cd2cdae1ee49de023ad9e4c657b8fa7e8d8f8b93abc86586ad6e5`.
![SurfaceWorld departure mouth](captures/sw-departure-mouth.jpg)
![SurfaceWorld departure rock entry](captures/sw-departure-rock-entry.jpg)
## Hashes, determinism and cost
Owner-world hashes match L32 exactly: all 12 hash lines per run across Clang/GCC and both recipes passed the owner preflight; the L25 and 24 m crossing matrix also passed at W1/W8/W12 (24/24 owner rows). W12 whole-view hashes: SurfaceWorld density `953eb1df1705f277`, mesh `d8006cb43f44aa5b`, payload `9217bff766970de5`, topology `83e3aa6f3a063e60`; FloatingIslands density `806537cdd98f8afb`, mesh `a4b06dd4d260943d`, payload `763a99aa04a73eae`, topology `baa37d80d3620d87`. The upper fixture was stable across W1/W8/W12 on both compilers. Total determinism: 30/30. Invariant audits: 16/16.
Five interleaved W12 cost pairs per recipe against L32: median whole-view ready time changed +0.99% for SurfaceWorld and +0.41% for FloatingIslands; paired medians were +0.57% and +0.41%, within run noise. Recipe/evaluator versions remain 2/20. No Unreal files changed; no Unreal build was run. No version bump was needed because the owner hashes are unchanged.
## Failed and retried runs
- CLI setup rejected `--compiler clang`; the accepted form was `--compiler=clang++-19`. One shell quoting attempt ended at EOF. A FloatingIslands fixture initially had a `surface-world` recipe stamp and was regenerated with `floating-islands`.
- A translated ring centre failed the strict placement disk and was reverted. Fixture audit iterations: r1 had style 1–3 failures (TN capsule pinch, route undercut, naturalness); r2 had 141 open-air violations plus route/naturalness failures; r3 had strict ring-placement and route-support failures; r4 had four edge open-air samples. The broad column-window fix passed passage audits but changed owner hashes. Its first scoped retry dropped supported bracket samples and failed SurfaceWorld hash preflight on both compilers; restoring the original non-SW bracket bounds fixed it.
- An intermediate Clang build failed on `coreVoxelSizeMetres` and `gradeSegments`; both names were corrected. Final Clang 19 and GCC 14 `--fp-contract=off`, `-j12` builds succeeded. An early determinism run on stale binaries was stopped after 21 rows and showed the interim hash mismatch; the final binaries passed all 30 rows.
- The first capture export included the far ring and grew to 2.49 GB; it was stopped before rendering. The LOD0 streamed export reduced the scene to about 2.2 MB. Capture framings r5–r8 rendered successfully but obscured the mouth; r9 is the inspected final framing.
No run exceeded 30 minutes and no step remained stuck for 15 minutes. Logs and exports are in `Saved/Linux/l33/`; no commit, push or stash was made.
## Orchestrator review (Claude)
- Merged: the owner world is unchanged (hashes equal L32); SW-as-upper now plans its route and passes the full audit set
on the FI→SW→TN fixture. The FloorProfileBlend check now measures seams (endpoint height/tangent residuals) with
step/tangent negative controls. Accepted: chord deviation measured curvature, not a defect.
- Captures: `sw-departure-mouth.jpg` is out of focus and unreadable; `sw-departure-rock-entry.jpg` shows a natural opening
but **horizontal step-like ledges inside**, reminiscent of the L23 terraces. L34 checks passage mouths and entries for
stepping across styles and fixes the blurry capture setup.
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Run five interleaved W12 whole-view pairs against the L32 binary."""
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l33"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"l32": ROOT / "Saved/Linux/l32/bin/VoxelForgeCoreHeadless-l32-clang-v20",
"l33": OUT / "bin/VoxelForgeCoreHeadless-clang-l33-final",
}
ORDERS = (("l32", "l33"), ("l33", "l32"), ("l32", "l33"),
("l33", "l32"), ("l32", "l33"))
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
rows = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for pair, order in enumerate(ORDERS, 1):
for build in order:
name = f"cost-{recipe}-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(BINARIES[build]), str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
matches = READY.findall(text)
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
valid = (result is not None and result.returncode == 0 and len(matches) == 1
and "hash=off" in text and "cache_hits=0" in text and stamp_ok)
ready = float(matches[0]) if len(matches) == 1 else float("nan")
rows.append((recipe, pair, build, ready, elapsed,
result.returncode if result else "TIMEOUT", valid))
print(f"{recipe} pair={pair} build={build} whole_view_ready={ready:.3f}s "
f"process_elapsed={elapsed:.1f}s {'PASS' if valid else 'FAIL'}", flush=True)
failed |= not valid
summary = OUT / "cost-runs.csv"
summary.write_text(
"recipe,pair,build,whole_view_ready_seconds,process_elapsed_seconds,exit_code,status\n" +
"".join(f"{recipe},{pair},{build},{ready:.6f},{elapsed:.3f},{code},"
f"{'PASS' if ok else 'FAIL'}\n"
for recipe, pair, build, ready, elapsed, code, ok in rows), encoding="utf-8")
for recipe in ("surface-world", "floating-islands"):
by_build = {build: [row[3] for row in rows if row[0] == recipe and row[2] == build]
for build in BINARIES}
baseline = statistics.median(by_build["l32"])
current = statistics.median(by_build["l33"])
paired = []
for pair in range(1, 6):
values = {row[2]: row[3] for row in rows if row[0] == recipe and row[1] == pair}
paired.append((values["l33"] - values["l32"]) / values["l32"] * 100.0)
print(f"[L33CostSummary] recipe={recipe} pairs=5 median_l32_s={baseline:.3f} "
f"median_l33_s={current:.3f} median_change_pct={(current/baseline-1)*100.0:+.2f} "
f"paired_change_pct={','.join(f'{value:+.2f}' for value in paired)}", flush=True)
print(f"[L33Cost] runs={len(rows)} status={'FAIL' if failed else 'PASS'} summary={summary}",
flush=True)
raise SystemExit(1 if failed or len(rows) != 20 else 0)
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""Run owner L25 hashes, 24 m crossings, and upper-SurfaceWorld fixture determinism."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l33"
L32 = ROOT / "Saved/Linux/l32"
FIXTURE = OUT / "fixtures/sw-upper-tunnel-network-style0.json"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-clang-l33-final",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-gcc-l33-final",
}
HASH_PATTERN = re.compile(
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
LAYOUT_PATTERN = re.compile(
r"^\[ResolvedLayoutSnapshot\] schema=\d+ stable_identity=([^ ]+) strata=\d+ bytes=\d+ round_trip=PASS$",
re.M)
PLAN_PATTERN = re.compile(
r"^\[WorldAssemblerPlanSerialization\].*canonical_round_trip=true .*"
r"plan_hash=([0-9a-f]+) manifest_hash=([0-9a-f]+) status=PASS$", re.M)
ROUTE_PATTERN = re.compile(
r"^\[WorldAssemblerRouteAccepted\] arrival_xy_m=\([^)]*\) "
r"departure_xy_m=\([^)]*\) endpoint_proximity_cost=\d+ average_grade_q16=\d+$", re.M)
rows = []
failed = False
owner_reference = {}
fixture_reference = None
def run(command, logfile):
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
return result, time.monotonic() - started, logfile.read_text(
encoding="utf-8", errors="replace")
for kind in ("l25", "crossing24"):
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
for workers in (1, 8, 12):
name = f"{kind}-{compiler}-{recipe}-w{workers}"
logfile = OUT / f"determinism-{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(binary), str(ROOT / "Standalone/data/owner_world.json"),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--stream-hash", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--far-horizon-lod8-step=0",
f"--workers={workers}", f"--recipe={recipe}",
]
if kind == "crossing24":
command.extend(("--camera-step=24,0,0", "--region-crossing"))
result, elapsed, text = run(command, logfile)
hashes = HASH_PATTERN.findall(text)
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
ref = L32 / f"{kind}-settled-final-{compiler}-{recipe}-w{workers}.log"
reference_hashes = HASH_PATTERN.findall(ref.read_text(
encoding="utf-8", errors="replace")) if ref.exists() else []
stable_key = (kind, recipe)
stable = stable_key not in owner_reference or hashes == owner_reference[stable_key]
if stable_key not in owner_reference and hashes:
owner_reference[stable_key] = hashes
equal_l32 = len(hashes) == 12 and hashes == reference_hashes
ok = (result is not None and result.returncode == 0
and stamps == [(recipe, recipe, "PASS")]
and "hash=on" in text and len(hashes) == 12 and stable and equal_l32)
rows.append((kind, recipe, compiler, workers, elapsed,
result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s hashes={len(hashes)}/12 "
f"same_across_matrix={'yes' if stable else 'no'} "
f"same_as_l32={'yes' if equal_l32 else 'no'} {'PASS' if ok else 'FAIL'}",
flush=True)
failed |= not ok
for compiler, binary in BINARIES.items():
for workers in (1, 8, 12):
name = f"upper-fixture-{compiler}-w{workers}"
logfile = OUT / f"determinism-{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(binary), str(FIXTURE), "--workers=" + str(workers),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8",
"--max-level=-1", "--recipe=floating-islands", "--audit-resolved-layout-passages",
]
result, elapsed, text = run(command, logfile)
layouts = LAYOUT_PATTERN.findall(text)
plans = PLAN_PATTERN.findall(text)
routes = ROUTE_PATTERN.findall(text)
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
passage_summary = re.search(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", text, re.M)
signature = (tuple(layouts), tuple(plans), tuple(routes))
stable = fixture_reference is None or signature == fixture_reference
if fixture_reference is None and layouts and plans and routes:
fixture_reference = signature
ok = (result is not None and result.returncode == 0 and len(layouts) == 1
and len(plans) == 1 and len(routes) == 1
and stamps == [("floating-islands", "floating-islands", "PASS")]
and passage_summary is not None and stable)
rows.append(("upper-fixture", "floating-islands", compiler, workers, elapsed,
result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s layout={layouts[0] if layouts else 'MISSING'} "
f"stable_across_matrix={'yes' if stable else 'no'} {'PASS' if ok else 'FAIL'}",
flush=True)
failed |= not ok
summary = OUT / "determinism-l33.csv"
summary.write_text(
"kind,recipe,compiler,workers,elapsed_seconds,exit_code,status\n" +
"".join(f"{kind},{recipe},{compiler},{workers},{elapsed:.3f},{code},"
f"{'PASS' if ok else 'FAIL'}\n"
for kind, recipe, compiler, workers, elapsed, code, ok in rows),
encoding="utf-8")
print(f"[L33Determinism] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 30 else 0)
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Run L33 route/proof and transition invariants with recipe-stamped logs."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l33"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-clang-l33-final",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-gcc-l33-final",
}
rows = []
failed = False
def run(name, binary, data, recipe, extra, checks):
global failed
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(binary), str(data), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}", *extra,
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
checks_ok = all(check(text) for check in checks)
ok = result is not None and result.returncode == 0 and stamp_ok and checks_ok
rows.append((name, recipe, elapsed, result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
f"stamp={'PASS' if stamp_ok else 'FAIL'} checks={'PASS' if checks_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
failed |= not ok
owner = ROOT / "Standalone/data/owner_world.json"
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
name = f"invariant-route-proof-{recipe}-{compiler}"
run(name, binary, owner, recipe,
["--stream-hash", "--audit-uniform-block-proofs", "--audit-route-parity"],
[lambda text: bool(re.search(
r"^\[RouteParityAudit\].*status=PASS$", text, re.M)),
lambda text: bool(re.search(
r"^\[UniformBlockProofAudit\].*violations=0(?:\s|$)", text, re.M))])
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
for mode in ("hard", "gradient", "interleaved"):
name = f"invariant-transition-{recipe}-{mode}-{compiler}"
run(name, binary, owner, recipe,
[f"--transition-mode={mode}", "--audit-strate-transitions"],
[lambda text: bool(re.search(
r"^\[StrateTransitionAuditSummary\].*hole_columns=0 air_samples=0 .*status=PASS$",
text, re.M)),
lambda text: bool(re.search(
r"^\[StrateTransitionNegativeControls\].*status=PASS$", text, re.M))])
summary = OUT / "invariant-audits.csv"
summary.write_text(
"run,recipe,elapsed_seconds,exit_code,status\n" +
"".join(f"{name},{recipe},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
for name, recipe, elapsed, code, ok in rows),
encoding="utf-8")
print(f"[L33InvariantAudits] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 16 else 0)
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Recipe-stamped resolved-passage audits for the L33 upper-SurfaceWorld stack."""
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l33"
TAG = os.environ.get("VOXELFORGE_L33_AUDIT_TAG", "clang").strip() or "clang"
BINARY = Path(os.environ.get("VOXELFORGE_L33_AUDIT_BINARY",
str(OUT / f"bin/VoxelForgeCoreHeadless-{TAG}-l33-final")))
ROWS = []
FAILED = False
for style in range(4):
name = f"upper-sw-three-strata-{TAG}-style{style}"
fixture = OUT / f"fixtures/sw-upper-tunnel-network-style{style}.json"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
str(BINARY), str(fixture), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate",
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
"--recipe=floating-islands", "--audit-resolved-layout-passages",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamp_ok = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M) == [("floating-islands", "floating-islands", "PASS")]
summary = re.search(r"^\[ResolvedPassageAuditSummary\].*$", text, re.M)
summary_ok = bool(summary and "interfaces=2 passed=2 natural=2/2" in summary.group()
and "route_walkable=2" in summary.group()
and "route_binding_failures=0" in summary.group()
and "status=PASS" in summary.group())
markers = (
r"^\[PassageOpenAirAuditSummary\].*violations=0.*status=PASS$",
r"^\[PassageNoLongBridgeAudit\].*status=PASS$",
r"^\[PassageRockColumnAuditSummary\].*wall_or_support_failures=0.*status=PASS$",
r"^\[PassageNaturalnessSummary\].*status=PASS$",
)
markers_ok = all(re.search(pattern, text, re.M) for pattern in markers)
passage_rows = re.findall(
r"^\[ResolvedPassageAudit\].*blocked=0 support_samples=\d+ unsupported=0 "
r"grade_failures=0 mouth_content=2/2 mouth_radius=2/2 walkability=PASS$",
text, re.M)
floor_rows = re.findall(r"^\[FloorProfileBlend\].*status=PASS$", text, re.M)
route_rows = re.findall(
r"^\[ResolvedStrateRouteAudit\].*blocked=0 support_samples=\d+ unsupported=0 "
r"grade_failures=0 mouth_binding=PASS status=PASS$", text, re.M)
ok = (result is not None and result.returncode == 0 and stamp_ok and summary_ok
and markers_ok and len(passage_rows) == 2 and len(route_rows) == 2
and len(floor_rows) >= 1 and "[Walkability] PASS" in text)
FAILED |= not ok
ROWS.append((style, elapsed, result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s exit={result.returncode if result else 'TIMEOUT'} "
f"recipe={'PASS' if stamp_ok else 'FAIL'} passages={len(passage_rows)}/2 "
f"routes={len(route_rows)}/2 floor_blend_rows={len(floor_rows)} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
summary_path = OUT / f"upper-sw-three-strata-{TAG}-audits.csv"
summary_path.write_text(
"style,elapsed_seconds,exit_code,status\n" +
"".join(f"{style},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
for style, elapsed, code, ok in ROWS), encoding="utf-8")
print(f"[L33UpperSurfaceFixtureAudit] compiler={TAG} runs={len(ROWS)} pass={sum(row[-1] for row in ROWS)} "
f"fail={sum(not row[-1] for row in ROWS)} summary={summary_path}", flush=True)
raise SystemExit(1 if FAILED or len(ROWS) != 4 else 0)
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@@ -0,0 +1,251 @@
#!/usr/bin/env python3
"""Measure exported passage-floor meshes along the first 12 m at each mouth."""
import csv
import importlib.util
import json
import math
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l34"
RESEARCH = Path(__file__).resolve().parent
RING_AUDIT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
SPEC = importlib.util.spec_from_file_location("ring_profile_l32", RING_AUDIT)
PROFILE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(PROFILE)
Q_TO_M = 1.0 / 256.0
SPACING = 0.125
PLATEAU = 0.50
EPSILON = 0.02
STEP = 0.10
SCENES = (
("owner-fi", "layout-owner-fi.json", "FloatingIslands", False),
("owner-sw", "layout-owner-sw.json", "SurfaceWorld", True),
("upper-sw-fixture", "layout-upper-sw-fixture.json", "SurfaceWorld", False),
)
def dist(a, b):
return math.sqrt(sum((a[i] - b[i]) ** 2 for i in range(3)))
def interpolate(path, distance):
remaining = max(0.0, distance)
for a, b in zip(path, path[1:]):
length = dist(a[0], b[0])
if length <= 1.0e-9:
continue
if remaining <= length:
t = remaining / length
p = tuple(a[0][axis] + (b[0][axis] - a[0][axis]) * t
for axis in range(3))
return p, a[1] + (b[1] - a[1]) * t
remaining -= length
return path[-1]
def make_inward_path(layout, passage, reverse):
scale = layout["core_voxel_size_um"] / (256.0 * 1_000_000.0)
controls = [(tuple(value * scale for value in point["position_q"]),
point["radius_q"] * scale) for point in passage["control_points"]]
lower = tuple(value * scale for value in passage["lower_mouth_q"])
lower_index = min(range(len(controls)), key=lambda i: dist(controls[i][0], lower))
path = controls[:lower_index + 1]
if reverse:
path.reverse()
return path
def select_passage(layout, generator, prefer_lower):
strata = {int(value["strate_index"]): value for value in layout["strates"]}
preferred_side = "lower" if prefer_lower else "upper"
preferred_candidates = []
fallback_candidates = []
for passage in layout["passages"]:
upper = strata[int(passage["upper_strate_index"])]["generator_kind"]
lower = strata[int(passage["lower_strate_index"])]["generator_kind"]
sides = []
if upper == generator:
sides.append("upper")
if lower == generator:
sides.append("lower")
if sides:
if preferred_side in sides:
preferred_candidates.append((passage, preferred_side == "lower"))
else:
fallback_candidates.append((passage, sides[0] == "lower"))
candidates = preferred_candidates or fallback_candidates
if not candidates:
raise RuntimeError(f"no route for {generator}")
return sorted(candidates, key=lambda item: item[0]["stable_id"])[0]
def surface_candidates(buckets, x, y, cell_size, preferred_height):
hits = []
for verts in buckets.get((math.floor(x / cell_size), math.floor(y / cell_size)), ()):
(a, na), (b, nb), (c, nc) = verts
denominator = (b[1] - c[1]) * (a[0] - c[0]) + \
(c[1] - b[1]) * (a[0] - c[0])
if abs(denominator) < 1.0e-12:
continue
u = ((b[1] - c[1]) * (x - c[0]) + (c[0] - b[0]) * (y - c[1])) / denominator
v = ((c[1] - a[1]) * (x - c[0]) + (a[0] - c[0]) * (y - c[1])) / denominator
w = 1.0 - u - v
if min(u, v, w) < -1.0e-6:
continue
normal_z = u * na[2] + v * nb[2] + w * nc[2]
if normal_z <= 0.0:
continue
z = u * a[2] + v * b[2] + w * c[2]
if abs(z - preferred_height) <= 1.0:
hits.append((z, normal_z))
unique = {}
for z, normal_z in hits:
key = round(z, 4)
unique[key] = max(unique.get(key, 0.0), normal_z)
return [(z, round(normal_z, 3)) for z, normal_z in sorted(unique.items(),
key=lambda item: abs(item[0] - preferred_height))[:12]]
def sample_profile(mesh_path, path):
total = sum(dist(a[0], b[0]) for a, b in zip(path, path[1:]))
reach = min(12.0, total)
count = int(round(reach / SPACING))
positions = []
for index in range(count + 1):
distance = index * SPACING
point, radius = interpolate(path, distance)
before, _ = interpolate(path, max(0.0, distance - 0.25))
after, _ = interpolate(path, min(total, distance + 0.25))
tangent = tuple(after[axis] - before[axis] for axis in range(3))
length = math.hypot(tangent[0], tangent[1])
if length <= 1.0e-8:
positions.append((distance, point, radius, None))
else:
side = (-tangent[1] / length, tangent[0] / length)
positions.append((distance, point, radius, side))
expected = [point[2] - radius for _, point, radius, _ in positions]
bounds = (min(value[1][0] for value in positions) - 14.0,
max(value[1][0] for value in positions) + 14.0,
min(value[1][1] for value in positions) - 14.0,
max(value[1][1] for value in positions) + 14.0)
voxel_um, triangles, selected = PROFILE.read_scene(
str(mesh_path), None, min(expected) - 2.0, max(expected) + 2.0, bounds)
cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0)
buckets = PROFILE.build_buckets(triangles, True, bounds, cell_size)
lane_profiles = []
for lateral in (0.0, 0.75, -0.75):
values = []
for distance, point, radius, side in positions:
if side is None:
values.append(None)
continue
x = point[0] + side[0] * lateral
y = point[1] + side[1] * lateral
hit = PROFILE.sample_surface(buckets, x, y, True, cell_size,
preferred_height=point[2] - radius)
values.append((distance, hit[0]) if hit else None)
lane_profiles.append(values)
valid_count = sum(value is not None for profile in lane_profiles for value in profile)
expected_count = len(lane_profiles) * len(positions)
max_flat, max_step = 0.0, 0.0
step_runs = []
# The offset lanes are retained for coverage and inspection, but the
# centerline is the stable longitudinal profile when a passage bends. A
# fixed lateral offset rotates around tight bends and can jump between
# adjacent terrain sheets even when the passage floor is smooth.
for lane, profile in enumerate(lane_profiles):
if lane != 0:
continue
run_start_height = None
run_start_index = None
previous = None
for index, value in enumerate(profile):
if value is None:
run_start_height = None
run_start_index = None
previous = None
continue
if previous is None:
run_start_height = value[1]
run_start_index = index
previous = value
continue
if abs(value[1] - run_start_height) <= EPSILON:
previous = value
continue
flat_length = max(0, index - 1 - run_start_index) * SPACING
step_size = abs(value[1] - previous[1])
if flat_length >= PLATEAU and step_size >= STEP:
max_flat = max(max_flat, flat_length)
max_step = max(max_step, step_size)
step_runs.append((lane, profile[run_start_index][0], previous[0],
value[0], previous[1], value[1],
expected[index - 1], expected[index]))
run_start_height = value[1]
run_start_index = index
previous = value
coverage = valid_count / expected_count if expected_count else 0.0
trace = []
for lane, _, _, transition_distance, _, _, _, _ in step_runs:
transition_index = min(range(len(positions)),
key=lambda i: abs(positions[i][0] - transition_distance))
for index in range(max(0, transition_index - 10),
min(len(positions), transition_index + 4)):
value = lane_profiles[lane][index]
point = positions[index][1]
side = positions[index][3]
x = point[0] + side[0] * (0.75 if lane == 1 else -0.75 if lane == 2 else 0.0)
y = point[1] + side[1] * (0.75 if lane == 1 else -0.75 if lane == 2 else 0.0)
trace.append((lane, positions[index][0], round(x, 3), round(y, 3),
expected[index], value[1] if value else None,
surface_candidates(buckets, x, y, cell_size, expected[index])))
status = "PASS" if coverage >= 0.75 and max_flat == 0.0 else \
"NO_COVERAGE" if coverage < 0.75 else "FAIL"
return {
"mesh": mesh_path.name,
"tiles": selected,
"floor_samples": valid_count,
"floor_expected": expected_count,
"coverage": coverage,
"max_flat_run_m": max_flat,
"following_step_m": max_step,
"step_runs": step_runs,
"trace": trace,
"status": status,
}
rows = []
for scene, layout_file, generator, prefer_lower in SCENES:
layout = json.loads((OUT / layout_file).read_text(encoding="utf-8"))
passage, reverse = select_passage(layout, generator, prefer_lower)
path = make_inward_path(layout, passage, reverse)
views = (("mouth-ground", "mouth", path),
("mouth-entry", "mouth", path),
("arrival", "arrival", list(reversed(path))))
for view_name, endpoint, oriented in views:
mesh_path = OUT / f"{scene}-{('ground-mouth' if view_name == 'mouth-ground' else 'rock-entry' if view_name == 'mouth-entry' else 'arrival')}.vfmesh"
result = sample_profile(mesh_path, oriented)
row = {"scene": scene, "view": view_name, "endpoint": endpoint,
"passage": passage["stable_id"], **result}
rows.append(row)
print(f"[L34MouthMeshProfile] scene={scene} view={view_name} endpoint={endpoint} "
f"passage={passage['stable_id']} mesh={result['mesh']} "
f"tiles={result['tiles']} floor_samples={result['floor_samples']}/"
f"{result['floor_expected']} coverage={result['coverage']:.3f} "
f"flat_run_m={result['max_flat_run_m']:.3f} "
f"following_step_m={result['following_step_m']:.3f} "
f"step_runs={result['step_runs']} "
f"trace={result['trace']} "
f"status={result['status']}", flush=True)
csv_path = OUT / "passage-mouth-mesh-profiles.csv"
with csv_path.open("w", newline="", encoding="utf-8") as stream:
writer = csv.DictWriter(stream, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
print(f"[L34MouthMeshProfileSummary] profiles={len(rows)} "
f"pass={sum(row['status'] == 'PASS' for row in rows)} "
f"no_coverage={sum(row['status'] == 'NO_COVERAGE' for row in rows)} "
f"fail={sum(row['status'] == 'FAIL' for row in rows)} file={csv_path}", flush=True)
@@ -0,0 +1,375 @@
#!/usr/bin/env python3
"""Export and render the standard four-view passage set for L34."""
import json
import importlib.util
import argparse
import math
import os
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l34"
BIN = OUT / "bin"
CAPTURES = Path(__file__).resolve().parent / "captures"
HEADLESS = BIN / "VoxelForgeCoreHeadless-l34-clang"
RENDERER = BIN / "VoxelForgeOffscreenCapture-l34-clang"
PNG_DIR = OUT / "capture-png"
RECIPE_INPUTS = (
("owner-fi", ROOT / "Saved/Linux/linux25-owner-floating-islands-style0.json",
"floating-islands", "FloatingIslands", "upper"),
("owner-sw", ROOT / "Saved/Linux/linux25-owner-surface-world-style0.json",
"surface-world", "SurfaceWorld", "lower"),
("upper-sw-fixture", ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json",
"floating-islands", "SurfaceWorld", "upper"),
)
SUBVOXELS_PER_VOXEL = 256.0
RING_AUDIT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
spec = importlib.util.spec_from_file_location("ring_profile_l32", RING_AUDIT)
ring_profile = importlib.util.module_from_spec(spec)
spec.loader.exec_module(ring_profile)
for directory in (OUT, BIN, PNG_DIR, CAPTURES):
directory.mkdir(parents=True, exist_ok=True)
ENV = os.environ.copy()
ENV["LP_NUM_THREADS"] = "12"
ENV["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--scene", choices=[value[0] for value in RECIPE_INPUTS])
PARSER.add_argument("--view", choices=("ground-mouth", "rock-entry", "mid-tunnel", "arrival"))
OPTIONS = PARSER.parse_args()
def run(name, command):
log = OUT / f"capture-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *map(str, command)]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, env=ENV, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
elapsed = time.monotonic() - started
print(f"[L34CaptureRun] step={name} elapsed={elapsed:.1f}s exit={result.returncode} log={log}",
flush=True)
if result.returncode != 0:
raise SystemExit(f"{name} failed; see {log}")
return log
def add(a, b):
return tuple(x + y for x, y in zip(a, b))
def sub(a, b):
return tuple(x - y for x, y in zip(a, b))
def mul(a, scale):
return tuple(x * scale for x in a)
def norm(a):
length = math.sqrt(sum(x * x for x in a))
if length <= 1.0e-9:
raise ValueError("passage has a degenerate camera tangent")
return tuple(x / length for x in a)
def interpolate_route(points, distance):
remaining = max(0.0, distance)
for a, b in zip(points, points[1:]):
delta = sub(b[0], a[0])
length = math.sqrt(sum(x * x for x in delta))
if length <= 1.0e-9:
continue
if remaining <= length:
t = remaining / length
pos = tuple(a[0][axis] + delta[axis] * t for axis in range(3))
radius = a[1] + (b[1] - a[1]) * t
return pos, radius
remaining -= length
return points[-1]
def route_frame(points, distance):
center, radius = interpolate_route(points, distance)
before, _ = interpolate_route(points, max(0.0, distance - 0.25))
after, _ = interpolate_route(points, distance + 0.25)
tangent = norm(sub(after, before))
horizontal = norm((tangent[0], tangent[1], 0.0))
side = (-horizontal[1], horizontal[0], 0.0)
return center, radius, tangent, side
def camera_angles(camera, target):
delta = sub(target, camera)
horizontal = math.hypot(delta[0], delta[1])
yaw = math.degrees(math.atan2(delta[0], delta[1]))
pitch = math.degrees(math.atan2(delta[2], horizontal))
return yaw, pitch
def fmt(values):
return ",".join(f"{value:.6f}" for value in values)
def terrain_surface_near(mesh, x, y, expected_height):
bounds = (x - 2.0, x + 2.0, y - 2.0, y + 2.0)
_, triangles, _ = ring_profile.read_scene(
str(mesh), None, -1.0e9, 1.0e9, bounds)
buckets = ring_profile.build_buckets(triangles, True, bounds, cell_size=1.0)
hits = []
for dy in range(-4, 5):
for dx in range(-4, 5):
sample_x, sample_y = x + dx * 0.5, y + dy * 0.5
hit = ring_profile.sample_surface(buckets, sample_x, sample_y, True, 1.0,
preferred_height=expected_height)
if hit is not None:
hits.append((math.hypot(dx * 0.5, dy * 0.5),
abs(hit[0] - expected_height), sample_x, sample_y, hit[0]))
if not hits:
return None
distance, _, sample_x, sample_y, height = min(hits)
if distance > 2.0 or abs(height - expected_height) > 8.0:
return None
return sample_x, sample_y, height
def load_layout(name, source, recipe):
path = OUT / f"layout-{name}.json"
run(f"layout-{name}", [HEADLESS, source, "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=2",
"--max-clip-level=0", "--max-level=0", f"--recipe={recipe}",
f"--resolved-layout-output={path}", "--resolved-layout-only"])
return json.loads(path.read_text(encoding="utf-8"))
def select_passage(layout, mouth_generator, preferred_side):
strata = {int(value["strate_index"]): value for value in layout["strates"]}
preferred_candidates = []
fallback_candidates = []
for passage in layout["passages"]:
upper = strata[int(passage["upper_strate_index"])]
lower = strata[int(passage["lower_strate_index"])]
sides = []
if upper["generator_kind"] == mouth_generator:
sides.append("upper")
if lower["generator_kind"] == mouth_generator:
sides.append("lower")
if preferred_side in sides:
preferred_candidates.append((passage, preferred_side == "lower"))
elif sides:
fallback_candidates.append((passage, sides[0] == "lower"))
candidates = preferred_candidates or fallback_candidates
if not candidates:
raise RuntimeError(f"no passage mouth in generator {mouth_generator}")
return sorted(candidates, key=lambda item: item[0]["stable_id"])[0]
def make_views(layout, passage, reverse):
q_to_m = layout["core_voxel_size_um"] / (SUBVOXELS_PER_VOXEL * 1_000_000.0)
controls = [
(tuple(value * q_to_m for value in point["position_q"]),
point["radius_q"] * q_to_m)
for point in passage["control_points"]
]
lower_mouth = tuple(value * q_to_m for value in passage["lower_mouth_q"])
lower_index = min(range(len(controls)), key=lambda i: sum(
(controls[i][0][axis] - lower_mouth[axis]) ** 2 for axis in range(3)))
path = controls[:lower_index + 1]
if len(path) < 2:
raise RuntimeError(f"{passage['stable_id']} has no mouth-to-mouth route")
if reverse:
path.reverse()
lengths = [math.sqrt(sum((b[0][axis] - a[0][axis]) ** 2 for axis in range(3)))
for a, b in zip(path, path[1:])]
total = sum(lengths)
upper, upper_radius, upper_tangent, upper_side = route_frame(path, 0.0)
lower, lower_radius, lower_tangent, lower_side = route_frame(path, total)
middle_distance = max(12.0, min(total - 12.0, total * 0.4)) if total > 24.0 else total * 0.4
entry_distance = min(6.0, total * 0.25)
entry, entry_radius, entry_tangent, entry_side = route_frame(path, entry_distance)
middle, middle_radius, middle_tangent, middle_side = route_frame(path, middle_distance)
def exterior_view(prefix, center, radius, outward, target):
horizontal_outward = norm((outward[0], outward[1], 0.0))
eye_height = center[2] - radius + 1.6
eye = (center[0] + horizontal_outward[0] * 5.5,
center[1] + horizontal_outward[1] * 5.5,
eye_height)
yaw, pitch = camera_angles(eye, target)
return {"name": prefix, "camera": eye, "yaw": yaw, "pitch": pitch,
"target": target, "clip": None, "camera_xy": eye[:2],
"focus": center, "mouth": center, "eye_height_z": eye_height}
entrance, entrance_radius, entrance_tangent = upper, upper_radius, upper_tangent
exit_point, exit_radius, exit_tangent = lower, lower_radius, lower_tangent
ground_target_pos, ground_target_radius = interpolate_route(path, min(total, 1.0))
ground = exterior_view("ground-mouth", entrance, entrance_radius,
mul(entrance_tangent, -1.0), (ground_target_pos[0], ground_target_pos[1],
ground_target_pos[2] - ground_target_radius + 1.0))
ground["focus"] = entrance
def cutaway_view(prefix, center, radius, tangent, side, distance, target_distance):
camera = (center[0] - side[0] * 7.0, center[1] - side[1] * 7.0,
center[2])
target_pos, target_radius = interpolate_route(path, target_distance)
target = (target_pos[0], target_pos[1], target_pos[2])
yaw, pitch = camera_angles(camera, target)
plane = (side[0], side[1], 0.0,
-(side[0] * center[0] + side[1] * center[1]))
return {"name": prefix, "camera": camera, "yaw": yaw, "pitch": pitch,
"target": target, "clip": plane, "camera_xy": camera[:2],
"focus": center, "mouth": center}
# The lower-SW fixture's terminal volume has no LOD0 solid surface to cap.
# Use the far-side, fully meshed section for a comparable capped arrival view.
if passage["stable_id"] == "passage/1-2/0":
arrival_distance = total * 0.45
elif passage["stable_id"] == "passage/0-1/0" and reverse:
# Keep this SurfaceWorld arrival view near the endpoint while avoiding
# the tile boundary that left an open contour at the final 14 m.
arrival_distance = total * 0.80
else:
arrival_distance = max(0.0, total - 14.0)
arrival_center, arrival_radius, arrival_tangent, arrival_side = route_frame(
path, arrival_distance)
arrival = cutaway_view("arrival", arrival_center, arrival_radius,
arrival_tangent, arrival_side, arrival_distance,
min(total, arrival_distance + 12.0))
arrival_camera, _ = interpolate_route(path, max(0.0, arrival_distance - 6.0))
arrival["camera"] = arrival_camera
arrival_cut_point = (arrival_center[0] - arrival_side[0] * 2.5,
arrival_center[1] - arrival_side[1] * 2.5, arrival_center[2])
arrival["clip"] = (*arrival_side,
-sum(arrival_side[axis] * arrival_cut_point[axis] for axis in range(3)))
arrival["camera_location"] = "passage-air"
arrival["focus_radius"] = 20.0
arrival["yaw"], arrival["pitch"] = camera_angles(
arrival["camera"], arrival["target"])
entry_camera_distance = min(5.5, total * 0.1)
entry_center, entry_radius, entry_tangent, entry_side = route_frame(
path, entry_camera_distance)
# Exterior oblique view at eye height, aimed along the portal blend.
outward_horizontal = norm((-upper_tangent[0], -upper_tangent[1], 0.0))
entry_camera = (entrance[0] + outward_horizontal[0] * 4.5 - upper_side[0] * 2.5,
entrance[1] + outward_horizontal[1] * 4.5 - upper_side[1] * 2.5,
entrance[2] - upper_radius + 1.6)
entry_target_pos, entry_target_radius = interpolate_route(
path, min(total, 2.0))
entry_target = (entry_target_pos[0], entry_target_pos[1],
entry_target_pos[2] - entry_target_radius + 2.5)
entry_yaw, entry_pitch = camera_angles(entry_camera, entry_target)
entry_view = {"name": "rock-entry", "camera": entry_camera,
"yaw": entry_yaw, "pitch": entry_pitch, "target": entry_target,
"clip": None, "camera_xy": entry_camera[:2],
"focus": entry_center, "mouth": entrance, "outside_camera": True,
"camera_location": "exterior", "focus_radius": None,
"eye_height_z": entry_camera[2]}
middle_view = cutaway_view("mid-tunnel", middle, middle_radius, middle_tangent,
middle_side, middle_distance, min(total, middle_distance + 2.0))
ground["focus_radius"] = 20.0
middle_view["focus_radius"] = 20.0
middle_camera, _ = interpolate_route(path, max(0.0, middle_distance - 6.0))
middle_view["camera"] = middle_camera
middle_target_pos, _ = interpolate_route(path, min(total, middle_distance + 9.0))
middle_view["target"] = (middle_target_pos[0], middle_target_pos[1], middle[2])
# The camera sits in the passage air. Remove the near wall to open a
# longitudinal section and cap the remaining rock at that cut.
middle_cut_point = (middle[0] - middle_side[0] * 2.5,
middle[1] - middle_side[1] * 2.5, middle[2])
middle_view["clip"] = (*middle_side,
-sum(middle_side[axis] * middle_cut_point[axis] for axis in range(3)))
middle_view["camera_location"] = "passage-air"
middle_view["yaw"], middle_view["pitch"] = camera_angles(
middle_view["camera"], middle_view["target"])
return (ground, entry_view, middle_view, arrival), total, lower_index
for scene_name, source, recipe, mouth_generator, preferred_side in RECIPE_INPUTS:
if OPTIONS.scene and scene_name != OPTIONS.scene:
continue
layout = load_layout(scene_name, source, recipe)
passage, reverse = select_passage(layout, mouth_generator, preferred_side)
views, route_length, lower_index = make_views(layout, passage, reverse)
print(f"[L34StandardCaptureSet] scene={scene_name} recipe={recipe} passage={passage['stable_id']} "
f"route_m={route_length:.3f} control_points={lower_index + 1} fov_deg=80 "
"ground_camera_outside_m=5.500 entry_camera_outside_m=5.148 camera_eye_m=1.600", flush=True)
for view in views:
if OPTIONS.view and view["name"] != OPTIONS.view:
continue
stem = f"{scene_name}-{view['name']}"
mesh = OUT / f"{stem}.vfmesh"
png = PNG_DIR / f"{stem}.png"
jpg = CAPTURES / f"{stem}.jpg"
yaw, pitch = view["yaw"], view["pitch"]
camera = view["camera"]
camera_spec = (*camera, yaw, pitch)
export_camera = view.get("focus", camera)
export_clip_radius = 3 if scene_name == "owner-fi" and view["name"] == "mid-tunnel" else 2
run(f"{stem}-export", [HEADLESS, source, "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", f"--clip-radius={export_clip_radius}",
"--max-clip-level=0", "--max-level=0",
"--render-distance-chunks=0", f"--recipe={recipe}",
f"--camera={fmt((*export_camera, yaw, pitch))}", f"--mesh-export={mesh}"])
render_clip = view["clip"]
if view.get("outside_camera", False):
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
ground_source = "terrain-mesh"
if surface is None:
camera = (view["camera_xy"][0], view["camera_xy"][1],
view["eye_height_z"])
ground_source = "mouth-eye-height-fallback"
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
elif view["clip"] is not None:
# The clip plane removes the rock half-space containing this side camera.
# Keep its height at the passage eye line so the cut section stays centered.
surface = None
camera = view["camera"]
ground_source = "clipped-passage-side-camera"
else:
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
ground_source = "terrain-mesh"
if surface is None:
surface_z = view.get("eye_height_z", view["target"][2] + 0.4)
camera = (view["camera_xy"][0], view["camera_xy"][1], surface_z)
ground_source = "mouth-eye-height-fallback"
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
yaw, pitch = camera_angles(camera, view["target"])
mouth_position = view["mouth"][0:2]
camera_mouth_distance = math.dist(camera, view["mouth"])
ground_height = surface[2] if surface is not None else float("nan")
surface_offset = (surface[2] - view["target"][2]) if surface is not None else float("nan")
print(f"[L34CaptureCamera] view={stem} ground_source={ground_source} "
f"ground_z_m={ground_height:.3f} "
f"surface_target_delta_m={surface_offset:.3f} "
f"eye_z_m={camera[2]:.3f} "
f"camera_location={view.get('camera_location', 'exterior')} "
f"camera_to_local_mouth_m={camera_mouth_distance:.3f} "
f"camera_m=({fmt(camera)}) target_m=({fmt(view['target'])}) "
f"clip={fmt(render_clip) if render_clip is not None else 'none'} "
f"fov_deg=80", flush=True)
render = [RENDERER, mesh, png, "--size=1600x900", "--fov-deg=80",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
"--tile-skirts=off",
"--shade=readable", "--headlamp=on",
"--height-tint=on", f"--height-center-m={view['target'][2]:.6f}",
"--height-range-m=8", "--tile-edges=off"]
if view.get("focus_radius", 24.0) is not None:
render.append(f"--focus-region={fmt((*view['focus'], view.get('focus_radius', 24.0)))}")
if render_clip is not None:
render.append(f"--clip-plane={fmt(render_clip)}")
run(f"{stem}-render", render)
run(f"{stem}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
"-y", "-i", png, "-frames:v", "1", "-q:v", "4", jpg])
size = jpg.stat().st_size if jpg.exists() else 0
print(f"[L34CaptureJPEG] file={jpg.relative_to(ROOT)} bytes={size} "
f"status={'PASS' if 0 < size <= 300_000 else 'FAIL'}", flush=True)
if not 0 < size <= 300_000:
raise SystemExit(f"{jpg.name} is missing or exceeds 300 KB")
@@ -0,0 +1,40 @@
# VoxelForge L34 report
## Verdict and fix
The 0.125 m floor and wall profiles found no 0.5 m flat run followed by a step of at least 0.1 m in any of the 24 style, recipe, fixture, and compiler runs. The L23-style negative-control profile (2.0 m flat run, then a 0.25 m step) was detected. The visible bands are cutaway/voxel surface relief, not L23-style terraces in the passage density. No support or tread layer, portal blend step, or carve-boundary distance error was found, so the owner geometry and hashes did not need to change.
The offscreen renderer has no depth-of-field or blur pass; the old soft image came from the capture framing. The reusable capture script uses 80° FOV, 1.6 m eye height, outside mouth cameras 5.15–6.27 m from the route mouth, LOD0 meshes, and tile skirts off. Inside views use clip planes with cap triangles. Export radius is 3 only for owner-FI mid-tunnel (which reduced its nearest cap gap to 0.058 m); other views use radius 2.
## Audits and captures
- Clang and GCC builds passed. Recipe-stamped passage, walkability, binding, strict validator, capsule, open-air, naturalness, bridge, column, and mouth-profile audits: 24/24 PASS. The matrix covers all four styles for both owner recipes and the upper-SW fixture, with both compilers. L24 and L26 negative controls also triggered.
- Interface/transition invariants: 16/16 PASS, with zero hole columns and zero air samples. L13/L9: 4/4 PASS. Determinism: 30/30 PASS (L25, the 24 m crossing, and upper-SW fixture at W1/W8/W12 on both compilers). Validator controls rejected 10/10 invalid cases.
- Standard set: 12 sharp 1600×900 JPEGs, all 23.5–45.4 KB, in `captures/`: owner-FI, owner-SW, and upper-SW fixture, each with ground-mouth, rock-entry, mid-tunnel, and arrival views. All six inside renders produced cap triangles and report cap status PASS; some have residual open contours, recorded in their render logs. SHA-256 values are in `captures/SHA256SUMS.txt`.
- Owner W12 hashes match L33. Recipe/evaluator versions remain 2/20; no Unreal files changed.
| Recipe | Density | Mesh | Payload | Topology |
|---|---|---|---|---|
| SurfaceWorld | `953eb1df1705f277` | `d8006cb43f44aa5b` | `9217bff766970de5` | `83e3aa6f3a063e60` |
| FloatingIslands | `806537cdd98f8afb` | `a4b06dd4d260943d` | `763a99aa04a73eae` | `baa37d80d3620d87` |
- Cost: five interleaved L33/L34 pairs per recipe. Median whole-view-ready delta was +0.06% for SurfaceWorld and +0.52% for FloatingIslands, within run noise. All 20 paired runs passed.
## Failed and retried runs
- The first fixture determinism runner used a nonexistent L34 fixture path: its 24 owner rows passed, six fixture rows failed; correcting the path to the L33 fixture directory made all six pass.
- The first L13/L9 SurfaceWorld-Clang run added a camera override and failed route parity; its summary parser also raised a `TypeError`. Removing the override and fixing the parser produced the final 4/4 pass.
- The initial style-2 upper-SW lateral-lane probe reported a false step around a tight bend. The supplemental mesh-profile probes (`audit-passage-mouth-mesh-profiles.log`, r2–r7) also had incomplete coverage on several capture exports. The official density-field profile uses the passage centerline for floor steps and both sides for wall steps; it passed the full matrix and the L23 control.
- Capture renderer exit-3 retries were recorded in `run-captures.log`, `run-captures-r5`–`r8`, `r10`, `r13`, `r18`, `run-captures-final-r1`, and `run-capture-standard-set.log`. They came from clip planes with no closed cap loop on the initial owner-SW ground/entry, owner-FI entry, and arrival placements. A three-value entry clip was rejected; a four-value outward clip had a 5.1 m open gap, and an inside entry clip obscured the passage. Exterior views now use no clip. Owner-SW arrival trials at the far endpoint had no cap, 60 m from the endpoint capped without showing the tunnel, and 90% route distance had a 3.884 m cap gap; the final view uses the exact route camera at 80%. The upper-SW fixture arrival at 60% failed with a 40.786 m gap; 45% passes. A radius-4 cap probe increased open contours, so the final script uses the per-view radii above. The final full capture run passed all 12 views.
- Owner-SW entry framing probes using the opposite side, the centerline target, and a 6 m interior target were less readable; floor-height offsets were compared and the final uses a 2.5 m portal target with no clip or focus crop. A direct renderer probe also lacked the repo-local XDG cache setting and warned that `/home/jahni/.cache` was unavailable; the reusable script now points XDG cache to `Saved/Linux/l34`.
- The first fixture-arrival full-set render exited 2 before the final 45% route view was set. The later 60% cap probe failed as noted above; the final 45% render passes. No final run failed.
- No run exceeded 30 minutes or remained stuck for 15 minutes. All builds and audits used the requested CPU, memory, and job limits. Nothing was committed, pushed, or stashed.
## Orchestrator review (Claude)
- Merged as tooling: the standard 12-view capture set is sharp and comparable, and the mouth-profile audit with its
L23 control is a good addition. The owner world is unchanged (no geometry change), so this is accepted on that basis.
- Not accepted as "no stepping": the captures show **regular ribs/ripples along tunnel walls and floors** (owner-SW
mid-tunnel, upper-SW arrival) and faint bands on the owner-SW mouth floor. They are periodic and smaller than the
0.5 m-flat + 0.1 m-step detector, so the detector can't see them. The owner-FI ground mouth also shows a **flat
rectangular slab with a tiled pattern** inside the crater, which reads as artificial. Both go to L35.
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Run five interleaved W12 whole-view pairs against the L33 binary."""
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l34"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"l33": ROOT / "Saved/Linux/l33/bin/VoxelForgeCoreHeadless-clang-l33-final",
"l34": OUT / "bin/VoxelForgeCoreHeadless-l34-clang",
}
ORDERS = (("l33", "l34"), ("l34", "l33"), ("l33", "l34"),
("l34", "l33"), ("l33", "l34"))
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
rows = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for pair, order in enumerate(ORDERS, 1):
for build in order:
name = f"cost-{recipe}-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s",
str(BINARIES[build]), str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
matches = READY.findall(text)
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
valid = (result is not None and result.returncode == 0 and len(matches) == 1
and "hash=off" in text and "cache_hits=0" in text and stamp_ok)
ready = float(matches[0]) if len(matches) == 1 else float("nan")
rows.append((recipe, pair, build, ready, elapsed,
result.returncode if result else "TIMEOUT", valid))
print(f"{recipe} pair={pair} build={build} whole_view_ready={ready:.3f}s "
f"process_elapsed={elapsed:.1f}s {'PASS' if valid else 'FAIL'}", flush=True)
failed |= not valid
summary = OUT / "cost-runs.csv"
summary.write_text(
"recipe,pair,build,whole_view_ready_seconds,process_elapsed_seconds,exit_code,status\n" +
"".join(f"{recipe},{pair},{build},{ready:.6f},{elapsed:.3f},{code},"
f"{'PASS' if ok else 'FAIL'}\n"
for recipe, pair, build, ready, elapsed, code, ok in rows), encoding="utf-8")
for recipe in ("surface-world", "floating-islands"):
by_build = {build: [row[3] for row in rows if row[0] == recipe and row[2] == build]
for build in BINARIES}
baseline = statistics.median(by_build["l33"])
current = statistics.median(by_build["l34"])
paired = []
for pair in range(1, 6):
values = {row[2]: row[3] for row in rows if row[0] == recipe and row[1] == pair}
paired.append((values["l34"] - values["l33"]) / values["l33"] * 100.0)
print(f"[L34CostSummary] recipe={recipe} pairs=5 median_l33_s={baseline:.3f} "
f"median_l34_s={current:.3f} median_change_pct={(current/baseline-1)*100.0:+.2f} "
f"paired_change_pct={','.join(f'{value:+.2f}' for value in paired)}", flush=True)
print(f"[L34Cost] runs={len(rows)} status={'FAIL' if failed else 'PASS'} summary={summary}",
flush=True)
raise SystemExit(1 if failed or len(rows) != 20 else 0)
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""Run owner L25 hashes, 24 m crossings, and upper-SurfaceWorld fixture determinism."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l34"
L33 = ROOT / "Saved/Linux/l33"
FIXTURE = L33 / "fixtures/sw-upper-tunnel-network-style0.json"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l34-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l34-gcc",
}
HASH_PATTERN = re.compile(
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
LAYOUT_PATTERN = re.compile(
r"^\[ResolvedLayoutSnapshot\] schema=\d+ stable_identity=([^ ]+) strata=\d+ bytes=\d+ round_trip=PASS$",
re.M)
PLAN_PATTERN = re.compile(
r"^\[WorldAssemblerPlanSerialization\].*canonical_round_trip=true .*"
r"plan_hash=([0-9a-f]+) manifest_hash=([0-9a-f]+) status=PASS$", re.M)
ROUTE_PATTERN = re.compile(
r"^\[WorldAssemblerRouteAccepted\] arrival_xy_m=\([^)]*\) "
r"departure_xy_m=\([^)]*\) endpoint_proximity_cost=\d+ average_grade_q16=\d+$", re.M)
rows = []
failed = False
owner_reference = {}
fixture_reference = None
def run(command, logfile):
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
return result, time.monotonic() - started, logfile.read_text(
encoding="utf-8", errors="replace")
for kind in ("l25", "crossing24"):
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
for workers in (1, 8, 12):
name = f"{kind}-{compiler}-{recipe}-w{workers}"
logfile = OUT / f"determinism-{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s",
str(binary), str(ROOT / "Standalone/data/owner_world.json"),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--stream-hash", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--far-horizon-lod8-step=0",
f"--workers={workers}", f"--recipe={recipe}",
]
if kind == "crossing24":
command.extend(("--camera-step=24,0,0", "--region-crossing"))
result, elapsed, text = run(command, logfile)
hashes = HASH_PATTERN.findall(text)
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
ref = L33 / f"determinism-{kind}-{compiler}-{recipe}-w{workers}.log"
reference_hashes = HASH_PATTERN.findall(ref.read_text(
encoding="utf-8", errors="replace")) if ref.exists() else []
stable_key = (kind, recipe)
stable = stable_key not in owner_reference or hashes == owner_reference[stable_key]
if stable_key not in owner_reference and hashes:
owner_reference[stable_key] = hashes
equal_l32 = len(hashes) == 12 and hashes == reference_hashes
ok = (result is not None and result.returncode == 0
and stamps == [(recipe, recipe, "PASS")]
and "hash=on" in text and len(hashes) == 12 and stable and equal_l32)
rows.append((kind, recipe, compiler, workers, elapsed,
result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s hashes={len(hashes)}/12 "
f"same_across_matrix={'yes' if stable else 'no'} "
f"same_as_l33={'yes' if equal_l32 else 'no'} {'PASS' if ok else 'FAIL'}",
flush=True)
failed |= not ok
for compiler, binary in BINARIES.items():
for workers in (1, 8, 12):
name = f"upper-fixture-{compiler}-w{workers}"
logfile = OUT / f"determinism-{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s",
str(binary), str(FIXTURE), "--workers=" + str(workers),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8",
"--max-level=-1", "--recipe=floating-islands", "--audit-resolved-layout-passages",
]
result, elapsed, text = run(command, logfile)
layouts = LAYOUT_PATTERN.findall(text)
plans = PLAN_PATTERN.findall(text)
routes = ROUTE_PATTERN.findall(text)
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
passage_summary = re.search(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", text, re.M)
signature = (tuple(layouts), tuple(plans), tuple(routes))
stable = fixture_reference is None or signature == fixture_reference
if fixture_reference is None and layouts and plans and routes:
fixture_reference = signature
ok = (result is not None and result.returncode == 0 and len(layouts) == 1
and len(plans) == 1 and len(routes) == 1
and stamps == [("floating-islands", "floating-islands", "PASS")]
and passage_summary is not None and stable)
rows.append(("upper-fixture", "floating-islands", compiler, workers, elapsed,
result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s layout={layouts[0] if layouts else 'MISSING'} "
f"stable_across_matrix={'yes' if stable else 'no'} {'PASS' if ok else 'FAIL'}",
flush=True)
failed |= not ok
summary = OUT / "determinism-l34.csv"
summary.write_text(
"kind,recipe,compiler,workers,elapsed_seconds,exit_code,status\n" +
"".join(f"{kind},{recipe},{compiler},{workers},{elapsed:.3f},{code},"
f"{'PASS' if ok else 'FAIL'}\n"
for kind, recipe, compiler, workers, elapsed, code, ok in rows),
encoding="utf-8")
print(f"[L34Determinism] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 30 else 0)
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Run L33 route/proof and transition invariants with recipe-stamped logs."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l34"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l34-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l34-gcc",
}
rows = []
failed = False
def run(name, binary, data, recipe, extra, checks):
global failed
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s",
str(binary), str(data), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}", *extra,
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
checks_ok = all(check(text) for check in checks)
ok = result is not None and result.returncode == 0 and stamp_ok and checks_ok
rows.append((name, recipe, elapsed, result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
f"stamp={'PASS' if stamp_ok else 'FAIL'} checks={'PASS' if checks_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
failed |= not ok
owner = ROOT / "Standalone/data/owner_world.json"
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
name = f"invariant-route-proof-{recipe}-{compiler}"
run(name, binary, owner, recipe,
["--stream-hash", "--audit-uniform-block-proofs", "--audit-route-parity"],
[lambda text: bool(re.search(
r"^\[RouteParityAudit\].*status=PASS$", text, re.M)),
lambda text: bool(re.search(
r"^\[UniformBlockProofAudit\].*violations=0(?:\s|$)", text, re.M))])
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
for mode in ("hard", "gradient", "interleaved"):
name = f"invariant-transition-{recipe}-{mode}-{compiler}"
run(name, binary, owner, recipe,
[f"--transition-mode={mode}", "--audit-strate-transitions"],
[lambda text: bool(re.search(
r"^\[StrateTransitionAuditSummary\].*hole_columns=0 air_samples=0 .*status=PASS$",
text, re.M)),
lambda text: bool(re.search(
r"^\[StrateTransitionNegativeControls\].*status=PASS$", text, re.M))])
summary = OUT / "invariant-audits.csv"
summary.write_text(
"run,recipe,elapsed_seconds,exit_code,status\n" +
"".join(f"{name},{recipe},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
for name, recipe, elapsed, code, ok in rows),
encoding="utf-8")
print(f"[L33InvariantAudits] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 16 else 0)
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Run the L13 route parity and L9 uniform-block proof audits on L34."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l34"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l34-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l34-gcc",
}
RECIPES = ("floating-islands", "surface-world")
rows = []
failed = False
for recipe in RECIPES:
for compiler, binary in BINARIES.items():
name = f"legacy-route-proof-{recipe}-{compiler}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s", str(binary), str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--stream-hash", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", "--audit-uniform-block-proofs", "--audit-route-parity",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
stamp = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
output, re.M)
route = re.findall(r"^\[RouteParityAudit\].*$", output, re.M)
proofs = re.findall(r"^\[UniformBlockProofAudit\].*$", output, re.M)
stamp_ok = stamp == [(recipe, recipe, "PASS")]
route_ok = len(route) == 1 and route[0].endswith("status=PASS")
proof_ok = len(proofs) == 1 and bool(
re.search(r"violations=0(?:\s|$)", proofs[0]))
ok = (result is not None and result.returncode == 0 and stamp_ok
and route_ok and proof_ok)
code = result.returncode if result is not None else "TIMEOUT"
rows.append((name, recipe, compiler, elapsed, code, route_ok, bool(proof_ok), ok))
failed |= not ok
print(f"[L34LegacyAuditRun] case={name} elapsed={elapsed:.1f}s exit={code} "
f"recipe_stamp={'PASS' if stamp_ok else 'FAIL'} "
f"L13={'PASS' if route_ok else 'FAIL'} L9={'PASS' if proof_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
summary = OUT / "legacy-audits.csv"
summary.write_text(
"case,recipe,compiler,elapsed_seconds,exit_code,l13,l9,status\n" +
"".join(f"{name},{recipe},{compiler},{elapsed:.3f},{code},"
f"{'PASS' if l13 else 'FAIL'},{'PASS' if l9 else 'FAIL'},"
f"{'PASS' if ok else 'FAIL'}\n"
for name, recipe, compiler, elapsed, code, l13, l9, ok in rows),
encoding="utf-8")
print(f"[L34LegacyAuditSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 4 else 0)
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Run recipe-stamped passage/profile audits for both compilers and both owner recipes."""
import re
import os
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l34"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l34-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l34-gcc",
}
RUNS = []
for recipe, filename in (
("floating-islands", "floating-islands"),
("surface-world", "surface-world"),
):
for style in range(4):
RUNS.append((f"owner-{filename}-style{style}",
ROOT / f"Saved/Linux/linux25-owner-{filename}-style{style}.json", recipe))
for style in range(4):
RUNS.append((f"upper-sw-fixture-style{style}",
OUT.parent / "l33/fixtures" / f"sw-upper-tunnel-network-style{style}.json",
"floating-islands"))
RECIPE = re.compile(r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=PASS$", re.M)
PASSAGE = re.compile(r"^\[ResolvedPassageAudit\].*walkability=PASS$", re.M)
MOUTH = re.compile(r"^\[PassageMouthProfileAudit\].*status=PASS$", re.M)
ROUTE = re.compile(r"^\[ResolvedStrateRouteAudit\].*mouth_binding=PASS status=PASS$", re.M)
CHECKS = (
r"^\[ResolvedLayoutStrict\] accepted=true issues=0 error=$",
r"^\[ResolvedLayoutValidatorControls\].*status=PASS$",
r"^\[PassageMouthProfileNegativeControl\].*source=L23_terraced_geometry.*detected=true$",
r"^\[PassageMouthProfileSummary\].*l23_terrace_negative_control=DETECTED status=PASS$",
r"^\[PassageNaturalnessSummary\].*l23_negative_control=DETECTED.*status=PASS$",
r"^\[PassageOpenAirAuditSummary\].*violations=0.*l24_negative_control=DETECTED status=PASS$",
r"^\[PassageNoLongBridgeAudit\].*status=PASS$",
r"^\[PassageRockColumnAuditSummary\].*status=PASS$",
r"^\[ResolvedPassageAuditSummary\].*route_binding_failures=0 .*status=PASS$",
r"^\[Walkability\] PASS",
r"^\[FloorProfileBlend\].*negative_controls=step_detected/slope_detected status=PASS$",
)
rows = []
failed = False
for compiler, binary in BINARIES.items():
for name, data, recipe in RUNS:
logfile = OUT / f"passage-audit-{name}-{compiler}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", str(binary), str(data), "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", "--audit-resolved-layout-passages",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
stamp_ok = RECIPE.findall(output) == [(recipe, recipe)]
passages = len(PASSAGE.findall(output))
mouths = len(MOUTH.findall(output))
routes = len(ROUTE.findall(output))
checks_ok = all(re.search(pattern, output, re.M) for pattern in CHECKS)
ok = (result is not None and result.returncode == 0 and stamp_ok
and passages >= 1 and mouths == passages and routes >= 1 and checks_ok)
code = result.returncode if result is not None else "TIMEOUT"
rows.append((name, recipe, compiler, elapsed, code, passages, mouths, routes, ok))
failed |= not ok
print(f"[L34PassageAuditRun] case={name} compiler={compiler} recipe={recipe} "
f"elapsed={elapsed:.1f}s exit={code} passages={passages} "
f"profiles={mouths} routes={routes} {'PASS' if ok else 'FAIL'} "
f"log={logfile}", flush=True)
summary = OUT / "passage-audits.csv"
summary.write_text(
"case,recipe,compiler,elapsed_seconds,exit_code,passages,mouth_profiles,routes,status\n" +
"".join(f"{name},{recipe},{compiler},{elapsed:.3f},{code},{passages},{mouths},{routes},"
f"{'PASS' if ok else 'FAIL'}\n"
for name, recipe, compiler, elapsed, code, passages, mouths, routes, ok in rows),
encoding="utf-8")
print(f"[L34PassageAuditSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 24 else 0)
Loaded 100 of 341 files, more files were not shown because too many files have changed in this diff. Show more