Author SHA1 Message Date
Fr0zkaandClaude Opus 5.5 34ae1d7187 wip(L17, side branch): FloatingIslands seal-cap fix + owner/Core chunk-scale change (breaks SurfaceWorld)
Not for assembler/world: the 8 m -> 4 m strate chunk change moves every stratum, so the SurfaceWorld
spawn camera floats in a void and the L1 mesh export shrank 58.7 -> 31.7 MB. Kept for its
diagnostics, the boundarySeal finding and the readable capture shading. See Linux17/REPORT.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-20 05:52:59 +02:00
444 changed files with 1302 additions and 951585 deletions

No files matched your search

+26 -460
View File
@@ -73,7 +73,7 @@ is the only density path; a degenerate (zero-height) strate has none and is open
| Maze | `FMazeGenerationParams` | `BuildMazeStack` | origin-directed spanning-tree corridors on a 3D lattice, with capped loop edges and a thread-local per-cell capsule cache |
| SurfaceWorld | `FSurfaceGenerationParams` | `BuildSurfaceStack` | heightfield terrain: domain-warped continents+ridged mtns+detail, a low-freq **relief map** (`M`) that scales mountains/elevation for plains↔highland variety, **F20 heightfield terrain ops** (`Surface|Ops` — all default off ⇒ byte-identical): **Cliff** (slope-gated steepening — where the analytic structural slope > `CliffSlopeThreshold`, push the height away from the local mean by `CliffSharpness` ⇒ gentle slopes become sheer walls/canyon faces that hug real steep ground, gentle areas untouched; 4 structural resamples only when enabled), **Terrace** (relief-gated plateau quantize + `TerraceHardness` soft-round↔crisp-mesa), **LayerLines** (sedimentary sine shelves, slope-expressed) — pure per-column height remaps in the single oracle `ComputeSurfaceTerrainZ` (`SampleSurfaceStructuralZ` = pre-op raw height, re-sampled at an XY offset for Cliff's slope), biome-selected + border-blended for free via each biome's `SurfaceParams` + the height output-lerp; plus **phase-2 Overhang** (the first VOLUMETRIC op — real jutting shelves like a cliff lip): in `FOverhangShelfMod`, for AIR voxels in the window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height and unioned in. Low in the window the shift is ~0 (borrows nearby low rock ⇒ stays air over the void); high up it reaches the far cliff (solid) ⇒ a shelf attached to the cliff, tapering out over the void with air underneath. Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill dir `(DirX,DirY)` are resolved once in `ComputeSurfaceColumn` — the gradient sampled at the REACH scale (`OverhangReach`) so a point out over the void can "see" the cliff to know which way is uphill — and cached on `FSurfaceColumn`. It's genuine 3D (per-voxel structural re-eval), so it's gated hard to steep overhang columns; the union only ADDS rock (never removes), capped at `TerrainZ+OverhangHeight`, so `ClassifyTile` forces Mixed only in `(TerrainZ, TerrainZ+OverhangMargin]` (upward-only; margin = max `OverhangHeight`) — a shelf never holes a trivially-skipped tile, and only the thin cliff-edge band of air tiles is woken (NOT far-field like phase-3 spikes/holes). Overhang undersides classify as ground rock by the F17 rule (down-facing but below `TerrainZ`). Known v1 limit: applies at all LODs (Step-agnostic) — may alias far; gate to fine tiles later. Beaches at water line, high sky-cap ceiling. The cap is shapeable terrain in its own right (`ComputeSurfaceCeiling`, `Surface|Sky` params: `CeilingUndulation` broad inverted hills/valleys, `CeilingRidgeStrength` hanging ridgelines, `CeilingRoughness`+freq fine bumps, `CeilingWarp*`) — defaults (strengths 0, freq 0.04) = old flat-ish cap. (`Surface|Macro` params = the cheap precursor to biomes; `ReliefStrength=0` ⇒ old uniform terrain.) **Sky-cap vs ground is a PER-TRIANGLE surface class (F17), not a per-tile verdict**: the mesher classifies each unique vertex on the **worker** — only down-facing verts (`N.Z<-0.1`) pay a memoized `GetSurfaceHeightAt` column query; nearer `CeilSurf` ⇒ sky-cap, nearer `TerrainZ` ⇒ terrain overhang stays ground (a future SurfaceWorld cave roof — down-facing but below `TerrainZ` — also lands ground by the same rule; non-surface strates always ground). Triangles take the majority class of their 3 verts and are packed as **two contiguous index runs** (ground then cap, `FVoxelMeshData::NumCeilingTriangles`; skirts inherit their source triangle's run) → RMC **polygroups 0/1** → one **section per non-empty group** (`ApplyMeshToTile`, material slot = group index): slot 0 = strate `OverrideMaterial`/default (min-corner chunk), slot 1 = `CeilingMaterial` resolved at the tile's **TOP chunk** (mid as gap fallback, then min; fallback: ground material) — a coarse tile is 2^level chunks tall, so its min corner can sit in a lower strate/gap while the cap belongs to the strate above (this was the "far cap = ground material" residue), so the shadowless overhead rock is tinted separately instead of reading flat/bright. Shadow is **per section** via `FRealtimeMeshSectionConfig::bCastsShadow` (NOT the component `SetCastShadow` — RMC's proxy ignores the component flag; this is also why level≥2 far tiles only stopped casting once the section flag was wired): ground casts at level≤1, the cap section never casts, so the rock ceiling never shadows the terrain below it. History: v1 was a game-thread centre height-oracle (misclassified coarse far tiles → terrain material on the cap underside); v2 a whole-tile worker normal VOTE — which painted **mixed coarse tiles** (one far tile spanning terrain AND cap) entirely with the winner's material, and put sky material under terrain overhangs. The per-triangle class fixes both and is the identity channel caves/F8 will reuse. |
| VerticalShafts | `FVerticalShaftParams` | `BuildVerticalShaftStack` | full-height shafts + horizontal connectors + partial ledges |
| FloatingIslands | `FFloatingIslandParams` | `BuildFloatingIslandStack` | asymmetric islands: flat land top + underside tapering to a point, lobed (domain-warped) outline, beneath the shared ceiling at its configured strate upper boundary |
| FloatingIslands | `FFloatingIslandParams` | `BuildFloatingIslandStack` | asymmetric islands: flat land top + underside tapering to a point, lobed (domain-warped) outline, in an open void |
| Underwater | `FStrateGenerationParams` + water | (reuses `BuildTunnelNetworkStack`) | tunnel rock + high water table |
All stacks share the convention: internal **positive=solid**, and `AppendStructuralPost` ends every
@@ -1431,9 +1431,8 @@ density roots, including a 100 cm datum-offset negative control. See
evaluators keep their recorded cap behavior. The clip joins the exact near density root at the owning
boundary without changing LOD0–3 density hashes. Future families with planar roofs must declare their
cap height and bounds through the family plan/provider contract; the shared tile generator consumes
that declaration. FloatingIslands uses the same resolved strate-boundary data flow: its upper seal
follows its plan's chunk range and the shared boundary-seal primitive, while SurfaceWorld keeps its
existing heightfield cap.
that declaration. FloatingIslands declares no planar roof, so open sky above a surface island is the
expected view.
For each requested far SurfaceWorld XY column at LOD4–8, desired-tile selection adds cap-height
bands only when they do not overlap an existing or already accepted 3D clipmap tile. The original
@@ -1472,9 +1471,8 @@ neighborhood have installed collision (or are explicitly collision-not-required)
now** — the active view has ground, horizon and the family-appropriate ceiling, with no exposed
coverage hole or unexpected sky; (3) **refined soon** — each target LOD resolves within its stated
time or travel distance after entering range. While moving, report the largest frame/time gap in
enclosure and maximum distance that unresolved work trails the pawn. FloatingIslands is enclosed by a
planar roof at the upper boundary of its configured strate range; a taller strate places that roof
farther above its islands.
enclosure and maximum distance that unresolved work trails the pawn. FloatingIslands has open sky by
design, so its enclosure check excludes a planar roof.
The shared `AVoxelWorld::UpdateChunksAroundPosition` scheduler serves both the Core snapshot path and
Core-off legacy path. It admits the collision-critical 3×3 ring first. The measured S5 order remains
@@ -1842,459 +1840,27 @@ fill and check page tiles against the evaluator they actually sample. This is th
the Unreal adapter and its asynchronous MPSC/Epoch flow are unchanged. See
[`Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md).
### 8.37 Shared strate boundaries and FloatingIslands lower seal — World Assembler L18
`FStandaloneShapeWorld::GetResolvedStratumBounds` supplies one physical lower/upper-bound descriptor
to layer ranges, far-field sample protection, and far-shell continuity for every strate. SurfaceWorld
retains its exact `topChunkZ * 32` Core-voxel mapping and native heightfield cap. Native FloatingIslands
takes its resolved bounds from its plan, whose chunk range is converted through the serialized
owner/Core voxel ratio. Its version-3 field removes only the lower all-XY seal and calls the same
`VF_ApplyUpperBoundarySeal` primitive used by the shared two-sided `VF_ApplyBoundarySeal` for its upper
boundary. The ceiling therefore sits at that strate's own upper boundary, with the common
`boundarySealSubvoxels` thickness; there is no FloatingIslands-specific height. The recipe range is
un-inset to match the Unreal adapter. Versions 1 and 2 retain their serialized two-sided seals.
SurfaceWorld's field and output hashes remain unchanged. See
[`Standalone/Research/WorldAssemblerM3/Linux18/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux18/REPORT.md).
### 8.38 Closed shared boundary ceilings and LOD sampling audit — World Assembler L19
`AppendStratumBoundaryCeilings` fills a candidate cell when the authoritative field is solid just
inside that strate's configured upper boundary. It does not require the raw field to cross back to
air above the boundary: the ceiling itself closes the strate even when the field continues solid
through that plane. This avoids isolated holes where a coarse lattice node has no zero crossing.
`FFarMeshColumnIndex::HasDownwardCellCoverage` still suppresses a fallback quad only after its
interior and perimeter probes all hit the existing downward face. The L19 `farHorizonVersion` is 4
for this shared boundary-cap change; hashes remain direct at LOD0–3 and salted at LOD4–8. No density
query, stratum position, scale, or asynchronous route changes. The strict LOD0–8 roof and paired-edge
audit and the matched before/after captures are in
[`Standalone/Research/WorldAssemblerM3/Linux19/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux19/REPORT.md).
### 8.39 Resolved layout snapshots and nominal strate bands — World Assembler L21 (historical)
`ResolvedLayoutSnapshotV1` is a separate Core-owned record built from the resolved manager slots and
the native SurfaceWorld/FloatingIslands plans. It has a canonical JSON representation, schema version,
and stable FNV-1a identity. Its physical intervals use half-open micrometre coordinates; recipe anchors
retain their serialized fixed-point units. The Unreal adapter exports it beside `world_assembly`, so the
layout digest never salts a recipe, manifest, density, or mesh hash. The standalone exporter follows
the same builder after plan resolution. It runs once per plan request and is not read from density or
tile-worker loops.
Each record separates the nominal ceiling, content, and bedrock bands. A strict layout requires that
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
inside their explicit transition zone; Hard has zero effective width. The manager's `TransitionBlendChunks`
is expressed in owner-chunk metres. L21 recorded the older Interleaved allowance as blend width plus
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;
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,
with protection represented explicitly.
Compatibility facts from L21: native SurfaceWorld plan/query bounds use 32 Core voxels (8 m) per
dispatch chunk, while FloatingIslands converts the 32-voxel owner chunk through its 12.5/25 cm scale
(16 Core voxels, 4 m). In the prototype where the top slot is FloatingIslands, this leaves a nominal
60 m interval between the FI bottom (−60 m) and SurfaceWorld top (−120 m); the Core selectors fall
through to the compiled analytic FloatingIslands fallback there. Core does not apply the manager's
parameter blend at either recipe boundary. The FI v3 upper seal remains a ceiling inside its current
content range, and its missing lower bedrock is recorded without changing evaluation. See
[`Standalone/Research/WorldAssemblerM3/Linux21/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux21/REPORT.md).
### 8.40 Fixed-metre strate layout units and shared boundary volumes — World Assembler L22
`StrateLayoutChunkSizeMeters` is one world-level setting, defaulting to **8 m**, independent of the
legacy 32-voxel chunk and Core's 32³ storage tile. The director resolves the setting once to physical
micrometres and Core subvoxels; every generator plan and dispatch reads that snapshot. At a 25 cm Core
voxel, an 8 m layout chunk is 32 Core voxels (8192 1/256-voxel units); at 20 cm it is 40 Core voxels
(10240 units). The Core tile remains 32³ voxels and is not a layout chunk.
The owner's FI/SW intervals are contiguous: FloatingIslands occupies [−120,+8) m and SurfaceWorld
occupies [−632,−120) m. Each strate is ceiling → content → bedrock. Both solid, diggable boundary
bands use its generator's existing `BoundarySealThickness` and `BaseDensity`; `InterStrateGapChunks`
controls only spacing, and `EdgeSealThickness` remains horizontal. No new thickness parameter or hidden
band height was added. The shared composition layer emits both volumes for every generator and the
shared mesh path closes their visible ceiling and floor faces.
The snapshot is schema V2 and is consumed by bounds planning, native and legacy dispatch, solid-band
composition, and validation. Strict validation accepts the real owner layouts, and the boundary probe
audit agrees at all 27 sampled endpoints in both recipe modes. The owner authored Gradient transition
is recorded as an explicit Hard fallback with zero interpenetration; Gradient and Interleaved mixing
remain unimplemented and must not be inferred from the declared blend width. Seven validator negative
controls continue to reject malformed bands.
Evidence and owner caveats are in
[`Standalone/Research/WorldAssemblerM3/Linux22/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux22/REPORT.md).
### 8.41 Director-resolved inter-strate passages and walkable landings — World Assembler L23
Core passage geometry is derived after the director has resolved physical strate bands and native
route anchors. `PlanResolvedLayoutPassagesV1` joins every adjacent resolved pair. Its placement hash
uses `(seed, upper strate, lower strate, connection, salt)`; `PassageConfig.DistanceMax` is the
outer radius around world `(0,0)` (24 m for the owner's first interface), while `DistanceMin` is
the inner radius used when a mouth has no route anchor. A route-bound mouth reuses the upper
strate's departure or lower strate's arrival anchor, after checking it lies within `DistanceMax`.
The legacy `UVoxelStrateManager::GeneratePassages` and its owner-voxel control points remain the
legacy generator path; Core does not import the exported legacy `passages` array. The season
manifest carries recipes and plans, and the adapter rebuilds the resolved layout and its passages
from the current manager slots and route anchors each time it attaches the Core world.
`ApplyResolvedStrateComposition` composes the current generator field, applies the resolved ceiling
and bedrock bands, then unions the passage carve and floor support. Only the hashed capsule path
opens a band; the support strip stays below the player capsule. The lower mouth index splits the
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
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
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,
and async rules remain unchanged.
[`Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md)
records the audit and captures.
### 8.42 Per-strate natural passages and the FloatingIslands anchor helix — World Assembler L24
`FStratePassageConfig.Style` now drives the shared Core planner for every generator. The default is
`Worm`; Straight, Spiral and Cascading remain selectable per upper strate. Passage radius, reach,
distance and style stay in the resolved-layout input, so equal seeds and route anchors produce equal
paths on every peer. The passage carver adds bounded, deterministic wall relief (`0.24 m` before the
gravity-facing attenuation) while its support tread follows the same route profile below the capsule.
FloatingIslands' owner config selects Spiral. A seed-derived anchor island near `(0,0)` is joined to
bedrock by a 6 m radius rock root. The 3 m helix descends from the island mouth to the `-119.5 m`
bedrock top inside that root, leaving at least the authored clearance around its 1.5 m bore, then
widens into the Worm route toward the SurfaceWorld arrival. The selected anchor is ordinal 2; the
other island ordinals and their placement hashes are unchanged. FloatingIslands recipe geometry is
version 5 and the shared passage evaluator is version 10; the resolved-layout snapshot stays V3.
The passage naturalness audit rejects a horizontal run above 8 m (segments flatter than 0.5 degrees)
or wall relief below 0.20 m peak-to-peak across at least 8 valid samples. The smooth-wall control
has zero relief and fails; the anchored Spiral has 0 m flat run and 0.793 m measured relief. Strict
layout controls, route binding, capsule clearance, support, grade, and the no-carve negative control
remain required. Passage-centered interval proofs remain unknown/fallback rather than falsely
certified. At the lower mouth, the shared layer now relies on native terrain plus the passage tread;
it does not stamp additional flat route-anchor shelves over the cave opening. The L24 report records
compiler/worker parity, unchanged SurfaceWorld hashes away from the route, captures and the Windows
adapter checklist.
The Linux offscreen capture tool accepts `--headlamp=on` to aim brighter readable shading from the
camera for interior views and `--clip-plane=NX,NY,NZ,D` to discard the plane's negative half-space for
cutaway views. Both affect captures only, not generated geometry.
[`Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md)
records the evidence and owner-facing caveats.
### 8.43 Enclosed FloatingIslands helix and native-solid passage support — World Assembler L25
L24's support tread was composited into the passage field without requiring native solid terrain at
the support sample. A 6 m root could not contain the resolved helix either: the centreline reaches
5.562 m from the anchor axis and the mouth bore is 4.5 m. With the old root radius, the tread could
therefore read as a free-floating spiral outside the rock. This was not a unit conversion error.
The shared passage composer now emits route-mouth support, tread, and fallback support only where
the pre-passage native density is solid. The open-air audit samples each support footprint at 0.25 m
spacing and two depths, including route-mouth shelves; its L24 ungated-support control must report a
violation. The FI anchor root is now a relief-bearing 13.25 m nominal rock column. Across five
heights its measured radius is 12.0–13.375 m. Taking the maximum 5.562 m centreline radius and the
full 4.5 m mouth bore at every height gives a conservative minimum wall of 1.938 m, exceeding the
previous 1.5 m reserve. The authored mid-bore remains 3 m. The helix is selected from its route-bound
root point and joins the lower route before the tunnel hands off through the shared SW ceiling.
The configured FI Spiral passes the capsule/support/grade audit with zero blocked, unsupported, or
grade-failure samples; the L23 smooth-wall control fails naturalness and the L24 ungated-support
control fails the open-air audit. The other style overrides also pass the open-air audit, but their
capsule results are recorded individually in the L25 report because some overrides are not
walkable after floating support is suppressed. Evaluator version is 12, FI geometry version is 7,
and resolved-layout schema V3 remains unchanged. The L25 report records measurements, hashes,
captures, style results, and the single Unreal adapter version edit.
[`Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md)
records the evidence and caveats.
### 8.44 Walkable passages as tunnels in rock — World Assembler L26
L25 correctly stopped support from appearing in native air, but its SurfaceWorld-recipe passages
still crossed a long open volume in the SurfaceWorld arrival strata. The segment map places that gap
inside SurfaceWorld (about `-146` to `-497 m`), after the TunnelNetwork transition; it is not TunnelNetwork
cave air. Both ends meet native rock. The shared Core planner now finds contiguous unsupported ranges
from the native field sign and caches them by resolved-layout identity. It adds a deterministic,
relief-bearing rock span only across those ranges. The span is at least 5 m wide, several metres thick,
and joins native rock at both ends. The passage carve then opens the bore through the added density,
leaving a grounded floor and rock walls/ceiling. SurfaceWorld away from the passage remains unchanged.
Native route support remains gated on pre-passage solid density. Before adding any support density, the
composer also checks the resolved player capsule, so neighboring floor segments cannot restore rock
inside the avatar path. This keeps all four style overrides walkable in both recipes. The bridge audit
measures its authored density field at 256 route sections, checks the 5 m width and connected ends, and
records a relief-bearing thickness range; the open-air audit still runs the L24 negative control.
The shared evaluator is version 13; FloatingIslands geometry remains version 7 and resolved-layout
schema V3 is unchanged. These changes are in Core and the standalone audit/capture path; no Unreal
adapter edit is required. The L26 report contains the eight-style table, hashes, captures and run history.
[`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md)
records the final evidence.
### 8.45 Tile-bounded passage evaluation — World Assembler M3 Linux L28
Each mesh tile scopes passage composition to the inclusive density-sample AABB, including the
mesher's normal halo. A conservative feature AABB BVH selects route segments, columns and mouth
support for that tile. A per-passage nearest-segment BVH bounds the long helix search; a separate
passage-bound BVH rejects far L9 proof boxes. An unindexed native-only proof object uses a conservative
linear fallback. No field, union order, serialized layout or recipe version changes.
The column's exact complex SDF is evaluated only in XY/Z feature candidates. A cheap density-aware
radial guard remains for samples sharing the column's Z slab: a positive column distance can still
lower positive base density, so skipping that guard needs an exact bound on the base field. This is
the remaining SurfaceWorld cost noted in the Linux L28 report. The passage candidate lists and
proof bounds preserve the L27 output.
[Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md)
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.
### 8.37 Owner/Core stratum coordinates and FloatingIslands boundaries — World Assembler M3 L17
Manager chunk coordinates are in owner chunks, while Core density positions are Core voxels. Convert
between them with `32 * OwnerVoxelSizeMicrons / CoreVoxelSizeMicrons`; do not divide Core positions
by `CHUNK_SIZE` directly. The L17 owner snapshot uses 0.125 m owner voxels and 0.25 m Core voxels,
so one owner chunk is 16 Core voxels (4 m), not 32 (8 m). The old hard-coded divisor doubled the
manifest's vertical stratum bounds and misplaced native-only readiness queries.
FloatingIslands is the sparse `FloatingIslandFieldV1` manifest route. Its 0.5 m `boundarySeal`
limits feature influence; it is not a signed solid cap across the whole XY plane. Applying it as a
solid cap produced a false top at +3.5 m, while the doubled legacy layer bound also produced a cap at
+8 m. SurfaceWorld remains the planar `Heightfield2D` field with its independent ground and cap
surfaces. Adjacent native FI/SW strata now clip their signed distances to the same physical owner
boundary, preserving exact surface signs and height through the mesh sample lattice.
The output change increments `WorldAssemblyEvaluatorVersion` from 6 to 7; the plan schema and
recipe geometry version stay unchanged because their inputs did not change. The L17 probes found
zero cave/floor influence at the false square and at the sampled island ground. The shared Core
coordinate conversion also changes SurfaceWorld native readiness and hashes where the previous
mapping selected the wrong route. See
[`Standalone/Research/WorldAssemblerM3/Linux17/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux17/REPORT.md).
## 9. Multiplayer model (listen-server first, dedicated-friendly)
+3 -6
View File
@@ -26,12 +26,9 @@ try to run anything.
- **Density sign: negative = solid, positive = air** (MC convention at the mesher).
`FVoxelModification::Strength` negative = carve. *The #1 source of confusion.*
- **Density functions take VOXEL coords, not cm.** The legacy plugin field uses 12.5 cm voxels;
the opt-in Core field uses 25 cm voxels and its own 32³-voxel storage tile. The legacy 32³ chunk
spans 4 m at 12.5 cm. Strate placement uses the separate world-level `StrateLayoutChunkSizeMeters`
setting (8 m by default), resolved once and shared by every generator. At 25 cm that layout unit is
32 Core voxels; at 20 cm it is 40. Never infer the layout unit from `CHUNK_SIZE` or the Core tile.
Conversions live in `VoxelTypes.h`, the director, and the Core bridge; do not mix scales in queries,
anchors, edits, or surface lookups.
the opt-in Core field uses 25 cm voxels and its own tile keys. One legacy 32³ chunk is 4 m.
Conversions live in `VoxelTypes.h` and the Core bridge; do not mix scales in queries, anchors, edits,
or surface lookups.
- **Determinism:** all randomness = hash of (coord, seed, strateIndex). No RNG state — same seed ⇒
same world. Player edits (diff layer) are the only non-deterministic overlay.
- **Core world invariants:** Core density keeps the same sign (negative solid, positive air), but Core
+29 -208
View File
@@ -29,9 +29,7 @@ A **density-field voxel terrain** plugin for Unreal Engine, built around undergr
### Core terminology
| 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. |
| 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. |
| Chunk | 32×32×32 voxels (`CHUNK_SIZE`). Voxel = 12.5 cm (`VOXEL_SIZE`), so a chunk is 4 m. |
| Density | Scalar field. `< 0` solid, `> 0` air, `0` = surface (`IsoLevel`). |
| Strate | Vertical layer of the world, stacked downward. Has its own generator + params. |
| Generator type | Archetype per strate: `TunnelNetwork`, `FlatPlain`, `CrystalChamber`, `Maze`, `SurfaceWorld`, `VerticalShafts`, `FloatingIslands`, `Underwater`. See §8. |
@@ -70,10 +68,8 @@ A **density-field voxel terrain** plugin for Unreal Engine, built around undergr
(RealtimeMeshComponent)
```
`UVoxelStrateManager` owns resolved half-open metre intervals (`[bottom, top)`) and is the side
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.
`UVoxelStrateManager` is the side oracle: "what strate is at this Z, what params,
what generator type, and is there a passage/elevator SDF near here?"
---
@@ -82,12 +78,10 @@ dispatch APIs.
Paths relative to `Source/VoxelForge/`. `Public/` = headers, `Private/` = impl.
### 3.0 Human-scale generation contract (2026-10-01)
`VOXEL_SIZE` is **12.5 cm** for the legacy generator. Its 32³-voxel chunks span **4 m**. The
world-level strate-layout unit is a separate setting with an **8 m default**; a 16-chunk strate is
therefore **128 m**, and a 64-chunk strate is **512 m**. Core still stores work in 32³-voxel tiles;
at 25 cm those tiles happen to span 8 m, but they are not the layout unit. The gameplay capsule remains
**0.34 m radius / 1.76 m tall** (**2.72 / 14.08 legacy voxels**). Research-field dimensions are authored
in metres and converted at runtime. The live
`VOXEL_SIZE` is **12.5 cm**; 32³ voxels make each chunk **4 m**. The gameplay capsule remains
**0.34 m radius / 1.76 m tall** (**2.72 / 14.08 voxels**). The default strate envelope is
**16 chunks / 512 voxels / 64 m**; serialized owner strate assets that still say 8 chunks must be
changed to 16. Research-field dimensions are authored in metres and converted at runtime. The live
strate generator continues to read voxel-valued parameters from its assets, so those authored
dimensions become half as large in metres unless their asset values are doubled.
@@ -169,16 +163,13 @@ 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. |
| `../../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` / `../../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. |
| `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`. |
| `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/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` | 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/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` | Validates the optional manifest against each resolved `SurfaceWorld` slot (unique strate index and exact top/bottom chunks), then places canonical `world_assembly` JSON and its hash in the Core snapshot. 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. |
| `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. |
@@ -190,7 +181,7 @@ remains `0`, and lateral regions remain gated off.
| Symbol | Line | Notes |
|--------|------|-------|
| `CHUNK_SIZE` (32) | 211 | Chunk dimensions. 64³ would cut draw calls but makes streaming too bursty; fps is fixed render-side instead. |
| `VOXEL_SIZE` (12.5f cm) | 213 | Legacy generator scale; `CHUNK_SIZE` 32 spans 4 m here. Keep separate from the 8 m default layout chunk and 32³ Core tile. |
| `VOXEL_SIZE` (12.5f cm) | 213 | World scale; one 32³ chunk is 4 m. |
| `EVoxelFace` enum | 266 | 6 cube faces. |
| `WorldToChunkCoord` | 287 | World cm → chunk coord (handles negatives via floor). |
| `VoxelMath::IsFiniteFast` / `IsFinite` | 24 / 45 | Exact IEEE bit test (`voxel.FastIsFinite` A/B switch). |
@@ -217,16 +208,14 @@ all eight archetypes; there is no other path. See [Docs/archive/OPSTACK-PLAN.md]
| `FVoxelBoxHypotheses` + `FVoxelBoxSdfInterval` + `VF_ForceHypotheses` / `VF_FoldEffect` / `VF_FoldOp` | The fold that turns a stack into an `EVoxelTileClass`, carrying the SDF interval between independent writers and consumers. Unknown is biased toward `Mixed`; the mapping and soundness rule are written out in the header. |
### 3.2c Structural primitives — `Public/VoxelDensityPrimitives.h`
`VF_ApplyOriginSpine` · `VF_ApplyBoundarySeal` · `VF_ApplyUpperBoundarySeal` · `VF_ApplyPassageCarving` · `VF_ApplyXYEdgeSeal` —
`VF_ApplyOriginSpine` · `VF_ApplyBoundarySeal` · `VF_ApplyPassageCarving` · `VF_ApplyXYEdgeSeal` —
the four world invariants every archetype appends, with landing/floor geometry joined by the
manager passage post, **moved here 2026-07-27** so the generator and
the operator stack share ONE copy. The XY helper is a radial smooth ramp in actor-space and is a
true no-op when `WorldRadiusVoxels == 0`; `VF_XYEdgeSealForcedMarginOverBox` is the shared sound
proof for `ClassifyBox` and T1.d's global shell skip. `VoxelGenerator.cpp` keeps same-named
`static FORCEINLINE` forwarders for `ApplyBoundarySeal` and `ApplyOriginSpine`; the passage and
edge helpers are called directly. `VF_ApplyUpperBoundarySealValue` contains the common upper-band
math used by both one-sided and two-sided seals. **Convention: INTERNAL (positive = solid);
`VF_ApplyXYEdgeSealMC` is the output wrapper.**
edge helpers are called directly. **Convention: INTERNAL (positive = solid); `VF_ApplyXYEdgeSealMC` is the output wrapper.**
### 3.2d Operator stack — `Public/VoxelDensityOpStack.h` + `Private/VoxelDensityOpStack.cpp`
⚠️ **This IS the game's generation path.** `GetDensityAt` builds the stack in its per-chunk refetch
@@ -602,16 +591,13 @@ content lists, audio, `GameplayTags`(393). EditConditions show/hide param groups
type.
**`Public/VoxelStrateManager.h` + `.cpp`** — `UVoxelStrateManager : UObject` (h:152).
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.
Maps depth→strate at runtime; owns passages.
- `FVoxelPassage` (h:46): standing endpoints, radius, type, wandering control points, and
upper/lower local landing descriptors (room, floor, and door; no root connector).
- `FStrateSlot` (h:128): content definition, resolved micrometre bounds/height, resolved transition
and passage inputs, and compatibility chunk-Z projection + strate index.
- `FStrateSlot` (h:128): definition + chunk-Z range + index.
| Method | .cpp line | Role |
|--------|-----------|------|
| `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. |
| `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. |
| `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. |
| `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. |
@@ -619,18 +605,18 @@ chunk APIs use the cached integer projection.
| `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. |
| `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` / `FindSlotIndexForWorldZCentimetres` | 6239 / 6251 | Chunk callers use the cached compatibility projection; world-Z callers search physical half-open micrometre intervals. |
| `GetStrateAt` / `GetStrateIndex` | 6276 / 6287 | World-Z queries against half-open micrometre intervals. |
| `FindSlotIndexForChunkZ` | 6111 | Z → layout index. |
| `GetStrateAt` / `GetStrateIndex` | 6127 / 6139 | World-Z queries. |
| `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` | 6298 | Chunk → definition via the compatibility projection. |
| `GetGeneratorTypeForChunk` | 6324 | Chunk → generator type via the compatibility projection. |
| `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. |
| `GetStrateForChunk` | 6150 | Chunk → definition. |
| `GetGeneratorTypeForChunk` | 6176 | Chunk → generator type. |
| `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. |
| `GetRecipeForChunk` | — | Runtime immutable recipe/vector/strate-seed copy for cooked seasons; editor slot overrides use the same worker hand-off. |
| `GetSlabParamsForChunk` | 6415 | Slab params with runtime Z bounds (no blend — slabs use Hard). |
| `GetSlabParamsForChunk` | 6263 | 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. |
| `GetGenerationParams` | 6661 | **Blended** TunnelNetwork params (handles Gradient/Hard/Interleaved transitions). |
| `GetGenerationParams` | 6509 | **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. |
| `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. |
| `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. |
**`Public/VoxelTerrainOpDefinition.h` + `.cpp`** — `UVoxelTerrainOpDefinition : UPrimaryDataAsset`
(h:67). One asset = one terrain op. `EVoxelTerrainOpType` (h:36): Terrace, LayerLines,
@@ -1146,183 +1132,18 @@ Implementation and evidence: [`Standalone/Research/WorldAssemblerM3/Linux9/REPOR
| Symbol | Canonical file | Role |
|---|---|---|
| `FStandaloneShapeWorld::SetSharedDensitySource`, `SharedDensitySource` | `Standalone/Core/src/ShapeWorldInternal.h`, bound before publication in `Standalone/Core/src/VoxelForgeCore.cpp` | Makes compiled analytic pages delegate field, lattice, detail, classification, and solid queries to the Core world's base shape; `WorldAssemblyEvaluatorVersion` is 6. |
| `FStandaloneShapeWorld::SetSharedDensitySource`, `SharedDensitySource` | `Standalone/Core/src/ShapeWorldInternal.h`, bound before publication in `Standalone/Core/src/VoxelForgeCore.cpp` | Makes compiled analytic pages delegate field, lattice, detail, classification, and solid queries to the Core world's base shape; `WorldAssemblyEvaluatorVersion` is 7. |
The base manifest field is authoritative for tile generation and exact queries. See ARCHITECTURE
§8.36 and [`Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md).
### 3.23 Shared strate boundaries and FloatingIslands seal — World Assembler M3 Linux18
### 3.23 Owner/Core stratum coordinates — World Assembler M3 L17
| Symbol | Canonical file | Role |
|---|---|---|
| `FStandaloneShapeWorld::FResolvedStratumBounds`, `MakeResolvedStratumBounds`, `GetResolvedStratumBounds` | `Standalone/Core/src/ShapeWorldInternal.h` | Resolves each strate's physical lower/upper range and seal width for layer ranges, far-field sample protection, and far-shell continuity. Native FloatingIslands uses its plan's owner/Core ratio; SurfaceWorld retains its `32` Core voxels per slot chunk. |
| `VF_ApplyUpperBoundarySeal` / `VF_ApplyUpperBoundarySealValue` | `Standalone/Source/VoxelForge/Public/VoxelDensityPrimitives.h` and `Source/VoxelForge/Public/VoxelDensityPrimitives.h` | Shared one-sided top seal used by version-3 native FloatingIslands; the two-sided seal reuses the same upper-band math. |
Geometry version 3 removes the FI lower all-XY seal while retaining its ceiling through the shared
upper-bound path. Versions 1 and 2 retain their serialized two-sided behavior. See ARCHITECTURE §8.37
and [`Standalone/Research/WorldAssemblerM3/Linux18/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux18/REPORT.md).
### 3.24 Resolved inter-strate passages — World Assembler M3 L23
| 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. |
| `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. |
The evaluator version is 9. See ARCHITECTURE §8.41 and
[`Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md).
### 3.25 Per-strate natural passages and FloatingIslands anchor helix — World Assembler L24
| Symbol | Canonical file | Role |
|---|---|---|
| `FStratePassageConfig::Style`, `EVoxelPassageStyle` | `Source/VoxelForge/Public/VoxelStrateTypes.h` | Selects Straight, Worm, Spiral or Cascading per upper strate; Worm is the default. |
| `PlanResolvedLayoutPassagesV1` | `Standalone/Core/src/ResolvedLayoutInternal.h` | Applies the selected style in the shared planner; a configured FI Spiral follows the anchor root into the bedrock and smoothly transitions into the inter-strate route. |
| Anchor root recipe fields and `EvaluateIslandSDF` | `Standalone/Core/src/FloatingIslandsInternal.h` | Add the seed-stable ordinal-2 island/root without changing other island placements; geometry version 5. |
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies deterministic rough wall relief and the supported route profile to all native generator fields. |
| `--audit-resolved-layout-passages` naturalness checks | `Standalone/Source/StreamBenchmarkPipeline.h` | Checks 8 m maximum flat run, 0.20 m minimum wall relief, capsule/support/grade, mouths, routes and the blocked-passage control. |
| `--headlamp=on`, `--clip-plane=NX,NY,NZ,D` | `Standalone/Source/LinuxOffscreenCapture.cpp` | Adds camera-directed light or discards the plane's negative half-space for offscreen cutaways; neither affects Core output. |
The shared evaluator is version 10, the FI recipe geometry is version 5, and resolved-layout schema
V3 is unchanged. See ARCHITECTURE §8.42 and
[`Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md).
### 3.26 Enclosed FloatingIslands helix and open-air support gate — World Assembler L25
| Symbol | Canonical file | Role |
|---|---|---|
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Emits route-mouth support, tread and fallback support only over pre-passage native solid density, shared by every passage style. |
| `auditPassageOpenAir` / `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples the support footprint at 0.25 m spacing and two depths; compares against L24 ungated support as a required negative control. |
| Anchor root dimensions and relief | `Standalone/Core/src/FloatingIslandsInternal.h` | Raises the FI anchor root from a 6 m cylinder to a nominal 13.25 m relief-bearing rock column sized around the 3 m helix and 4.5 m mouth. |
| Passage route and root measures | `Standalone/Core/src/ResolvedLayoutInternal.h`, `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Binds the Spiral to the root and reports measured centreline/root clearance. |
The shared evaluator is version 12 and FI geometry version is 7; resolved-layout schema V3 is
unchanged. See ARCHITECTURE §8.43 and
[`Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md).
### 3.27 Tunnels in native rock with grounded passage bridges — World Assembler L26
| Symbol | Canonical file | Role |
|---|---|---|
| `BuildPassageBridgePlan` / `PassageBridgeSignedDistance` | `Standalone/Core/src/ShapeWorldInternal.h` | Uses native density sign to plan cached, connected rock spans only across unsupported route ranges; shared by both recipes and all passage styles. |
| `PassageBridgeCache` | `Standalone/Source/OwnerWorldLoader.h` | Shares deterministic ranges by resolved-layout identity across shape instances. |
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Adds bridge density before carving the bore; gates native floor support on solid input and player-capsule clearance. |
| `auditPassageBridges` / `auditPassageOpenAir` / `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Measures bridge width, thickness, relief and connected ends; checks capsule support, open air, naturalness, route binding and the L24 negative control. |
| `PassageBridgeRangeAuditV1` / `passageBridgeRanges()` / `isPassageBridgeSample()` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/VoxelForgeCore.cpp` | Exposes audit-only bridge ranges and density queries to the standalone validator. |
The shared evaluator is version 13; FloatingIslands geometry remains version 7 and resolved-layout
schema V3 is unchanged. See ARCHITECTURE §8.44 and
[`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md).
### 3.28 Tile-bounded passage evaluation — World Assembler M3 Linux L28
| Symbol | Canonical file | Role |
|---|---|---|
| FScopedPassageTileQueries / BuildPassageFeatureTileList / ResolvedPassageIntersectsBox | Standalone/Core/src/ShapeWorldInternal.h; scope entered by GenerateMesh in Standalone/Core/src/VoxelForgeCore.cpp | Builds conservative per-tile route, column and mouth-support candidates from an AABB BVH; long paths use a nearest-segment BVH and L9 box checks use a separate passage-bound BVH. Empty native-only proof indexes fall back conservatively. The density arithmetic and serialized versions stay unchanged. |
See ARCHITECTURE §8.45 and
[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.
`FStandaloneShapeWorld::GetCoreVoxelsPerOwnerChunk` is the shared conversion for manager chunk
lookups, manifest layer bounds, readiness proofs, and adjacent native strata. FloatingIslands uses
`FloatingIslandFieldV1`; its boundary seal bounds feature influence and does not fill an XY-wide
solid cap. SurfaceWorld remains the `Heightfield2D` path. See ARCHITECTURE §8.37 and
[`Standalone/Research/WorldAssemblerM3/Linux17/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux17/REPORT.md).
## 4. The density pipeline (most-edited hot path)
@@ -32,38 +32,6 @@ namespace
using FCoreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
constexpr double OwnerVoxelSizeCm = VOXEL_SIZE;
float StrateBoundarySealThickness(const UVoxelStrateDefinition& Definition)
{
switch(Definition.GeneratorType)
{
case ECaveGeneratorType::SurfaceWorld:return Definition.SurfaceParams.BoundarySealThickness;
case ECaveGeneratorType::FloatingIslands:return Definition.FloatingIslandParams.BoundarySealThickness;
case ECaveGeneratorType::Maze:return Definition.MazeParams.BoundarySealThickness;
case ECaveGeneratorType::VerticalShafts:return Definition.VerticalShaftParams.BoundarySealThickness;
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
case ECaveGeneratorType::Underwater:return Definition.GenerationParams.BoundarySealThickness;
}
return Definition.GenerationParams.BoundarySealThickness;
}
float StrateBoundarySolidDensity(const UVoxelStrateDefinition& Definition)
{
switch(Definition.GeneratorType)
{
case ECaveGeneratorType::SurfaceWorld:return Definition.SurfaceParams.BaseDensity;
case ECaveGeneratorType::FloatingIslands:return Definition.FloatingIslandParams.BaseDensity;
case ECaveGeneratorType::Maze:return Definition.MazeParams.BaseDensity;
case ECaveGeneratorType::VerticalShafts:return Definition.VerticalShaftParams.BaseDensity;
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
case ECaveGeneratorType::Underwater:return Definition.GenerationParams.BaseDensity;
}
return Definition.GenerationParams.BaseDensity;
}
std::int64_t WorldAssemblyFixed(double Voxels)
{
return static_cast<std::int64_t>(std::llround(
@@ -107,14 +75,6 @@ std::int64_t WorldAssemblyRoundRatio(std::int64_t Numerator, std::int64_t Denomi
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 Root = 0;
@@ -320,18 +280,15 @@ VoxelForgeCore::WorldAssemblyBiomeProfileV1 BuildSurfaceProfile(
bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
const FStrateSlot& Slot,
VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum,
std::uint32_t CoreVoxelMicrometres, FString& OutError)
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum, FString& OutError)
{
using namespace VoxelForgeCore;
const FIntVector Chunk(0, 0, Slot.TopChunkZ);
const FSurfaceGenerationParams Surface = StrateManager->GetSurfaceParamsForChunk(Chunk);
const FBiomeContext BiomeContext = StrateManager->GetBiomeContextForChunk(Chunk);
const UVoxelStrateDefinition* Definition = Slot.Definition;
const double BottomVoxel = static_cast<double>(Slot.BottomWorldMicrometres)
/ std::max<std::uint32_t>(1, CoreVoxelMicrometres);
const double HeightVoxels = static_cast<double>(Slot.HeightMicrometres)
/ std::max<std::uint32_t>(1, CoreVoxelMicrometres);
const double BottomVoxel = static_cast<double>(Slot.BottomChunkZ) * CHUNK_SIZE;
const double HeightVoxels = static_cast<double>(Slot.TopChunkZ - Slot.BottomChunkZ + 1) * CHUNK_SIZE;
const FBiomeMapParams& Map = BiomeContext.Map;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.biomeCellSizeSubvoxels = WorldAssemblyFixed(Map.CellSize);
@@ -463,12 +420,9 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
return false;
}
Shelf.minimumSubvoxels = {WorldAssemblyFixed(MinX), WorldAssemblyFixed(MinY),
std::max(WorldAssemblyMicrometresToSubvoxels(
Slot.BottomWorldMicrometres, CoreVoxelMicrometres),
LowestGround - WorldAssemblyFixed(4.0))};
std::max(WorldAssemblyFixed(BottomVoxel), LowestGround - WorldAssemblyFixed(4.0))};
Shelf.maximumSubvoxels = {WorldAssemblyFixed(MaxX), WorldAssemblyFixed(MaxY),
std::min(WorldAssemblyMicrometresToSubvoxels(
Slot.TopWorldMicrometres, CoreVoxelMicrometres),
std::min(WorldAssemblyFixed((Slot.TopChunkZ + 1) * CHUNK_SIZE),
HighestGround + WorldAssemblyFixed(ShelfHeight + 4.0))};
Shelf.parametersSubvoxels = {WorldAssemblyFixed(DirX), WorldAssemblyFixed(DirY),
WorldAssemblyFixed(ShelfHeight), WorldAssemblyFixed(Reach)};
@@ -486,20 +440,17 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
const FStrateSlot& Slot, VoxelForgeCore::WorldAssemblyManifest& Manifest,
double CoreVoxelSizeCm, std::int64_t LayoutChunkWidthSubvoxels, FString& OutError)
double CoreVoxelSizeCm, FString& OutError)
{
using namespace VoxelForgeCore;
const FIntVector Chunk(0, 0, Slot.TopChunkZ);
const FFloatingIslandParams Params = StrateManager->GetFloatingIslandParamsForChunk(Chunk);
const FStratePassageConfig& Passage = Slot.ResolvedPassageConfig;
WorldAssemblyFloatingIslandPlanV1 IslandPlan;
IslandPlan.strateIndex = Slot.StrateIndex;
IslandPlan.topChunkZ = Slot.TopChunkZ;
IslandPlan.bottomChunkZ = Slot.BottomChunkZ;
auto& Recipe = IslandPlan.manifest.recipe;
Recipe.geometryVersion = 8;
Recipe.terrainFollowingRoute = static_cast<std::uint32_t>(Passage.Style) == 0;
Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels;
Recipe.geometryVersion = 2;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
Recipe.coreVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(CoreVoxelSizeCm * 10000.0));
@@ -512,10 +463,12 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
}
Recipe.ownerToCoreScaleNumerator = Recipe.ownerVoxelSizeMicrons / ScaleGcd;
Recipe.ownerToCoreScaleDenominator = Recipe.coreVoxelSizeMicrons / ScaleGcd;
Recipe.layoutMinimumZSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.BottomWorldMicrometres, Recipe.coreVoxelSizeMicrons);
Recipe.layoutMaximumZSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.TopWorldMicrometres, Recipe.coreVoxelSizeMicrons);
const double CoreVoxelsPerOwnerChunk = static_cast<double>(CHUNK_SIZE)
* Recipe.ownerToCoreScaleNumerator / Recipe.ownerToCoreScaleDenominator;
Recipe.layoutMinimumZSubvoxels = WorldAssemblyFixed(
static_cast<double>(Slot.BottomChunkZ) * CoreVoxelsPerOwnerChunk);
Recipe.layoutMaximumZSubvoxels = WorldAssemblyFixed(
static_cast<double>(Slot.TopChunkZ + 1) * CoreVoxelsPerOwnerChunk);
Recipe.layoutCenterTopZSubvoxels = (Recipe.layoutMinimumZSubvoxels
+ Recipe.layoutMaximumZSubvoxels) / 2;
Recipe.islandDensityQ16 = WorldAssemblyQ16(FMath::Clamp(Params.IslandDensity, 0.0f, 1.0f));
@@ -530,43 +483,6 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
Recipe.roughnessSubvoxels = WorldAssemblyFixed(Params.SurfaceRoughness * OwnerToCoreScale);
Recipe.blendRadiusSubvoxels = WorldAssemblyFixed(Params.SDFBlendRadius * OwnerToCoreScale);
Recipe.boundarySealSubvoxels = WorldAssemblyFixed(Params.BoundarySealThickness * OwnerToCoreScale);
const auto OwnerVoxelsToCoreQ=[&](double Value)
{
return static_cast<std::int64_t>(std::llround(Value*Recipe.ownerVoxelSizeMicrons
*WorldAssemblySubvoxelsPerVoxel/Recipe.coreVoxelSizeMicrons));
};
Recipe.anchorIslandOrdinal=Recipe.primaryIslandCount-1;
Recipe.anchorPlacementRadiusSubvoxels=WorldAssemblyMicrometresToSubvoxels(
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.
constexpr double AnchorPassageMinimumBoreOwnerVoxels=12.0;
const std::int64_t OwnerVoxelMicrometres = Recipe.ownerVoxelSizeMicrons;
const std::int64_t mouthBore=std::max(
WorldAssemblyMicrometresToSubvoxels(Slot.PassageMouthRadiusMicrometres,
Recipe.coreVoxelSizeMicrons),
WorldAssemblyMicrometresToSubvoxels(12 * OwnerVoxelMicrometres,
Recipe.coreVoxelSizeMicrons));
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.anchorIslandMinimumRadiusSubvoxels=std::max(Recipe.minimumRadiusSubvoxels,
Recipe.anchorMouthOffsetSubvoxels+mouthBore+FourOwnerVoxelsSubvoxels);
Recipe.anchorRootBottomZSubvoxels=Recipe.layoutMinimumZSubvoxels
+Recipe.boundarySealSubvoxels;
std::string PlanError;
const std::optional<FloatingIslandManifestV1> Planned = PlanFloatingIslandCluster(Recipe, PlanError);
if (!Planned)
@@ -842,12 +758,6 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
const TSharedRef<FJsonObject>& Root,
FString& OutError)
{
const std::uint32_t LayoutChunkSizeMicrometres=StrateManager->GetStrateLayoutChunkSizeMicrometres();
const std::uint32_t CoreVoxelMicrometres=static_cast<std::uint32_t>(std::llround(CoreVoxelSizeCm*10000.0));
const std::int64_t LayoutChunkWidthSubvoxels=static_cast<std::int64_t>(std::llround(
static_cast<double>(LayoutChunkSizeMicrometres)*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
/std::max<std::uint32_t>(1,CoreVoxelMicrometres)));
StrateManager->SetHardTransitionFallbackForAssembly(true);
FString ManifestJson;
FString ManifestHash;
if (!Settings->GetWorldAssemblyManifestJson(ManifestJson, ManifestHash, OutError))
@@ -873,7 +783,6 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
{
VoxelForgeCore::WorldAssemblyManifest Generated;
Generated.recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Generated.recipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
int32 SurfaceSlotCount = 0;
int32 IslandSlotCount = 0;
for (const FStrateSlot& Slot : StrateManager->GetLayout())
@@ -906,10 +815,9 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
{
VoxelForgeCore::WorldAssemblyRecipeV1 SlotRecipe;
SlotRecipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
SlotRecipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
VoxelForgeCore::WorldAssemblyStratumPlanV1 Stratum;
if (!BuildSurfaceStratumPlan(StrateManager, Slot,
SlotRecipe, Stratum, CoreVoxelMicrometres, OutError))
SlotRecipe, Stratum, OutError))
{
return false;
}
@@ -921,7 +829,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
else if (Type == ECaveGeneratorType::FloatingIslands)
{
if (!BuildFloatingIslandStratumPlan(StrateManager, Slot, Generated,
CoreVoxelSizeCm, LayoutChunkWidthSubvoxels, OutError))
CoreVoxelSizeCm, OutError))
{
return false;
}
@@ -965,11 +873,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
OutError = TEXT("floating-island override uses a different Core voxel size; regenerate the plan.");
return false;
}
if(Island.manifest.recipe.layoutChunkWidthSubvoxels!=LayoutChunkWidthSubvoxels)
{OutError=TEXT("floating-island override uses a different strate-layout chunk width; regenerate the plan.");return false;}
}
if(Manifest->recipe.layoutChunkWidthSubvoxels!=LayoutChunkWidthSubvoxels)
{OutError=TEXT("world assembly manifest uses a different strate-layout chunk width; regenerate the plan.");return false;}
int32 SurfaceSlotCount = 0;
int32 FloatingIslandSlotCount = 0;
@@ -1040,255 +944,6 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
MatchedStrateIndices.Add(Plan.strateIndex);
}
// Keep the inspectable physical-layout export separate from the manifest identity: it is
// assembled from the same resolved slots/plans, but adding it must not salt evaluation hashes.
VoxelForgeCore::ResolvedLayoutSnapshotV3 Layout;
Layout.worldIdentity = std::string("owner-world/seed/")
+ std::to_string(StrateManager->GetWorldSeed());
Layout.ownerVoxelSizeMicrometres = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
Layout.coreVoxelSizeMicrometres = ExpectedCoreVoxelMicrons;
Layout.strateLayoutChunkSizeMicrometres=LayoutChunkSizeMicrometres;
Layout.strateLayoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
const auto generatorName = [](ECaveGeneratorType Type) -> const char*
{
switch (Type)
{
case ECaveGeneratorType::TunnelNetwork: return "TunnelNetwork";
case ECaveGeneratorType::FlatPlain: return "FlatPlain";
case ECaveGeneratorType::CrystalChamber: return "CrystalChamber";
case ECaveGeneratorType::Maze: return "Maze";
case ECaveGeneratorType::SurfaceWorld: return "SurfaceWorld";
case ECaveGeneratorType::VerticalShafts: return "VerticalShafts";
case ECaveGeneratorType::FloatingIslands: return "FloatingIslands";
case ECaveGeneratorType::Underwater: return "Underwater";
}
return "Unknown";
};
const auto transitionName = [](EVoxelStrateTransition Type) -> const char*
{
switch (Type)
{
case EVoxelStrateTransition::Gradient: return "Gradient";
case EVoxelStrateTransition::Hard: return "Hard";
case EVoxelStrateTransition::Interleaved: return "Interleaved";
}
return "Unknown";
};
const std::int64_t OwnerVoxelMicrons = Layout.ownerVoxelSizeMicrometres;
std::vector<VoxelForgeCore::ResolvedLayoutPassageConfigV1> PassageConfigs;
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (Slot.Definition == nullptr) continue;
const FStratePassageConfig& ResolvedPassage = Slot.ResolvedPassageConfig;
VoxelForgeCore::ResolvedLayoutPassageConfigV1 PassageConfig;
PassageConfig.upperStrateIndex = Slot.StrateIndex;
PassageConfig.connections = ResolvedPassage.Connections;
PassageConfig.style = static_cast<std::uint32_t>(ResolvedPassage.Style);
PassageConfig.mouthRadiusSubvoxels = std::max(
WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMouthRadiusMicrometres, ExpectedCoreVoxelMicrons),
WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.midRadiusSubvoxels = std::max(
WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMidRadiusMicrometres, ExpectedCoreVoxelMicrons),
WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.reachMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
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(
static_cast<double>(ResolvedPassage.SpiralTurns) * 65536.0));
PassageConfig.cascadeSteps = static_cast<std::uint32_t>(
FMath::Clamp(ResolvedPassage.CascadeSteps, 1, 16));
PassageConfig.cascadeLedgeSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageCascadeLedgeMicrometres, ExpectedCoreVoxelMicrons);
PassageConfigs.push_back(PassageConfig);
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
VoxelForgeCore::ResolvedLayoutStratumV2 Item;
Item.strateIndex = Slot.StrateIndex;
Item.generatorKind = generatorName(Type);
Item.stableId = Layout.worldIdentity + "/strate/" + std::to_string(Slot.StrateIndex)
+ "/generator/" + generatorName(Type);
Item.recipeId = Type == ECaveGeneratorType::SurfaceWorld ? "SurfaceWorld.Heightfield2D"
: Type == ECaveGeneratorType::FloatingIslands ? "FloatingIslands.ClusterField"
: std::string("legacy.") + generatorName(Type);
Item.recipeVersion = Type == ECaveGeneratorType::SurfaceWorld ? Manifest->recipe.schemaVersion : 0;
Item.voxelSizeMicrometres = ExpectedCoreVoxelMicrons;
Item.interiorBottomMicrometres = Slot.BottomWorldMicrometres;
Item.interiorTopMicrometres = Slot.TopWorldMicrometres;
Item.managerChunkWidthMicrometres = LayoutChunkSizeMicrometres;
Item.dispatchChunkWidthMicrometres = LayoutChunkSizeMicrometres;
Item.transitionMode = transitionName(Slot.ResolvedTransitionType);
Item.transitionBlendChunks = Slot.ResolvedTransitionBlendChunks;
Item.transitionWidthMicrometres = Slot.TransitionWidthMicrometres;
Item.transitionWarpAmplitudeMicrometres = Item.transitionMode == "Interleaved"
? std::min(Item.transitionWidthMicrometres / 2, Item.managerChunkWidthMicrometres) : 0;
Item.transitionInterpenetrationMicrometres = Item.transitionMode == "Interleaved"
? Item.transitionWidthMicrometres
: 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
? "declared zero-width transition uses an explicit Hard fallback"
: 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";
if (Type == ECaveGeneratorType::SurfaceWorld)
{
Item.boundDispatchPath = "BuildSurfaceStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV3";
Item.queryDispatchPath = "NativeSurfaceDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3";
const auto Plan = std::find_if(Manifest->plan.strates.begin(), Manifest->plan.strates.end(),
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
if (Plan != Manifest->plan.strates.end())
{
for (const auto& Profile : Manifest->recipe.biomes)
if (Profile.hasCapProfile)
{
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "cap_profile";
Anchor.stableId = "biome/" + std::to_string(Profile.stableId);
Anchor.units = "1/256 Core voxel";
Anchor.values = { Profile.capHeightSubvoxels, Profile.capUndulationSubvoxels,
Profile.capWavelengthSubvoxels, Profile.capDetailAmplitudeSubvoxels,
Profile.capRidgeAmplitudeSubvoxels };
Item.anchors.push_back(std::move(Anchor));
}
for (std::size_t RouteIndex = 0; RouteIndex < Plan->route.size(); ++RouteIndex)
{
const auto& Point = Plan->route[RouteIndex];
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "route_point"; Anchor.stableId = "route/" + std::to_string(RouteIndex);
Anchor.units = "1/256 Core voxel";
Anchor.values = { Point.positionSubvoxels[0], Point.positionSubvoxels[1],
Point.positionSubvoxels[2], Point.radiusSubvoxels };
Item.anchors.push_back(std::move(Anchor));
}
}
}
else if (Type == ECaveGeneratorType::FloatingIslands)
{
Item.boundDispatchPath = "BuildFloatingIslandStratumPlan uses 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(),
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
if (Plan != Manifest->floatingIslands.end())
{
Item.recipeVersion = Plan->manifest.recipe.geometryVersion;
for (std::size_t IslandIndex = 0; IslandIndex < Plan->manifest.plan.islands.size(); ++IslandIndex)
{
const auto& Island = Plan->manifest.plan.islands[IslandIndex];
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "island_anchor"; Anchor.stableId = "island/" + std::to_string(IslandIndex);
Anchor.units = "1/256 Core voxel";
Anchor.values = { Island.centerSubvoxels[0], Island.centerSubvoxels[1],
Island.centerSubvoxels[2], Island.radiusSubvoxels, Island.topHalfSubvoxels };
Item.anchors.push_back(std::move(Anchor));
if(IslandIndex==Plan->manifest.recipe.anchorIslandOrdinal
&&Plan->manifest.recipe.geometryVersion>=5)
{
VoxelForgeCore::ResolvedLayoutAnchorV2 PassageAnchor;
PassageAnchor.kind="passage_anchor";
PassageAnchor.stableId="passage-anchor/island/"+std::to_string(IslandIndex);
PassageAnchor.units="1/256 Core voxel";
PassageAnchor.values={Island.centerSubvoxels[0],Island.centerSubvoxels[1],
Plan->manifest.recipe.anchorRootRadiusSubvoxels,
Plan->manifest.recipe.anchorMouthOffsetSubvoxels,
Plan->manifest.recipe.anchorPlacementRadiusSubvoxels,
Plan->manifest.recipe.anchorRootBottomZSubvoxels,
Island.radiusSubvoxels};
Item.anchors.push_back(std::move(PassageAnchor));
}
}
for (std::size_t RouteIndex = 0; RouteIndex < Plan->manifest.plan.route.size(); ++RouteIndex)
{
const auto& Point = Plan->manifest.plan.route[RouteIndex];
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "route_point"; Anchor.stableId = "route/" + std::to_string(RouteIndex);
Anchor.units = "1/256 Core voxel";
Anchor.values = { Point.positionSubvoxels[0], Point.positionSubvoxels[1],
Point.positionSubvoxels[2], Point.radiusSubvoxels };
Item.anchors.push_back(std::move(Anchor));
}
}
}
else
{
Item.boundDispatchPath = "resolved manager slot scaled by ResolvedLayoutSnapshotV3 chunk width";
Item.queryDispatchPath = "legacy Core route selects manager chunk by floor(CoreZ / resolved layout chunk width)";
}
const std::int64_t SealMicrometres=static_cast<std::int64_t>(std::llround(
std::max(0.0f,StrateBoundarySealThickness(*Slot.Definition))*OwnerVoxelMicrons));
if(SealMicrometres<=0)
{OutError=FString::Printf(TEXT("Strate %d has no positive BoundarySealThickness for shared ceiling and bedrock bands."),Slot.StrateIndex);return false;}
const std::int64_t SolidDensityQ16=std::max<std::int64_t>(1,WorldAssemblyQ16(
std::max(0.05f,StrateBoundarySolidDensity(*Slot.Definition))));
Item.ceiling={"ceiling",Item.interiorTopMicrometres-SealMicrometres,
Item.interiorTopMicrometres,true,true,false,"solid ceiling from strate BoundarySealThickness; breakable by default",
"interior-facing lower surface","shared resolved-layout density band",SolidDensityQ16};
Item.bedrock={"bedrock",Item.interiorBottomMicrometres,
Item.interiorBottomMicrometres+SealMicrometres,true,true,false,
"solid bedrock floor from strate BoundarySealThickness; breakable by default",
"interior-facing upper surface","shared resolved-layout density band",SolidDensityQ16};
Item.content = { "content", Item.interiorBottomMicrometres,
Item.interiorTopMicrometres, true, true, false, "resolved generator interior",
"none", "none" };
Item.content.bottomMicrometres+=SealMicrometres;
Item.content.topMicrometres-=SealMicrometres;
Layout.strates.push_back(std::move(Item));
}
std::string PassagePlanError;
if (!VoxelForgeCore::PlanResolvedLayoutPassagesV1(Layout,
StrateManager->GetWorldSeed(), PassageConfigs, PassagePlanError))
{
OutError = FString::Printf(TEXT("Resolved inter-strate passage planning failed: %s"),
UTF8_TO_TCHAR(PassagePlanError.c_str()));
return false;
}
std::string LayoutError;
if (!Layout.finalize(LayoutError))
{
OutError = FString::Printf(TEXT("Core resolved-layout export failed: %s"), UTF8_TO_TCHAR(LayoutError.c_str()));
return false;
}
const std::string LayoutJson = Layout.ToJson();
Root->SetStringField(TEXT("resolved_layout_json"),UTF8_TO_TCHAR(LayoutJson.c_str()));
const auto SurfaceSpawnPlan=std::find_if(Manifest->plan.strates.begin(),Manifest->plan.strates.end(),
[](const auto& Plan){return !Plan.route.empty();});
if(SurfaceSpawnPlan!=Manifest->plan.strates.end())
{
const auto& Spawn=SurfaceSpawnPlan->route.front();
TSharedRef<FJsonObject> ViewerStart=MakeShared<FJsonObject>();
ViewerStart->SetBoolField(TEXT("found_open_space"),true);
ViewerStart->SetNumberField(TEXT("x"),static_cast<double>(Spawn.positionSubvoxels[0])
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
ViewerStart->SetNumberField(TEXT("y"),static_cast<double>(Spawn.positionSubvoxels[1])
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
ViewerStart->SetNumberField(TEXT("z"),static_cast<double>(Spawn.positionSubvoxels[2])
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
Root->SetObjectField(TEXT("viewer_start"),ViewerStart);
}
const std::string CanonicalJson = Manifest->ToJson();
if (CanonicalJson.empty())
{
@@ -1342,8 +997,6 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
}
const int32 Seed = StrateManager->GetWorldSeed();
const std::uint32_t LayoutChunkSizeMicrometres =
StrateManager->GetStrateLayoutChunkSizeMicrometres();
const float OriginSpineRadius = Settings->GetEffectiveOriginSpineRadius();
const float WorldRadiusVoxels = Settings->GetEffectiveWorldRadiusVoxels();
const int32 CoreClipRadius = FMath::Clamp(ClipRadius, 1, 64);
@@ -1360,8 +1013,6 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
// Override only the temporary schema copy. Clipmap tuning on the owner's asset is preserved.
SettingsValue->AsObject()->SetNumberField(TEXT("ClipRadius"), CoreClipRadius);
SettingsValue->AsObject()->SetNumberField(TEXT("MaxClipLevel"), CoreMaxClipLevel);
SettingsValue->AsObject()->SetNumberField(TEXT("StrateLayoutChunkSizeMeters"),
static_cast<double>(StrateManager->GetStrateLayoutChunkSizeMicrometres()) / 1000000.0);
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
Root->SetNumberField(TEXT("schema_version"), 1);
@@ -1372,20 +1023,15 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0)));
Root->SetNumberField(TEXT("core_voxel_size_cm"), ResolvedCoreVoxelSizeCm);
Root->SetNumberField(TEXT("core_voxel_size_microns"), CoreVoxelSizeMicrons);
Root->SetNumberField(TEXT("owner_to_core_length_scale"),
OwnerVoxelSizeCm / ResolvedCoreVoxelSizeCm);
Root->SetNumberField(TEXT("seed"), Seed);
Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry);
Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius);
Root->SetNumberField(TEXT("world_radius_voxels"), WorldRadiusVoxels);
Root->SetNumberField(TEXT("edge_seal_thickness"), Settings->EdgeSealThickness);
Root->SetNumberField(TEXT("total_strates"), StrateManager->GetNumStrates());
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"),
LayoutChunkSizeMicrometres);
Root->SetNumberField(TEXT("inter_strate_gap_chunks"), Settings->InterStrateGapChunks);
Root->SetNumberField(TEXT("clip_radius"), CoreClipRadius);
Root->SetNumberField(TEXT("max_clip_level"), CoreMaxClipLevel);
Root->SetNumberField(TEXT("full_res_clip_levels"), Settings->FullResClipLevels);
@@ -1453,15 +1099,12 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
return false;
}
Root->SetArrayField(TEXT("density_samples"), {});
if(!Root->HasField(TEXT("viewer_start")))
{
TSharedRef<FJsonObject> ViewerStart = MakeShared<FJsonObject>();
ViewerStart->SetBoolField(TEXT("found_open_space"), false);
ViewerStart->SetNumberField(TEXT("x"), 0.0);
ViewerStart->SetNumberField(TEXT("y"), 0.0);
ViewerStart->SetNumberField(TEXT("z"), 0.0);
Root->SetObjectField(TEXT("viewer_start"), ViewerStart);
}
TSharedRef<FJsonObject> ViewerStart = MakeShared<FJsonObject>();
ViewerStart->SetBoolField(TEXT("found_open_space"), false);
ViewerStart->SetNumberField(TEXT("x"), 0.0);
ViewerStart->SetNumberField(TEXT("y"), 0.0);
ViewerStart->SetNumberField(TEXT("z"), 0.0);
Root->SetObjectField(TEXT("viewer_start"), ViewerStart);
FString WriterBuffer;
TSharedRef<FCoreJsonWriter> Writer = TJsonWriterFactory<TCHAR,
+107 -256
View File
@@ -13,7 +13,6 @@
#include "VoxelTerrainOpDefinition.h" // For UVoxelTerrainOpDefinition::ApplyTo
#include "VoxelBiomeDefinition.h" // For UVoxelBiomeDefinition (biome context flatten)
#include "HAL/IConsoleManager.h"
#include <cmath> // std::llround
#include "Misc/CommandLine.h"
#include "Misc/Crc.h"
#include "Misc/Parse.h"
@@ -25,27 +24,6 @@ namespace
{
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-
// preserving rewrites in this round are deliberately unconditional; this switch isolates
// the projection reuse without changing any density arithmetic.
@@ -343,7 +321,7 @@ namespace
const FVector ChunkCenter(
(ChunkCoord.X + 0.5f) * (float)CHUNK_SIZE,
(ChunkCoord.Y + 0.5f) * (float)CHUNK_SIZE,
(ChunkCoord.Z + 0.5f) * Manager->GetStrateLayoutChunkWidthVoxels());
(ChunkCoord.Z + 0.5f) * (float)CHUNK_SIZE);
const float ChunkRadius = (float)CHUNK_SIZE * 0.8660254f + 3.0f;
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
Cache.FloorProjections.SetNum(Passages.Num());
@@ -1125,7 +1103,7 @@ static bool VF_FindWalkableTunnelAir(
const FIntVector ChunkCoord(
FMath::FloorToInt(Position.X / (float)CHUNK_SIZE),
FMath::FloorToInt(Position.Y / (float)CHUNK_SIZE),
FMath::FloorToInt(Position.Z / Manager->GetStrateLayoutChunkWidthVoxels()));
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
const FVoxelPassage* PassageData = Passages.GetData();
@@ -1160,7 +1138,7 @@ static bool VF_IsAnyPassageFloorAt(
const FIntVector ChunkCoord(
FMath::FloorToInt(Position.X / (float)CHUNK_SIZE),
FMath::FloorToInt(Position.Y / (float)CHUNK_SIZE),
FMath::FloorToInt(Position.Z / Manager->GetStrateLayoutChunkWidthVoxels()));
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
const FVoxelPassage* PassageData = Passages.GetData();
@@ -1191,17 +1169,6 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
UE_LOG(LogTemp, Error, TEXT("[StrateManager] No settings provided!"));
return false;
}
if (!FMath::IsFinite(Settings->StrateLayoutChunkSizeMeters)
|| Settings->StrateLayoutChunkSizeMeters < 0.25f
|| Settings->StrateLayoutChunkSizeMeters > 64.0f)
{
UE_LOG(LogTemp, Error, TEXT("[StrateManager] StrateLayoutChunkSizeMeters must be in [0.25, 64] m."));
return false;
}
StrateLayoutChunkSizeMicrometres = static_cast<uint32>(std::llround(
static_cast<double>(Settings->StrateLayoutChunkSizeMeters) * 1000000.0));
StrateLayoutChunkWidthVoxels = static_cast<float>(
static_cast<double>(StrateLayoutChunkSizeMicrometres) / (10000.0 * VOXEL_SIZE));
FVoxelSeasonManifest SeasonManifest;
bool bUseSeason = false;
@@ -1322,11 +1289,8 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
}
}
// Resolve the stack in physical integer micrometres first. The historical chunk indices are
// projected from these half-open bounds only after each slot is complete.
int64 CurrentTopWorldMicrometres = StrateLayoutChunkSizeMicrometres;
const int64 ResolvedGapMicrometres = static_cast<int64>(LayoutGapChunks)
* StrateLayoutChunkSizeMicrometres;
// Current Z position (in chunks). Starts at 0 and goes downward (negative).
int32 CurrentTopZ = 0;
for (int32 i = 0; i < TotalStrates; i++)
{
@@ -1378,31 +1342,18 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
Slot.Definition->FloatingIslandParams = SeasonStrate.Params.FloatingIslandParams;
Slot.HeightInChunks = SeasonStrate.HeightInChunks;
const int32 LayoutChunkVoxels=FMath::RoundToInt(StrateLayoutChunkWidthVoxels);
Slot.TopChunkZ = SeasonStrate.TopWorldZ / LayoutChunkVoxels - 1;
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
|| SeasonStrate.BottomWorldZ % LayoutChunkVoxels != 0
|| Slot.TopChunkZ != ExpectedTopChunk
|| Slot.BottomChunkZ != ExpectedBottomChunk
|| Slot.TopWorldMicrometres != CurrentTopWorldMicrometres
|| Slot.HeightMicrometres != static_cast<int64>(Slot.HeightInChunks)
* StrateLayoutChunkSizeMicrometres)
Slot.TopChunkZ = SeasonStrate.TopWorldZ / CHUNK_SIZE - 1;
Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / CHUNK_SIZE;
if (SeasonStrate.TopWorldZ % CHUNK_SIZE != 0
|| SeasonStrate.BottomWorldZ % CHUNK_SIZE != 0
|| Slot.TopChunkZ != CurrentTopZ
|| Slot.BottomChunkZ != CurrentTopZ - (Slot.HeightInChunks - 1))
{
UE_LOG(LogTemp, Error,
TEXT("[StrateManager] Season slot %d bounds do not describe the declared stacked layout."), i);
RestorePreviousLayout();
return false;
}
CurrentTopWorldMicrometres = Slot.BottomWorldMicrometres - ResolvedGapMicrometres;
SeasonStrates.Add(SeasonStrate);
}
else
@@ -1425,50 +1376,15 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
continue;
}
// Resolve the authored chunk height into an exact physical interval.
// Compute Z range from definition's height
Slot.HeightInChunks = Slot.Definition->StrateHeightInChunks;
Slot.TopWorldMicrometres = CurrentTopWorldMicrometres;
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;
Slot.TopChunkZ = CurrentTopZ;
Slot.BottomChunkZ = CurrentTopZ - (Slot.HeightInChunks - 1);
}
Slot.ResolvedTransitionType = Slot.Definition->TransitionType;
Slot.ResolvedTransitionBlendChunks = Slot.Definition->TransitionBlendChunks;
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);
// Move the cursor down for the next strate, leaving a solid-bedrock gap of
// InterStrateGapChunks chunks between this strate and the next.
CurrentTopZ = Slot.BottomChunkZ - 1 - LayoutGapChunks;
StrateLayout.Add(Slot);
@@ -1501,7 +1417,6 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
OriginSpineRadius = bUseSeason
? SeasonManifest.OriginSpineRadius : Settings->OriginSpineRadius;
InterStrateGapChunks = LayoutGapChunks;
ResolvedInterStrateGapMicrometres = ResolvedGapMicrometres;
if (bUseSeason)
{
ActiveSeasonContentHash = SeasonManifest.ContentHash;
@@ -1610,8 +1525,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
}
VF_SetComposerRuntimeBounds(
Override->Params,
TargetSlot->LegacyTopWorldZVoxels,
TargetSlot->LegacyBottomWorldZVoxels);
(float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE,
(float)TargetSlot->BottomChunkZ * CHUNK_SIZE);
Override->bUseRecipe = bUseRecipe;
if (Recipe != nullptr)
{
@@ -1630,8 +1545,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
Override->bUseRegions = true;
Override->Regions = *InRegions;
VF_RekeyStrateRegionManifest(Override->Regions, CandidateSeed, StrateIndex);
Override->Regions.StrateTopWorldZ = TargetSlot->LegacyTopWorldZVoxels;
Override->Regions.StrateBottomWorldZ = TargetSlot->LegacyBottomWorldZVoxels;
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE;
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * CHUNK_SIZE;
VF_SetStrateArchetypeRuntimeBounds(Override->Params,
Override->Regions.StrateTopWorldZ,
Override->Regions.StrateBottomWorldZ);
@@ -1878,36 +1793,14 @@ void UVoxelStrateManager::GeneratePassages()
// that opens the next room rather than a second vertical spine.
const UVoxelStrateDefinition* UpperDef = Upper.Definition;
if (!UpperDef) continue;
// 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);
const FStratePassageConfig& Cfg = UpperDef->PassageConfig;
// Upper strate floor and lower strate ceiling (differ when there's a bedrock gap).
const float UpperTopZ = Upper.LegacyTopWorldZVoxels;
const float UpperBottomZ = Upper.LegacyBottomWorldZVoxels;
const float LowerTopZ = Lower.LegacyTopWorldZVoxels;
const float UpperMax = Upper.LegacyHeightVoxels * 0.9f;
const float LowerMax = Lower.LegacyHeightVoxels * 0.9f;
const float UpperTopZ = (float)(Upper.TopChunkZ + 1) * CHUNK_SIZE;
const float UpperBottomZ = (float)(Upper.BottomChunkZ) * CHUNK_SIZE;
const float LowerTopZ = (float)(Lower.TopChunkZ + 1) * CHUNK_SIZE;
const float UpperMax = (float)Upper.HeightInChunks * CHUNK_SIZE * 0.9f;
const float LowerMax = (float)Lower.HeightInChunks * CHUNK_SIZE * 0.9f;
const float DistLo = FMath::Min(Cfg.DistanceMin, Cfg.DistanceMax);
const float DistHi = FMath::Max(Cfg.DistanceMin, Cfg.DistanceMax);
@@ -2037,7 +1930,7 @@ void UVoxelStrateManager::GeneratePassages()
LowerDef->FloatingIslandParams,
LowerQuerySeed,
LowerTopZ,
Lower.LegacyBottomWorldZVoxels,
(float)(Lower.BottomChunkZ) * CHUNK_SIZE,
PX,
PY,
MaxLateralSnap,
@@ -2097,7 +1990,7 @@ void UVoxelStrateManager::GeneratePassages()
if (bAimedLowerAtPlayerFitPoint)
{
const float LowerSealThickness = BoundarySealThicknessFor(*LowerDef);
const float LowerBottomZ = Lower.LegacyBottomWorldZVoxels;
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * CHUNK_SIZE;
const float InnerBottomZ = LowerBottomZ + LowerSealThickness;
const float InnerTopZ = LowerTopZ - LowerSealThickness;
if (VoxelMath::IsFinite(SuggestedLowerPoint.X)
@@ -2265,7 +2158,7 @@ void UVoxelStrateManager::GeneratePassages()
Cfg.MouthRadius,
LowerDoorDirection,
LowerTopZ,
Lower.LegacyBottomWorldZVoxels,
(float)(Lower.BottomChunkZ) * CHUNK_SIZE,
LowerDef ? BoundarySealThicknessFor(*LowerDef) : 0.0f,
bAimedLowerAtPlayerFitPoint);
Passage.UpperPoint = Passage.UpperLanding.StandingPoint;
@@ -2690,8 +2583,8 @@ void UVoxelStrateManager::GeneratePassages()
if (bOpenSurfaceEntry && OriginSpineRadius > 0.0f && StrateLayout.Num() > 0)
{
const FStrateSlot& Top = StrateLayout[0];
const float TopZ = Top.LegacyTopWorldZVoxels;
const float BottomZ = Top.LegacyBottomWorldZVoxels;
const float TopZ = (float)(Top.TopChunkZ + 1) * CHUNK_SIZE;
const float BottomZ = (float)Top.BottomChunkZ * CHUNK_SIZE;
const float TopSeal = Top.Definition
? BoundarySealThicknessFor(*Top.Definition) : 0.0f;
const VoxelPassageGeometry::FOriginLandingGeometry OriginLanding =
@@ -2714,7 +2607,7 @@ void UVoxelStrateManager::GeneratePassages()
// the top seal into the finite landing room; lower strate seals remain untouched.
Entry.Radius = FMath::Max(OriginSpineRadius,
VoxelPassageGeometry::MetersToVoxels(0.25f));
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + StrateLayoutChunkWidthVoxels);
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + CHUNK_SIZE);
// The capsule's lower tangent meets the room ceiling; it never bores through the
// room's floor or creates an origin column in strate 0.
Entry.LowerPoint = FVector(
@@ -2778,7 +2671,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
// The shortlist is rebuilt once for a (manager, version, chunk) and is shared by the tube,
// landing, and floor paths. No source-fit stencil or topology search is allowed below it.
@@ -2924,7 +2817,7 @@ void UVoxelStrateManager::ApplyPassageModifier(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
@@ -3017,7 +2910,7 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0) return;
@@ -3128,7 +3021,7 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
@@ -3224,7 +3117,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0) return;
@@ -3351,7 +3244,7 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0
|| VoxelPassageGeometry::VerticalShaftConnectorAirMarker())
@@ -3542,7 +3435,7 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
@@ -6016,8 +5909,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
@@ -6059,8 +5952,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearBox(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
if (VoxelPassageGeometry::OriginLandingRoomTouchesBox(
VoxelBox, TopZ, BottomZ,
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
@@ -6080,8 +5973,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearBox(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
if (VoxelPassageGeometry::OriginLandingFloorTouchesBox(
VoxelBox, TopZ, BottomZ,
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
@@ -6105,8 +5998,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearLattice(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
if (VoxelPassageGeometry::OriginLandingRoomTouchesLattice(
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius,
@@ -6143,8 +6036,8 @@ bool UVoxelStrateManager::AnyOriginLandingAirNearLattice(
{
continue;
}
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
@@ -6220,8 +6113,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearLattice(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
if (VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius))
@@ -6238,36 +6131,15 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearLattice(
int32 UVoxelStrateManager::FindSlotIndexForChunkZ(int32 ChunkZ) const
{
// Legacy chunk callers use the projected interval cached during Initialize. The source bounds
// are the slot's physical micrometre interval; these indices do not define or stack strates.
for (int32 i = 0; i < StrateLayout.Num(); ++i)
// Linear search through strate layout.
// With ~10-20 strates this is fine. If we ever have hundreds,
// switch to binary search (layout is sorted by Z).
for (int32 i = 0; i < StrateLayout.Num(); i++)
{
const FStrateSlot& Slot = StrateLayout[i];
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)
{
const int32 Mid = Low + (High - Low) / 2;
const FStrateSlot& Slot = StrateLayout[Mid];
if (WorldZMicrometres >= static_cast<double>(Slot.TopWorldMicrometres))
if (ChunkZ <= Slot.TopChunkZ && ChunkZ >= Slot.BottomChunkZ)
{
High = Mid - 1;
}
else if (WorldZMicrometres < static_cast<double>(Slot.BottomWorldMicrometres))
{
Low = Mid + 1;
}
else
{
return Mid;
return i;
}
}
return -1;
@@ -6275,8 +6147,9 @@ int32 UVoxelStrateManager::FindSlotIndexForWorldZCentimetres(double WorldZCentim
UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
{
if (!FMath::IsFinite(WorldZ)) return nullptr;
const int32 SlotIdx = FindSlotIndexForWorldZCentimetres(WorldZ);
// Convert world Z to chunk Z coordinate
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (SlotIdx >= 0)
{
return StrateLayout[SlotIdx].Definition;
@@ -6286,8 +6159,8 @@ UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
int32 UVoxelStrateManager::GetStrateIndex(float WorldZ) const
{
if (!FMath::IsFinite(WorldZ)) return -1;
const int32 SlotIdx = FindSlotIndexForWorldZCentimetres(WorldZ);
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (SlotIdx >= 0)
{
return StrateLayout[SlotIdx].StrateIndex;
@@ -6400,13 +6273,9 @@ bool UVoxelStrateManager::IsGapChunk(const FIntVector& ChunkCoord) const
if (StrateLayout.Num() == 0) return false;
// Above the top strate or below the bottom strate = open air, NOT a gap.
const int64 ChunkCenterMicrometres = static_cast<int64>(ChunkCoord.Z)
* StrateLayoutChunkSizeMicrometres + StrateLayoutChunkSizeMicrometres / 2;
if (ChunkCenterMicrometres >= StrateLayout[0].TopWorldMicrometres
|| ChunkCenterMicrometres < StrateLayout.Last().BottomWorldMicrometres)
{
return false;
}
const int32 StackTop = StrateLayout[0].TopChunkZ;
const int32 StackBottom = StrateLayout.Last().BottomChunkZ;
if (ChunkCoord.Z > StackTop || ChunkCoord.Z < StackBottom) return false;
// Inside the stack's Z span but not in any strate slot → it's a bedrock gap.
return FindSlotIndexForChunkZ(ChunkCoord.Z) < 0;
@@ -6449,9 +6318,9 @@ FSlabGenerationParams UVoxelStrateManager::GetSlabParamsForChunk(const FIntVecto
// Fill in the runtime Z bounds (voxel coordinates, same convention as
// FStrateGenerationParams::StrateTopWorldZ / StrateBottomWorldZ).
// TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*StrateLayoutChunkWidthVoxels.
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
// TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*CHUNK_SIZE.
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
return Result;
}
@@ -6484,8 +6353,8 @@ FMazeGenerationParams UVoxelStrateManager::GetMazeParamsForChunk(const FIntVecto
}
}
#endif
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
return Result;
}
@@ -6511,8 +6380,8 @@ FSurfaceGenerationParams UVoxelStrateManager::GetSurfaceParamsForChunk(const FIn
}
}
#endif
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
return Result;
}
@@ -6538,8 +6407,8 @@ FVerticalShaftParams UVoxelStrateManager::GetVerticalShaftParamsForChunk(const F
}
}
#endif
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
return Result;
}
@@ -6565,8 +6434,8 @@ FFloatingIslandParams UVoxelStrateManager::GetFloatingIslandParamsForChunk(const
}
}
#endif
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
return Result;
}
@@ -6600,14 +6469,14 @@ FBiomeContext UVoxelStrateManager::GetBiomeContextForChunk(const FIntVector& Chu
bool UVoxelStrateManager::GetStrateUnrealZRange(float WorldZ, float& OutTopZ, float& OutBottomZ) const
{
if (!FMath::IsFinite(WorldZ)) return false;
const int32 SlotIdx = FindSlotIndexForWorldZCentimetres(WorldZ);
const int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (SlotIdx < 0) return false;
const FStrateSlot& Slot = StrateLayout[SlotIdx];
// Return the resolved physical bounds in Unreal centimeters.
OutTopZ = static_cast<float>(static_cast<double>(Slot.TopWorldMicrometres) / 10000.0);
OutBottomZ = static_cast<float>(static_cast<double>(Slot.BottomWorldMicrometres) / 10000.0);
// Voxel-space Z bounds → Unreal units. Ceiling = top chunk's upper edge.
OutTopZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE * VOXEL_SIZE;
OutBottomZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE * VOXEL_SIZE;
return true;
}
@@ -6620,8 +6489,8 @@ FStrateDisturbanceParams UVoxelStrateManager::GetDisturbanceParamsForChunk(const
}
const FStrateSlot& Slot = StrateLayout[SlotIdx];
FStrateDisturbanceParams Result = Slot.Definition->Disturbances;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
return Result;
}
@@ -6653,8 +6522,8 @@ float UVoxelStrateManager::GetWaterLevelWorldZForChunk(const FIntVector& ChunkCo
}
if (Rel <= 0.0f) return -FLT_MAX;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
const float TopZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
return FMath::Lerp(BottomZ, TopZ, FMath::Clamp(Rel, 0.0f, 1.0f));
}
@@ -6698,8 +6567,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
FindComposerOverride(Slot.StrateIndex))
{
FStrateGenerationParams Result = Override->Params.TunnelNetworkParams;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
// 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.
return ResolveFinalParams(Result);
@@ -6709,8 +6578,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (SeasonStrates.IsValidIndex(SlotIdx))
{
FStrateGenerationParams Result = SeasonStrates[SlotIdx].Params.TunnelNetworkParams;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
// Season vectors are already final, measured slot records. Vertical structural posts own
// their boundary; blending them with another selected recipe would describe neither one.
return ResolveFinalParams(Result);
@@ -6727,17 +6596,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
//
// 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
BaseParams.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
BaseParams.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
// Core currently dispatches one generator field per resolved strate. Gradient and
// interleaved metadata therefore use the explicitly declared Hard fallback for every
// legacy parameter lookup as well; mixing parameter vectors here would disagree with the
// shared ceiling/content/bedrock composition layer.
if (bHardTransitionFallbackForAssembly)
{
return ResolveFinalParams(BaseParams);
}
BaseParams.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
BaseParams.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
//=========================================================================
// BOUNDARY BLENDING (transition-type aware)
@@ -6775,23 +6635,18 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
//---------------------------------------------------------------------
// CHECK BOTTOM BOUNDARY (transitioning to strate below)
//---------------------------------------------------------------------
// 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.
// DistFromBottom = how many chunks above the bottom edge of this strate.
// When 0, we're right at the boundary. When == TransitionBlendChunks, we're at
// the outer edge of the transition zone.
const int64 DistFromBottomMicrometres = static_cast<int64>(ChunkCoord.Z)
* StrateLayoutChunkSizeMicrometres - Slot.BottomWorldMicrometres;
const int32 DistFromBottom = static_cast<int32>(VF_FloorDivide64(
DistFromBottomMicrometres, StrateLayoutChunkSizeMicrometres));
int32 DistFromBottom = ChunkCoord.Z - Slot.BottomChunkZ;
if (SlotIdx + 1 < StrateLayout.Num())
{
// The upper strate (this one) controls the transition type at its lower edge
const EVoxelStrateTransition TransType = Slot.ResolvedTransitionType;
const EVoxelStrateTransition TransType = Slot.Definition->TransitionType;
// Per-definition blend distance.
const int32 EffectiveBlend = static_cast<int32>(VF_FloorDivide64(
Slot.TransitionWidthMicrometres, StrateLayoutChunkSizeMicrometres));
const int32 EffectiveBlend = Slot.Definition->TransitionBlendChunks;
// Prepare the neighbor's params (only used for Gradient and Interleaved)
const FStrateSlot& BelowSlot = StrateLayout[SlotIdx + 1];
@@ -6815,8 +6670,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (EffectiveBlend > 0 && DistFromBottom < EffectiveBlend)
{
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
BelowParams.StrateTopWorldZ = BelowSlot.LegacyTopWorldZVoxels;
BelowParams.StrateBottomWorldZ = BelowSlot.LegacyBottomWorldZVoxels;
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * CHUNK_SIZE;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * CHUNK_SIZE;
// Linear alpha: 0 at EffectiveBlend chunks away, 1 at the boundary
float Alpha = 1.0f - ((float)DistFromBottom / (float)EffectiveBlend);
@@ -6853,8 +6708,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (DistFromBottom < CheckRange)
{
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
BelowParams.StrateTopWorldZ = BelowSlot.LegacyTopWorldZVoxels;
BelowParams.StrateBottomWorldZ = BelowSlot.LegacyBottomWorldZVoxels;
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * CHUNK_SIZE;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * CHUNK_SIZE;
// Sample 3D Perlin noise to warp the boundary position.
// XY frequency 0.15 gives medium-scale variation (~6-7 chunks per cycle).
@@ -6892,18 +6747,14 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
//---------------------------------------------------------------------
// 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].
const int64 DistFromTopMicrometres = Slot.TopWorldMicrometres
- (static_cast<int64>(ChunkCoord.Z) + 1) * StrateLayoutChunkSizeMicrometres;
const int32 DistFromTop = static_cast<int32>(VF_FloorDivide64(
DistFromTopMicrometres, StrateLayoutChunkSizeMicrometres));
int32 DistFromTop = Slot.TopChunkZ - ChunkCoord.Z;
if (SlotIdx > 0)
{
// The strate ABOVE controls the transition at its lower edge (= our upper edge)
const FStrateSlot& AboveSlot = StrateLayout[SlotIdx - 1];
const EVoxelStrateTransition TransType = AboveSlot.ResolvedTransitionType;
const int32 EffectiveBlend = static_cast<int32>(VF_FloorDivide64(
AboveSlot.TransitionWidthMicrometres, StrateLayoutChunkSizeMicrometres));
const EVoxelStrateTransition TransType = AboveSlot.Definition->TransitionType;
const int32 EffectiveBlend = AboveSlot.Definition->TransitionBlendChunks;
switch (TransType)
{
@@ -6918,8 +6769,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (EffectiveBlend > 0 && DistFromTop < EffectiveBlend)
{
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
AboveParams.StrateTopWorldZ = AboveSlot.LegacyTopWorldZVoxels;
AboveParams.StrateBottomWorldZ = AboveSlot.LegacyBottomWorldZVoxels;
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * CHUNK_SIZE;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * CHUNK_SIZE;
float Alpha = 1.0f - ((float)DistFromTop / (float)EffectiveBlend);
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
@@ -6940,8 +6791,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (DistFromTop < CheckRange)
{
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
AboveParams.StrateTopWorldZ = AboveSlot.LegacyTopWorldZVoxels;
AboveParams.StrateBottomWorldZ = AboveSlot.LegacyBottomWorldZVoxels;
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * CHUNK_SIZE;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * CHUNK_SIZE;
// Same noise function but with a different seed offset to avoid
// symmetry between top and bottom boundaries of adjacent strates
-6
View File
@@ -7771,12 +7771,6 @@ void AVoxelWorld::FallBackToLegacyAfterCoreInitializationFailure(const FString&
VoxelForgeCoreWorldAssemblyJson.Reset();
VoxelForgeCoreWorldAssemblyHash.Reset();
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;
if (Mesher != nullptr)
{
@@ -41,27 +41,21 @@
// SealFactor > 0 et BaseDensity > 0, le résultat est solide GARANTI quelle qu'ait été l'entrée.
// C'est exactement ce qu'encode `IVoxelDensityOp::ClassifyBox` (voir VoxelDensityOp.h), et la
// raison pour laquelle cette méthode existe.
FORCEINLINE void VF_ApplyUpperBoundarySealValue(float& Density, float WorldZ,
float StrateTopZ, float Thickness, float BaseDensity)
{
const float DistTop = StrateTopZ - WorldZ;
if (DistTop >= 0.0f && DistTop < Thickness)
{
float SealFactor = 1.0f - (DistTop / Thickness);
SealFactor = SmoothStep01(SealFactor);
Density = FMath::Max(Density, SealFactor * BaseDensity);
}
}
FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
float StrateTopZ, float StrateBottomZ,
float Thickness, float BaseDensity)
{
if (VoxelDensityAblation::IsBoundarySealOff() || Thickness <= 0.0f) return;
const float DistTop = StrateTopZ - WorldZ; // + si on est sous le plafond
const float DistBot = WorldZ - StrateBottomZ; // + si on est au-dessus du sol
VF_ApplyUpperBoundarySealValue(Density, WorldZ, StrateTopZ, Thickness, BaseDensity);
if (DistTop >= 0.0f && DistTop < Thickness)
{
float SealFactor = 1.0f - (DistTop / Thickness);
SealFactor = SmoothStep01(SealFactor);
Density = FMath::Max(Density, SealFactor * BaseDensity);
}
if (DistBot >= 0.0f && DistBot < Thickness)
{
float SealFactor = 1.0f - (DistBot / Thickness);
@@ -70,16 +64,6 @@ FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
}
}
// The same structural seal, limited to a strate's upper boundary. Native finite fields use this
// when their lower boundary intentionally has no seal; keeping the math here makes that ceiling
// use the same shared density primitive as every two-sided strate seal.
FORCEINLINE void VF_ApplyUpperBoundarySeal(float& Density, float WorldZ,
float StrateTopZ, float Thickness, float BaseDensity)
{
if (VoxelDensityAblation::IsBoundarySealOff() || Thickness <= 0.0f) return;
VF_ApplyUpperBoundarySealValue(Density, WorldZ, StrateTopZ, Thickness, BaseDensity);
}
//=============================================================================
// CARVE DE PASSAGE / PASSAGE CARVING
//=============================================================================
-7
View File
@@ -419,13 +419,6 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (ClampMin = "0", ClampMax = "16"))
int32 InterStrateGapChunks = 0;
// Physical width of one vertical strate-layout chunk. This is independent of the
// Core tile width (32^3 voxels) and of the source generator's voxel resolution.
// 8 m preserves the owner's current SurfaceWorld extent at 25 cm Core voxels.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates|World Assembly",
meta = (ClampMin = "0.25", ClampMax = "64.0", Units = "m"))
float StrateLayoutChunkSizeMeters = 8.0f;
//=========================================================================
// SPINE & INTER-STRATE CONNECTIONS (the (0,0) descent axis)
//=========================================================================
+4 -49
View File
@@ -121,8 +121,8 @@ struct FVoxelPassage
* FStrateSlot — Runtime info for one strate in the layout.
*
* Created during Initialize() and stored in the StrateLayout array.
* Each slot owns its resolved half-open physical Z interval. Chunk coordinates and legacy
* authoring values below are projections kept for existing callers and assets.
* Each slot knows its definition, its Z-range in chunk coordinates,
* and its index in the sequence.
*/
USTRUCT()
struct FStrateSlot
@@ -136,33 +136,6 @@ struct FStrateSlot
// Strate index (0 = topmost, increases downward)
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.
// Example: TopZ = 0, BottomZ = -3 means this strate spans chunks Z=0 to Z=-3
int32 TopChunkZ = 0;
@@ -271,17 +244,6 @@ public:
UFUNCTION(BlueprintCallable, Category = "Strate")
int32 GetStrateIndex(float WorldZ) const;
// Director-resolved physical strate chunk width expressed in owner voxel units.
// This is independent of Core's 32^3 tile width.
float GetStrateLayoutChunkWidthVoxels() const { return StrateLayoutChunkWidthVoxels; }
uint32 GetStrateLayoutChunkSizeMicrometres() const { return StrateLayoutChunkSizeMicrometres; }
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; }
/** Layout/passage generation counter (bumped by every Initialize → GeneratePassages).
* Hot-path callers key thread_local memos on this so an editor rebuild (RebuildStrates /
* live edit) can never serve a stale strate index or passage shortlist. */
@@ -559,15 +521,9 @@ protected:
// Copied from VoxelSettings::OriginSpineRadius.
float OriginSpineRadius = 28.0f;
// Compatibility gap projection, in layout chunks. Physical authority is the micrometre value
// resolved during Initialize below.
// Solid-bedrock gap between consecutive strates, in chunks. Copied from
// VoxelSettings::InterStrateGapChunks during Initialize().
int32 InterStrateGapChunks = 0;
int64 ResolvedInterStrateGapMicrometres = 0;
// Resolved once from VoxelSettings::StrateLayoutChunkSizeMeters during Initialize().
float StrateLayoutChunkWidthVoxels = 32.0f;
uint32 StrateLayoutChunkSizeMicrometres = 8000000;
mutable bool bHardTransitionFallbackForAssembly = false;
// 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
@@ -584,7 +540,6 @@ protected:
// Find which slot a chunk Z coordinate falls into.
// Returns INDEX into StrateLayout, or -1 if not found.
int32 FindSlotIndexForChunkZ(int32 ChunkZ) const;
int32 FindSlotIndexForWorldZCentimetres(double WorldZCentimetres) const;
#if WITH_EDITOR
const FVoxelStrateComposerSlotOverride* FindComposerOverride(int32 StrateIndex) const;
+25 -20
View File
@@ -201,19 +201,15 @@ enum class ECaveGeneratorType : uint8
};
/**
* EVoxelStrateTransition — How two adjacent strates meet at their shared boundary.
* EVoxelStrateTransition — How two adjacent strates blend at their shared boundary.
*
* 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
* itself and the strate below it.
*
* In the Core composition path, TransitionBlendChunks defines the full permitted
* crossing depth between the upper bedrock and lower ceiling bands. Each band keeps
* 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.
* Gradient: Smooth linear interpolation of generation params across TransitionBlendChunks.
* This is the classic approach — no seam visible, but also no dramatic
* geological feature at the boundary. Best for similar-looking strates.
*
* 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
@@ -221,21 +217,26 @@ enum class ECaveGeneratorType : uint8
* discontinuity at the boundary. Best for strates that should feel like
* distinct geological layers with a visible dividing line.
*
* Interleaved: A deterministic 3D domain-warped hash field sets the overlap depth per XY
* column from zero to the declared width, creating irregular fingers.
* Best for strates that should feel grown together.
* Interleaved: The boundary position is warped by a 3D noise field, so "fingers"
* of one strate reach into the other. Some XY columns transition early,
* others late, creating an irregular, interlocking boundary. The warp
* 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)
enum class EVoxelStrateTransition : uint8
{
// Smoothly undulates the shared bedrock/ceiling interface in the Core composition path.
// Smooth linear interpolation of all generation params across the blend zone.
// No visible boundary — params change gradually over TransitionBlendChunks distance.
Gradient UMETA(DisplayName = "Gradient (smooth blend)"),
// No blending — params switch instantly at the boundary.
// Creates natural cliffs, ledges, or material discontinuities.
Hard UMETA(DisplayName = "Hard (sharp boundary with cliff)"),
// A 3D domain-warped hash field creates finger-like intrusions within the declared width.
// 3D noise warps the boundary position per XY column.
// Creates finger-like intrusions of one strate into the other.
Interleaved UMETA(DisplayName = "Interleaved (domain-warped fingers)")
};
@@ -1851,6 +1852,8 @@ struct VOXELFORGE_API FStrateDisturbanceParams
UENUM(BlueprintType)
enum class EVoxelPassageStyle : uint8
{
// “Straight” is retained as the authored style name for the base connection, but its runtime
// geometry is a gentle two-leg switchback rather than a vertical shaft.
Straight UMETA(DisplayName = "Straight (walkable sloped tunnel)"),
Worm UMETA(DisplayName = "Worm (organic meander around the axis)"),
Spiral UMETA(DisplayName = "Spiral (corkscrew descent)"),
@@ -1875,12 +1878,14 @@ struct VOXELFORGE_API FStratePassageConfig
// Tunnel shape.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage")
EVoxelPassageStyle Style = EVoxelPassageStyle::Worm;
EVoxelPassageStyle Style = EVoxelPassageStyle::Straight;
// ----- WIDTH (tapers along the length) -----
// Radius at the two mouths (entry/exit) and at the middle. Equal = uniform tube;
// Core raises both radii to at least 12 owner voxels (1.5 m) for capsule clearance.
// The passage landing remains wider for turning.
// The walkable default is 1.5x the original comfortable profile: 2.25 m diameter at mouths
// and 1.875 m at the waist when the stored radii remain 9/7.5 voxels at 12.5 cm per voxel.
// Double these asset values to retain the previous 4.5/3.75 m dimensions. The §6.5 landing
// room remains wider for turning.
// Mouth > Mid = chambers at the ends with a squeeze between; Mid > Mouth = a bulge.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Width", meta = (ClampMin = "1.0"))
float MouthRadius = 9.0f;
@@ -1907,8 +1912,8 @@ struct VOXELFORGE_API FStratePassageConfig
float DistanceMax = 192.0f;
// ----- SHAPE detail -----
// Max sideways variation from the winding path's axis (voxels). Core keeps the route inside
// DistanceMax and adds enough distance to meet the walkable grade.
// Exotic styles only: max sideways excursion from the axis (voxels). 24 = 6 m; 0 = straight
// even in Worm style. The default walkable switchback does not use this wander value.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Shape", meta = (ClampMin = "0.0"))
float Wander = 24.0f;
@@ -1921,8 +1926,8 @@ struct VOXELFORGE_API FStratePassageConfig
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Shape", meta = (ClampMin = "0.0"))
float VerticalWobble = 0.0f;
// Spiral style: helix radius (voxels) and minimum number of turns. Core adds turns when
// needed to meet the walkable grade. The default stored radius is 3 m at the legacy scale.
// Spiral style: helix radius (voxels) and number of full turns over the descent. The default
// radius is 6 m.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Spiral", meta = (ClampMin = "1.0"))
float SpiralRadius = 24.0f;
+2 -22
View File
@@ -7,8 +7,6 @@ fi
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$SCRIPT_DIR"
PROJECT_ROOT="$(cd -- "$ROOT/.." && pwd)"
VERIFY_DIR="$ROOT/Tools/verify"
COMPILER="clang++-19"
VARIANT="clang19"
FP_CONTRACT="off"
@@ -38,20 +36,10 @@ command -v ar >/dev/null || { printf 'ar not found\n' >&2; exit 2; }
command -v pkg-config >/dev/null || { printf 'pkg-config not found\n' >&2; exit 2; }
pkg-config --exists egl glesv2 libpng || { printf 'EGL, GLESv2, and libpng development files are required\n' >&2; exit 2; }
BUILD_ROOT="${VOXELFORGE_BUILD_ROOT:-$ROOT/Build}"
BIN_DIR="${VOXELFORGE_BIN_DIR:-$ROOT/bin/linux}"
BUILD_DIR="$BUILD_ROOT/linux-$VARIANT"
BUILD_DIR="$ROOT/Build/linux-$VARIANT"
BIN_DIR="$ROOT/bin/linux"
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=(
"-I$ROOT/Core/include"
"-I$ROOT/Core/src"
@@ -63,7 +51,6 @@ INCLUDES=(
FLAGS=(
-std=c++20 -O2 -mavx2 -mfma -fno-fast-math
-fexceptions -pthread "${INCLUDES[@]}"
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\""
)
if [[ "$FP_CONTRACT" != "default" ]]; then
FLAGS+=("-ffp-contract=$FP_CONTRACT")
@@ -134,15 +121,8 @@ OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT"
"$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT"
CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT"
"$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" -I"$ROOT/Source" \
"$ROOT/Source/LinuxOffscreenCapture.cpp" -o "$CAPTURE_OUTPUT" \
$(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 EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT"
printf 'Compiler: %s | variant: %s | fp-contract: %s | jobs: %s\n' "$COMPILER" "$VARIANT" "$FP_CONTRACT" "$JOBS"
+2 -7
View File
@@ -153,13 +153,8 @@ radius range, top flattening, taper, bounded contour warp, roughness, blend, con
route constraints. `PlanFloatingIslandCluster` emits stable island and connector IDs, a connected
cluster graph, conservative influence AABBs, and a centerline route. At least the primary chain is
always planned; `islandDensityQ16` controls up to two optional side sites. Connectors carry a sharp-tag
ready bit for a later hybrid-mesher adapter. The schema accepts geometry versions 1 through 3.
Version 3 keeps the version-2 island form, removes the all-XY lower seal, and obtains its ceiling from
the shared upper-strate boundary seal. The resolved upper bound comes from the strate's own chunk
range; `boundarySealSubvoxels` sets the shared seal thickness and remains a planning margin for island
features. It does not define a FloatingIslands-specific ceiling height. Versions 1 and 2 retain their
recorded two-sided boundary seals. Manifests without a geometry version retain version-1 evaluation
and canonical hashes.
ready bit for a later hybrid-mesher adapter. The schema accepts geometry version 1 and 2. Manifests
without a geometry version retain version-1 evaluation and canonical hashes.
`FloatingIslandFieldV1::densitySubvoxels` evaluates the canonical island and connector field.
`interval` returns unknown for boxes overlapping any influence AABB and a certified all-air range
@@ -1,55 +0,0 @@
#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;
}
}
@@ -1,180 +0,0 @@
#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,12 +1,9 @@
#pragma once
#include "VoxelForgeCore/CpuCycleCounter.h"
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <chrono>
#include <cstring>
#include <memory>
#include <thread>
@@ -15,23 +12,6 @@
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:
// the same recipe/plan column has the same bits at every sampling step.
struct FColumnCacheKey
@@ -89,28 +69,13 @@ namespace VoxelForgeCoreShared
struct FSpinGuard
{
explicit FSpinGuard(std::atomic_flag& value, FColumnCacheMetrics* metrics,
bool sampleCycles)
: Flag(value), Metrics(metrics)
explicit FSpinGuard(std::atomic_flag& value) : Flag(value)
{
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))
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); }
std::atomic_flag& Flag;
FColumnCacheMetrics* Metrics;
};
public:
@@ -129,7 +94,7 @@ namespace VoxelForgeCoreShared
template <typename TFactory>
TValue getOrCompute(const FColumnCacheKey& key, TFactory&& factory,
bool& computed, FColumnCacheMetrics* metrics = nullptr)
bool& computed)
{
computed = false;
const FKeyWords keyWords = key.words();
@@ -137,47 +102,13 @@ namespace VoxelForgeCoreShared
const std::size_t setIndex = static_cast<std::size_t>(tag) & (SetCount - 1);
FSet& set = Sets[setIndex];
TValue 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;
if (TryReadSet(set, keyWords, tag, value)) return value;
FSpinGuard lock(set.Writer, metrics,sampleCycles);
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;
}
FSpinGuard lock(set.Writer);
if (TryReadSet(set, keyWords, tag, value)) return value;
const std::uint64_t buildStart=sampleCycles?ReadCpuCycles():0;
value = std::forward<TFactory>(factory)();
if(sampleCycles)
{
metrics->sampledBuildCycles+=AdjustedCpuCycleDelta(buildStart,ReadCpuCycles());
++metrics->sampledBuilds;
}
computed = true;
if (metrics) ++metrics->computes;
FValueWords valueWords{};
std::memcpy(valueWords.data(), &value, sizeof(TValue));
@@ -187,7 +118,6 @@ namespace VoxelForgeCoreShared
{ replacement = way; break; }
if (replacement == Ways)
{
if (metrics) ++metrics->conflictReplacements;
replacement = set.NextWay;
set.NextWay = static_cast<std::uint8_t>((set.NextWay + 1) % Ways);
}
@@ -81,9 +81,6 @@ namespace VoxelForgeCore
Vec3d normal;
Vec2f uv;
ColorRgba8 color;
std::uint16_t hostMaterialId = 1;
std::array<std::uint16_t, 2> resourceMaterialIds{};
std::array<std::uint16_t, 2> resourceWeightsQ16{};
};
struct MeshGroups
@@ -139,14 +136,6 @@ namespace VoxelForgeCore
std::uint64_t columnCacheConflictMisses = 0;
std::uint64_t sharedColumnCacheHits = 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 uniformBlockProofSamplesChecked = 0;
std::uint64_t uniformBlockProofFallbackBlocks = 0;
@@ -168,30 +157,12 @@ namespace VoxelForgeCore
double marchingCubesSkirtMilliseconds = 0.0;
double marchingCubesOtherMilliseconds = 0.0;
std::uint64_t marchingCubesGradientFallbackDensityCalls = 0;
std::uint64_t marchingCubesGradientCalls = 0;
std::uint64_t marchingCubesEdgeMidpointDensityCalls = 0;
std::uint64_t marchingCubesEdgeMidpointDensityMemoHits = 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 capRefinementExclusiveMilliseconds = 0.0;
double meshAssemblyMilliseconds = 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 farShellMilliseconds = 0.0;
double farStratumShellMilliseconds = 0.0;
@@ -245,9 +216,7 @@ namespace VoxelForgeCore
std::uint64_t densitySampleCount = 0;
std::uint64_t meshPayloadAllocatedBytes = 0;
std::uint64_t densityHash = 0, meshHash = 0, payloadHash = 0, topologyHash = 0;
std::uint64_t materialHash = 0;
bool hasDensityHash = false, hasMeshHash = false, hasPayloadHash = false,
hasTopologyHash = false, hasMaterialHash = false;
bool hasDensityHash = false, hasMeshHash = false, hasPayloadHash = false, hasTopologyHash = false;
};
struct WorldConfiguration
@@ -279,128 +248,9 @@ namespace VoxelForgeCore
// This is independent of tile size and is the fixed input scale for Core field blocks.
// 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::uint32_t WorldAssemblySchemaVersion = 2;
// Version 14 adds shared passage columns; version 16 smooths owner Gradient faces;
// 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;
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
// Version 7 fixes owner/Core chunk mapping and native adjacent-stratum boundaries.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 7;
enum class FieldChannel : std::uint32_t
{
@@ -447,66 +297,11 @@ namespace VoxelForgeCore
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
{
std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
std::uint32_t evaluatorVersion = WorldAssemblyEvaluatorVersion;
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
// Resolved physical strate chunk width in 1/256 Core voxel units; tileVoxelCount remains 32.
std::int64_t layoutChunkWidthSubvoxels = 8192;
std::uint32_t seed = 0;
std::int64_t biomeCellSizeSubvoxels = 256 * 800;
std::int64_t biomeWarpSubvoxels = 256 * 250;
@@ -517,7 +312,6 @@ namespace VoxelForgeCore
std::int64_t biomeClimateReliefWavelengthSubvoxels = 256 * 3333;
std::int64_t biomeClimateMoistureWavelengthSubvoxels = 256 * 2500;
std::int64_t biomeClimateReliefContrastQ16 = 2ll << 16;
WorldAssemblySurfaceProfileV1 surfaceProfile;
std::vector<WorldAssemblyBiomeProfileV1> biomes;
std::uint64_t contentHash = 0;
};
@@ -577,7 +371,6 @@ namespace VoxelForgeCore
std::int64_t capRidgeWavelengthSubvoxels = 256 * 64;
std::int64_t waterSurfaceSubvoxels = 0;
bool waterEnabled = false;
WorldAssemblySurfaceProfileV1 surfaceProfile;
std::vector<WorldAssemblyFeatureV1> features;
WorldAssemblyRouteConstraintsV1 routeConstraints;
std::vector<WorldAssemblyRoutePointV1> route;
@@ -599,12 +392,7 @@ namespace VoxelForgeCore
std::uint32_t schemaVersion = 1;
// Profile 1 preserves the original shallow pads. Profile 2 adds a thick crust,
// bounded lobed roots and arched spans without changing the feature-family schema.
// Version 3 keeps the version-2 island shapes, removes the all-XY lower boundary seal,
// and routes its upper seal through the shared strate-boundary primitive. Version 4
// records the director-resolved strate layout width. Version 5 adds one passage-rooted
// island whose root reaches the owned bedrock and whose surface route ends at its mouth.
// New production adapters opt in explicitly; the compatibility default remains version 4.
std::uint32_t geometryVersion = 4;
std::uint32_t geometryVersion = 2;
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
// Import scale is explicit in microns; the reduced ratio maps owner-authored lengths to Core voxels.
// L'echelle d'import est explicite en microns; le rapport reduit convertit les longueurs owner.
@@ -618,9 +406,6 @@ namespace VoxelForgeCore
std::int64_t layoutCenterTopZSubvoxels = -64 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t layoutMinimumZSubvoxels = -128 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t layoutMaximumZSubvoxels = 0;
// Director-resolved physical strate layout chunk in 1/256 Core voxel units.
// This is independent of the internal 32-voxel Core tile.
std::int64_t layoutChunkWidthSubvoxels = 8192;
std::uint32_t primaryIslandCount = 3;
std::int64_t islandDensityQ16 = 1ll << 15;
// Legacy owner lengths use ownerVoxelSizeMicrons; the scale ratio converts them to Core voxels.
@@ -641,16 +426,6 @@ namespace VoxelForgeCore
std::int64_t boundarySealSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t bridgeHalfWidthSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t bridgeThicknessSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel;
// Version 5 anchor-island inputs are resolved from that strate's passage config.
// The anchor is the last primary route island and must stay inside the passage disk.
std::uint32_t anchorIslandOrdinal = 2;
std::int64_t anchorPlacementRadiusSubvoxels = 0;
std::int64_t anchorRootRadiusSubvoxels = 0;
std::int64_t anchorIslandMinimumRadiusSubvoxels = 0;
std::int64_t anchorRootBottomZSubvoxels = 0;
std::int64_t anchorMouthOffsetSubvoxels = 0;
// Style-zero mouths follow the sampled island surface into the passage lip.
bool terrainFollowingRoute = false;
WorldAssemblyRouteConstraintsV1 routeConstraints;
std::uint64_t contentHash = 0;
};
@@ -754,8 +529,7 @@ namespace VoxelForgeCore
std::uint64_t contentHash = 0;
static std::optional<FloatingIslandManifestV1> FromJson(
std::string_view json, std::string& error,
std::int64_t resolvedLayoutChunkWidthSubvoxels = 0);
std::string_view json, std::string& error);
bool finalize(std::string& error);
bool validate(std::string& error) const;
std::string ToJson() const;
@@ -787,438 +561,6 @@ namespace VoxelForgeCore
std::string ToJson() const;
};
// Read-only, versioned account of the physical layout resolved by the director and the
// Core adapter. Coordinates are half-open micrometre intervals; recipe anchors retain their
// source fixed-point units so this record never rounds the recipe itself.
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 };
// 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
{
std::string kind;
std::string stableId;
std::string units;
std::vector<std::int64_t> values;
};
struct ResolvedLayoutBandV2
{
std::string kind; // ceiling, content, bedrock
std::int64_t bottomMicrometres = 0;
std::int64_t topMicrometres = 0;
bool presentInCurrentField = false;
bool diggable = true;
bool protectedBand = false;
std::string behaviour;
std::string densitySide;
std::string capPath;
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
{
std::string stableId;
std::int32_t order = 0;
std::int32_t strateIndex = -1;
std::string generatorKind;
std::string recipeId;
std::uint32_t recipeVersion = 0;
std::int64_t interiorBottomMicrometres = 0;
std::int64_t interiorTopMicrometres = 0;
std::uint32_t voxelSizeMicrometres = 0;
std::array<std::int64_t, 3> latticeOriginMicrometres{};
ResolvedLayoutBandV2 ceiling;
ResolvedLayoutBandV2 content;
ResolvedLayoutBandV2 bedrock;
std::string transitionMode; // mode at this strate's lower interface
std::int32_t transitionBlendChunks = 0;
std::int64_t transitionWidthMicrometres = 0;
std::int64_t transitionWarpAmplitudeMicrometres = 0;
std::int64_t transitionInterpenetrationMicrometres = 0;
bool transitionHonouredInCore = false;
bool transitionFallsBackToHard = false;
std::string transitionDispatchPath;
std::string boundDispatchPath;
std::string queryDispatchPath;
std::string managerLookup;
std::int64_t managerChunkWidthMicrometres = 0;
std::int64_t dispatchChunkWidthMicrometres = 0;
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
{
std::string code;
std::string detail;
};
struct ResolvedLayoutPassagePointV1
{
std::array<std::int64_t, 3> positionSubvoxels{};
std::int64_t radiusSubvoxels = 0;
};
struct ResolvedLayoutPassageV1
{
std::string stableId;
std::int32_t upperStrateIndex = -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::array<std::int64_t, 3> upperMouthSubvoxels{};
std::array<std::int64_t, 3> lowerMouthSubvoxels{};
std::int64_t mouthRadiusSubvoxels = 0;
std::int64_t midRadiusSubvoxels = 0;
// Derived from the mouth binding; the main descent is monotone through this point.
std::size_t lowerMouthControlIndex = 0;
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
};
struct PassageVoidCrossingAuditV1
{
std::size_t passageIndex = 0;
std::size_t firstSegment = 0;
std::size_t lastSegment = 0;
std::uint64_t unsupportedSamples = 0;
double unsupportedLengthMetres = 0.0;
std::array<double, 4> startPointSubvoxels{};
std::array<double, 4> endPointSubvoxels{};
bool needsColumn = false;
bool connectedAtStart = false;
bool connectedAtEnd = false;
std::array<std::int64_t, 2> columnAxisSubvoxels{};
std::int64_t columnTopZSubvoxels = 0;
std::int64_t columnBottomZSubvoxels = 0;
std::int64_t columnMinimumRadiusSubvoxels = 0;
std::int64_t columnNominalRadiusSubvoxels = 0;
std::int64_t columnMaximumRadiusSubvoxels = 0;
double maximumRouteOffsetMetres = 0.0;
double maximumBoreRadiusMetres = 0.0;
};
struct ResolvedLayoutPassageConfigV1
{
std::int32_t upperStrateIndex = -1;
std::int32_t connections = 1;
// EVoxelPassageStyle numeric values: Straight=0, Worm=1, Spiral=2, Cascading=3.
std::uint32_t style = 1;
std::int64_t mouthRadiusSubvoxels = 0;
std::int64_t midRadiusSubvoxels = 0;
std::int64_t reachMinimumSubvoxels = 0;
std::int64_t reachMaximumSubvoxels = 0;
std::int64_t distanceMinimumSubvoxels = 0;
std::int64_t distanceMaximumSubvoxels = 0;
std::int64_t wanderSubvoxels = 0;
std::uint32_t segments = 20;
std::int64_t verticalWobbleSubvoxels = 0;
std::int64_t spiralRadiusSubvoxels = 0;
std::int64_t spiralTurnsQ16 = 2ll << 16;
std::uint32_t cascadeSteps = 4;
std::int64_t cascadeLedgeSubvoxels = 0;
};
struct VFCORE_API ResolvedLayoutSnapshotV3
{
std::uint32_t schemaVersion = ResolvedLayoutSchemaVersionV3;
std::string worldIdentity;
std::uint32_t ownerVoxelSizeMicrometres = 0;
std::uint32_t coreVoxelSizeMicrometres = 0;
std::uint32_t strateLayoutChunkSizeMicrometres = 8000000;
std::int64_t strateLayoutChunkWidthSubvoxels = 8192;
std::vector<ResolvedLayoutStratumV2> strates;
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;
bool finalize(std::string& error);
bool validate(ResolvedLayoutValidationModeV3 mode,
std::vector<ResolvedLayoutIssueV2>& issues,
std::string& error) const;
std::string ToJson() const;
static std::optional<ResolvedLayoutSnapshotV3> FromJson(
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
{
std::uint32_t controlsRun = 0;
std::uint32_t controlsRejectedAsExpected = 0;
bool passed = false;
std::vector<ResolvedLayoutIssueV2> failures;
};
VFCORE_API ResolvedLayoutValidatorAuditV3 AuditResolvedLayoutValidatorV3();
VFCORE_API bool PlanResolvedLayoutPassagesV1(
ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
const std::vector<ResolvedLayoutPassageConfigV1>& configs,
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
{
std::uint64_t boxesTested = 0;
@@ -1496,9 +838,6 @@ namespace VoxelForgeCore
explicit operator bool() const noexcept { return static_cast<bool>(data_); }
const WorldConfiguration& configuration() const;
const WorldAssemblyManifest* worldAssembly() const noexcept;
const ResolvedLayoutSnapshotV3* resolvedLayout() const noexcept;
ResolvedWorldSnapshot withResolvedLayout(const ResolvedLayoutSnapshotV3& layout) const;
ResolvedWorldSnapshot withoutResolvedLayoutPassages() const;
private:
struct Data;
@@ -1541,7 +880,6 @@ namespace VoxelForgeCore
bool roundTunnels = false;
bool enhancedTunnelWalls = true;
bool profileDetailBand = false;
bool parallelSignEdgeRepair = false;
// 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.
bool validatePassageRoutes = true;
@@ -1744,12 +1082,6 @@ namespace VoxelForgeCore
// 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.
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 seulement; evalue et verifie tous les echantillons des blocs uniformes.
bool auditUniformBlockProofs = false;
@@ -1762,11 +1094,11 @@ namespace VoxelForgeCore
// LOD8-only planar horizon interior grid; zero follows the MC step, 128 restores S9.
// Grille interieure LOD8 seulement; zero suit la maille MC, 128 restaure S9.
std::int32_t lod8HorizonInteriorStepVoxels = 0;
// Bump when shared stratum-boundary caps or the distant horizon representation changes.
// LOD4-8 mesh hashes are salted; density and LOD0-3 keep their direct identities.
// 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.
std::uint32_t farHorizonVersion = 5;
// Bump when the distant cap representation changes. LOD4-8 mesh hashes are salted;
// density and LOD0-3 hashes remain unchanged.
// Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version;
// densite et hashes LOD0-3 restent inchanges.
std::uint32_t farHorizonVersion = 3;
// Linux research controls for reproducing L5 negative controls. Defaults preserve output.
std::uint32_t diagnosticGlobalOctaveDrop = 0;
std::uint32_t diagnosticCellStride = 1;
@@ -1820,20 +1152,6 @@ namespace VoxelForgeCore
ShapeBuildStats buildStats() 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.
std::vector<PassageVoidCrossingAuditV1> passageVoidCrossings() const;
bool isPassageColumnSample(std::size_t passageIndex, std::size_t segmentIndex,
Vec3d voxelPosition) const;
bool isPassageShortSpanSample(std::size_t passageIndex, std::size_t segmentIndex,
Vec3d voxelPosition) const;
// Audit-only selector provenance for the same point evaluator; never used by meshing.
std::string densityDispatchPathAt(Vec3d voxelPosition) const;
// Analytic source distance without the occupancy-preserving near-zero clamp or packed proxy.
float unclampedDensityAt(Vec3d voxelPosition) const;
DetailFieldSample detailSampleAt(Vec3d voxelPosition) const;
@@ -56,7 +56,6 @@
#include "VoxelForgeCore/VoxelForgeCore.h"
#include "VoxelForgeCore/ThreadLocalStorage.h"
#include "VoxelForgeCore/SharedColumnCache.h"
#include "VoxelForgeCore/FloatingIslandInstrumentation.h"
// 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.
+8 -555
View File
@@ -817,9 +817,8 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
struct FFarMeshColumnIndex
{
FFarMeshColumnIndex(const FVoxelMeshData& InMesh,const FIntVector& InOrigin,int32 InExtent,
int32 InCells,double InStep,double InVoxelSizeCm)
int32 InCells,double InStep)
:Mesh(InMesh),Origin(InOrigin),Extent(InExtent),Cells(InCells),Step(InStep),
VoxelSizeCm(InVoxelSizeCm),
ByColumn(static_cast<size_t>(InCells)*InCells)
{
for(int32 Triangle=0;Triangle+2<Mesh.Triangles.Num();Triangle+=3)
@@ -865,98 +864,16 @@ struct FFarMeshColumnIndex
return Hits;
}
bool HasDownwardHit(double X,double Y,double Z,double Tolerance) const
{
const int32 CX=std::clamp(static_cast<int32>(std::floor((X-Origin.X)/Step)),0,Cells-1);
const int32 CY=std::clamp(static_cast<int32>(std::floor((Y-Origin.Y)/Step)),0,Cells-1);
const auto& Candidates=ByColumn[static_cast<size_t>(CY)*Cells+CX];
for(const int32 Triangle:Candidates)
{
const int32 IA=Mesh.Triangles[Triangle*3],IB=Mesh.Triangles[Triangle*3+1],
IC=Mesh.Triangles[Triangle*3+2];
if(IA<0||IB<0||IC<0||IA>=Mesh.Normals.Num()||IB>=Mesh.Normals.Num()
||IC>=Mesh.Normals.Num())continue;
if(Mesh.Normals[IA].Z+Mesh.Normals[IB].Z+Mesh.Normals[IC].Z>=-1.5)continue;
const double AX=PositionAxis(IA,0),AY=PositionAxis(IA,1),AZ=PositionAxis(IA,2);
const double BX=PositionAxis(IB,0),BY=PositionAxis(IB,1),BZ=PositionAxis(IB,2);
const double CXv=PositionAxis(IC,0),CYv=PositionAxis(IC,1),CZ=PositionAxis(IC,2);
const double Den=(BX-AX)*(CYv-AY)-(BY-AY)*(CXv-AX);
if(std::abs(Den)<1.0e-10)continue;
const double U=((X-AX)*(CYv-AY)-(Y-AY)*(CXv-AX))/Den;
const double V=((BX-AX)*(Y-AY)-(BY-AY)*(X-AX))/Den;
const double W=1.0-U-V;
if(U<-1.0e-7||V<-1.0e-7||W<-1.0e-7)continue;
const double Hit=AZ+U*(BZ-AZ)+V*(CZ-AZ);
if(std::abs(Hit-Z)<=Tolerance)return true;
}
return false;
}
bool HasDownwardCellCoverage(double X0,double Y0,double X1,double Y1,
double Z,double Tolerance) const
{
// Probe the perimeter as well as the interior: sparse interior-only probes can
// incorrectly hide a missing ceiling quad at an MC grid vertex.
// Sonder le perimetre et l'interieur: des sondes seulement interieures peuvent
// masquer un carre de plafond manquant sur un sommet de la grille MC.
constexpr double Samples[5]={0.0,0.25,0.5,0.75,1.0};
for(const double V:Samples)for(const double U:Samples)
{
const double X=X0+(X1-X0)*U;
const double Y=Y0+(Y1-Y0)*V;
if(!HasDownwardHit(X,Y,Z,Tolerance))return false;
}
return true;
}
bool HasUpwardHit(double X,double Y,double Z,double Tolerance) const
{
const int32 CX=std::clamp(static_cast<int32>(std::floor((X-Origin.X)/Step)),0,Cells-1);
const int32 CY=std::clamp(static_cast<int32>(std::floor((Y-Origin.Y)/Step)),0,Cells-1);
const auto& Candidates=ByColumn[static_cast<size_t>(CY)*Cells+CX];
for(const int32 Triangle:Candidates)
{
const int32 IA=Mesh.Triangles[Triangle*3],IB=Mesh.Triangles[Triangle*3+1],
IC=Mesh.Triangles[Triangle*3+2];
if(IA<0||IB<0||IC<0||IA>=Mesh.Normals.Num()||IB>=Mesh.Normals.Num()
||IC>=Mesh.Normals.Num())continue;
if(Mesh.Normals[IA].Z+Mesh.Normals[IB].Z+Mesh.Normals[IC].Z<=1.5)continue;
const double AX=PositionAxis(IA,0),AY=PositionAxis(IA,1),AZ=PositionAxis(IA,2);
const double BX=PositionAxis(IB,0),BY=PositionAxis(IB,1),BZ=PositionAxis(IB,2);
const double CXv=PositionAxis(IC,0),CYv=PositionAxis(IC,1),CZ=PositionAxis(IC,2);
const double Den=(BX-AX)*(CYv-AY)-(BY-AY)*(CXv-AX);
if(std::abs(Den)<1.0e-10)continue;
const double U=((X-AX)*(CYv-AY)-(Y-AY)*(CXv-AX))/Den;
const double V=((BX-AX)*(Y-AY)-(BY-AY)*(X-AX))/Den;
const double W=1.0-U-V;
if(U<-1.0e-7||V<-1.0e-7||W<-1.0e-7)continue;
const double Hit=AZ+U*(BZ-AZ)+V*(CZ-AZ);
if(std::abs(Hit-Z)<=Tolerance)return true;
}
return false;
}
bool HasUpwardCellCoverage(double X0,double Y0,double X1,double Y1,
double Z,double Tolerance) const
{
constexpr double Samples[5]={0.0,0.25,0.5,0.75,1.0};
for(const double V:Samples)for(const double U:Samples)
if(!HasUpwardHit(X0+(X1-X0)*U,Y0+(Y1-Y0)*V,Z,Tolerance))return false;
return true;
}
private:
double PositionAxis(int32 Index,int32 Axis) const
{
const FVector& P=Mesh.Vertices[Index];
const double WorldCentimetres=Axis==0?P.X:(Axis==1?P.Y:P.Z);
return WorldCentimetres/VoxelSizeCm;
return (Axis==0?P.X:(Axis==1?P.Y:P.Z))*0.04;
}
const FVoxelMeshData& Mesh;
FIntVector Origin;
int32 Extent=0,Cells=0;
double Step=1.0;
double VoxelSizeCm=25.0;
std::vector<std::vector<int32>> ByColumn;
};
@@ -1057,11 +974,11 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
std::vector<FBoundary> Boundaries;
for(const FStrateSlot& Slot:Layout)
{
FStandaloneShapeWorld::FResolvedStratumBounds Resolved;
if(!Shape.GetResolvedStratumBounds(Slot,Resolved))continue;
const double HalfWidth=std::max(8.0,Resolved.SealThickness+8.0);
Boundaries.push_back({Resolved.BottomZ,HalfWidth});
Boundaries.push_back({Resolved.TopZ,HalfWidth});
const FStrateGenerationParams Params=World.Manager->GetGenerationParams(
FIntVector(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2));
const double HalfWidth=std::max(8.0,static_cast<double>(Params.BoundarySealThickness)+8.0);
Boundaries.push_back({static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE,HalfWidth});
Boundaries.push_back({static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE,HalfWidth});
}
std::sort(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B){return A.Z<B.Z;});
Boundaries.erase(std::unique(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B)
@@ -1079,8 +996,7 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
const int32 Cells=std::max(1,World.CoarseTileCells);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step,
static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step);
std::vector<FVector> NewVertices,NewNormals;
std::vector<FVector2f> NewUVs;std::vector<FColor> NewColors;
std::vector<int32> NewTriangles;
@@ -1216,466 +1132,3 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
Mesh.TriangleSkirtFaceMasks=std::move(CombinedFaceMasks);
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,
const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
{
if(Key.Level<0||Step<=0||!World.Manager)return;
const FIntVector Origin=Key.OriginVoxels();
const int32 Extent=Key.ExtentVoxels();
const int32 GridStep=Step;
if(Extent<=0||GridStep<=0||Extent%GridStep!=0)return;
const int32 GridSide=Extent/GridStep+1;
struct FCandidate
{
double FaceZ=0.0;
double Thickness=0.0;
double MaximumDisplacementVoxels=0.0;
std::vector<double> FaceHeights;
std::vector<uint8> SolidAbove;
const VoxelForgeCore::ResolvedLayoutStratumV2* Stratum=nullptr;
bool bTransition=false;
};
std::vector<FCandidate> Candidates;
std::vector<FTransitionFaceReplacement> Replacements;
const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
for(const FStrateSlot& Slot:World.Manager->GetLayout())
{
const auto* Stratum=Shape.FindResolvedLayoutStratum(Slot.StrateIndex);
if(!Stratum||!Stratum->ceiling.presentInCurrentField
||Stratum->ceiling.topMicrometres<=Stratum->ceiling.bottomMicrometres)continue;
FCandidate Candidate;
Candidate.FaceZ=static_cast<double>(Stratum->ceiling.bottomMicrometres)/CoreMicrons;
Candidate.Thickness=static_cast<double>(Stratum->ceiling.topMicrometres
-Stratum->ceiling.bottomMicrometres)/CoreMicrons;
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);
if(Epsilon<=0.0)continue;
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;
for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X)
{
const double WorldX=Origin.X+X*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),
static_cast<float>(Candidate.FaceHeights[GridIndex]+Epsilon));
const bool bSolidAbove=std::isfinite(Above)&&Above<0.0f;
Candidate.SolidAbove[GridIndex]=bSolidAbove?1:0;
AnySolidAbove=AnySolidAbove||bSolidAbove;
}
if(AnySolidAbove)Candidates.push_back(std::move(Candidate));
}
if(Candidates.empty())return;
const int32 Cells=std::max(1,World.CoarseTileCells);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step,
static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0);
const int32 OldVertexCount=Mesh.Vertices.Num();
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
const double VoxelSizeCm=static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0;
std::vector<FVector> NewVertices,NewNormals;
std::vector<FVector2f> NewUVs;
std::vector<FColor> NewColors;
std::vector<int32> NewTriangles;
for(const FCandidate& Candidate:Candidates)
{
const std::size_t GridCount=static_cast<std::size_t>(GridSide)*GridSide;
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 std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
int32& Mapped=VertexMap[GridIndex];
if(Mapped!=INDEX_NONE)return Mapped;
Mapped=static_cast<int32>(NewVertices.size());
const double WorldX=Origin.X+X*GridStep,WorldY=Origin.Y+Y*GridStep;
const double FaceZ=Candidate.FaceHeights[GridIndex];
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(bHasColors)NewColors.push_back(Color);
return Mapped;
};
for(int32 Y=0;Y+1<GridSide;++Y)for(int32 X=0;X+1<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;
if(!Candidate.SolidAbove[I00]||!Candidate.SolidAbove[I10]
||!Candidate.SolidAbove[I01]||!Candidate.SolidAbove[I11])continue;
const double CellX0=Origin.X+X*GridStep,CellY0=Origin.Y+Y*GridStep;
const double CellX1=CellX0+GridStep,CellY1=CellY0+GridStep;
bool fullyCovered=false;
if(!Candidate.bTransition)
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 C=AddVertex(X+1,Y+1),D=AddVertex(X,Y+1);
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;
const int32 removedCoarse=RemoveCoarseTransitionFaceTriangles(Mesh,Origin,GridStep,
GridSide,VoxelSizeCm,Replacements,false);
const int32 AddedVertices=static_cast<int32>(NewVertices.size());
auto InsertVertices=[&](auto& Existing,const auto& Added)
{
using TArrayType=std::remove_reference_t<decltype(Existing)>;
TArrayType Combined;Combined.Reserve(Existing.Num()+static_cast<int32>(Added.size()));
for(int32 I=0;I<OldSurfaceVertices;++I)Combined.Add(Existing[I]);
for(const auto& Value:Added)Combined.Add(Value);
for(int32 I=OldSurfaceVertices;I<Existing.Num();++I)Combined.Add(Existing[I]);
Existing=std::move(Combined);
};
InsertVertices(Mesh.Vertices,NewVertices);InsertVertices(Mesh.Normals,NewNormals);
if(bHasUVs)InsertVertices(Mesh.UVs,NewUVs);
if(bHasColors)InsertVertices(Mesh.Colors,NewColors);
for(int32& Index:Mesh.Triangles)if(Index>=OldSurfaceVertices)Index+=AddedVertices;
const int32 CeilingStart=std::max(0,Mesh.Triangles.Num()-Mesh.NumCeilingTriangles*3);
TArray<int32> CombinedTriangles;CombinedTriangles.Reserve(
Mesh.Triangles.Num()+static_cast<int32>(NewTriangles.size()));
for(int32 I=0;I<CeilingStart;++I)CombinedTriangles.Add(Mesh.Triangles[I]);
for(const int32 Index:NewTriangles)CombinedTriangles.Add(OldSurfaceVertices+Index);
for(int32 I=CeilingStart;I<Mesh.Triangles.Num();++I)CombinedTriangles.Add(Mesh.Triangles[I]);
const int32 OldTriangleCount=Mesh.Triangles.Num()/3;
TArray<uint8> ExistingFaceMasks=Mesh.TriangleSkirtFaceMasks;
ExistingFaceMasks.SetNum(OldTriangleCount);
TArray<uint8> CombinedFaceMasks;CombinedFaceMasks.Reserve(
OldTriangleCount+static_cast<int32>(NewTriangles.size()/3));
for(int32 Triangle=0;Triangle<CeilingStart/3;++Triangle)
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
for(std::size_t Triangle=0;Triangle<NewTriangles.size()/3;++Triangle)
CombinedFaceMasks.Add(0);
for(int32 Triangle=CeilingStart/3;Triangle<OldTriangleCount;++Triangle)
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
Mesh.Triangles=std::move(CombinedTriangles);
Mesh.TriangleSkirtFaceMasks=std::move(CombinedFaceMasks);
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
Mesh.NumCeilingTriangles+=static_cast<int32>(NewTriangles.size()/3);
}
// Fill only top cells that marching cubes missed. Active transitions use the same
// displaced face and passage-mouth fade as the signed-distance field.
inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World,
const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
{
if(Key.Level<0||Step<=0||!World.Manager||!Shape.ResolvedLayout)return;
const FIntVector Origin=Key.OriginVoxels();
const int32 Extent=Key.ExtentVoxels();
if(Extent<=0||Extent%Step!=0)return;
const int32 GridSide=Extent/Step+1;
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<FTransitionFaceReplacement> Replacements;
const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
for(const FStrateSlot& Slot:World.Manager->GetLayout())
{
const auto* Stratum=Shape.FindResolvedLayoutStratum(Slot.StrateIndex);
if(!Stratum||!Stratum->bedrock.presentInCurrentField
||Stratum->bedrock.topMicrometres<=Stratum->bedrock.bottomMicrometres)continue;
const double FloorZ=static_cast<double>(Stratum->bedrock.topMicrometres)/CoreMicrons;
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
-Stratum->bedrock.bottomMicrometres)/CoreMicrons,displacement,Stratum,bTransition});
}
if(Candidates.empty())return;
const int32 Cells=std::max(1,World.CoarseTileCells);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step,
static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0);
const int32 OldVertexCount=Mesh.Vertices.Num();
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
const double VoxelSizeCm=static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0;
std::vector<FVector> NewVertices,NewNormals;
std::vector<FVector2f> NewUVs;
std::vector<FColor> NewColors;
std::vector<int32> NewTriangles;
for(const FCandidate& Candidate:Candidates)
{
const double Epsilon=std::min(0.5,Candidate.Thickness*0.25);
if(Epsilon<=0.0)continue;
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)
{
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),
static_cast<float>(faceHeights[GridIndex]-Epsilon));
solidBelow[GridIndex]=
std::isfinite(Density)&&Density<0.0f?1:0;
}
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 std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
int32& Mapped=VertexMap[GridIndex];
if(Mapped!=INDEX_NONE)return Mapped;
Mapped=static_cast<int32>(NewVertices.size());
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,
faceHeights[GridIndex]*VoxelSizeCm);
NewNormals.push_back(Normal);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(WorldX),static_cast<float>(WorldY));
if(bHasColors)NewColors.push_back(Color);
return Mapped;
};
for(int32 Y=0;Y+1<GridSide;++Y)for(int32 X=0;X+1<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;
if(!solidBelow[I00]||!solidBelow[I10]||!solidBelow[I01]||!solidBelow[I11])continue;
const double X0=Origin.X+X*Step,Y0=Origin.Y+Y*Step;
const double X1=X0+Step,Y1=Y0+Step;
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 C=AddVertex(X+1,Y+1),D=AddVertex(X,Y+1);
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;
const int32 removedCoarse=RemoveCoarseTransitionFaceTriangles(Mesh,Origin,Step,
GridSide,VoxelSizeCm,Replacements,true);
const int32 AddedVertices=static_cast<int32>(NewVertices.size());
auto InsertVertices=[&](auto& Existing,const auto& Added)
{
using TArrayType=std::remove_reference_t<decltype(Existing)>;
TArrayType Combined;Combined.Reserve(Existing.Num()+static_cast<int32>(Added.size()));
for(int32 I=0;I<OldSurfaceVertices;++I)Combined.Add(Existing[I]);
for(const auto& Value:Added)Combined.Add(Value);
for(int32 I=OldSurfaceVertices;I<Existing.Num();++I)Combined.Add(Existing[I]);
Existing=std::move(Combined);
};
InsertVertices(Mesh.Vertices,NewVertices);InsertVertices(Mesh.Normals,NewNormals);
if(bHasUVs)InsertVertices(Mesh.UVs,NewUVs);
if(bHasColors)InsertVertices(Mesh.Colors,NewColors);
for(int32& Index:Mesh.Triangles)if(Index>=OldSurfaceVertices)Index+=AddedVertices;
const int32 CeilingStart=std::max(0,Mesh.Triangles.Num()-Mesh.NumCeilingTriangles*3);
TArray<int32> CombinedTriangles;CombinedTriangles.Reserve(
Mesh.Triangles.Num()+static_cast<int32>(NewTriangles.size()));
for(int32 I=0;I<CeilingStart;++I)CombinedTriangles.Add(Mesh.Triangles[I]);
for(const int32 Index:NewTriangles)CombinedTriangles.Add(OldSurfaceVertices+Index);
for(int32 I=CeilingStart;I<Mesh.Triangles.Num();++I)CombinedTriangles.Add(Mesh.Triangles[I]);
const int32 OldTriangleCount=Mesh.Triangles.Num()/3;
TArray<uint8> ExistingFaceMasks=Mesh.TriangleSkirtFaceMasks;
ExistingFaceMasks.SetNum(OldTriangleCount);
TArray<uint8> CombinedFaceMasks;CombinedFaceMasks.Reserve(
OldTriangleCount+static_cast<int32>(NewTriangles.size()/3));
for(int32 Triangle=0;Triangle<CeilingStart/3;++Triangle)
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
for(std::size_t Triangle=0;Triangle<NewTriangles.size()/3;++Triangle)
CombinedFaceMasks.Add(0);
for(int32 Triangle=CeilingStart/3;Triangle<OldTriangleCount;++Triangle)
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
Mesh.Triangles=std::move(CombinedTriangles);
Mesh.TriangleSkirtFaceMasks=std::move(CombinedFaceMasks);
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
}
+41 -676
View File
@@ -1,9 +1,6 @@
#pragma once
#include "VoxelForgeCore/ThreadLocalStorage.h"
#include "VoxelForgeCore/CpuCycleCounter.h"
#include "VoxelForgeCore/FloatingIslandInstrumentation.h"
#include "VoxelForgeCore/SharedColumnCache.h"
#include "Json.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
@@ -173,8 +170,6 @@ namespace floating_island_detail
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutCenterTopZSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutMinimumZSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutMaximumZSubvoxels), 8);
if(recipe.geometryVersion>=4)
HashWord(hash,static_cast<std::uint64_t>(recipe.layoutChunkWidthSubvoxels),8);
HashWord(hash, recipe.primaryIslandCount, 4);
HashWord(hash, static_cast<std::uint64_t>(recipe.islandDensityQ16), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.spacingSubvoxels), 8);
@@ -191,22 +186,12 @@ namespace floating_island_detail
HashWord(hash, static_cast<std::uint64_t>(recipe.crustDepthBonusQ16), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.rootDepthRatioQ16), 8);
}
if (recipe.geometryVersion >= 5)
{
HashWord(hash, recipe.anchorIslandOrdinal, 4);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorPlacementRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorRootRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorIslandMinimumRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorRootBottomZSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorMouthOffsetSubvoxels), 8);
}
HashWord(hash, static_cast<std::uint64_t>(recipe.blendRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.contourWarpSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.roughnessSubvoxels), 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.bridgeThicknessSubvoxels), 8);
if(recipe.terrainFollowingRoute)HashWord(hash,1,1);
HashWord(hash, HashRouteConstraints(recipe.routeConstraints), 8);
return hash;
}
@@ -260,10 +245,10 @@ namespace floating_island_detail
inline bool ValidateRecipe(const FloatingIslandRecipeV1& recipe, std::string& error)
{
const std::array<std::int64_t, 32> values = {
const std::array<std::int64_t, 25> values = {
recipe.layoutOriginXSubvoxels, recipe.layoutOriginYSubvoxels,
recipe.layoutCenterTopZSubvoxels, recipe.layoutMinimumZSubvoxels,
recipe.layoutMaximumZSubvoxels, recipe.layoutChunkWidthSubvoxels, recipe.islandDensityQ16,
recipe.layoutMaximumZSubvoxels, recipe.islandDensityQ16,
recipe.spacingSubvoxels, recipe.minimumRadiusSubvoxels,
recipe.maximumRadiusSubvoxels, recipe.thicknessRatioQ16,
recipe.verticalJitterQ16, recipe.topFlattenQ16,
@@ -272,10 +257,6 @@ namespace floating_island_detail
recipe.blendRadiusSubvoxels, recipe.contourWarpSubvoxels,
recipe.roughnessSubvoxels, recipe.boundarySealSubvoxels,
recipe.bridgeHalfWidthSubvoxels, recipe.bridgeThicknessSubvoxels,
static_cast<std::int64_t>(recipe.anchorIslandOrdinal),
recipe.anchorPlacementRadiusSubvoxels, recipe.anchorRootRadiusSubvoxels,
recipe.anchorIslandMinimumRadiusSubvoxels, recipe.anchorRootBottomZSubvoxels,
recipe.anchorMouthOffsetSubvoxels,
recipe.routeConstraints.capsuleRadiusSubvoxels,
recipe.routeConstraints.capsuleHeightSubvoxels,
recipe.routeConstraints.maximumGradeQ16};
@@ -285,7 +266,7 @@ namespace floating_island_detail
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
const std::uint32_t ExpectedDenominator = ScaleGcd > 0
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 8
if (recipe.schemaVersion != 1 || (recipe.geometryVersion != 1 && recipe.geometryVersion != 2)
|| (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q
@@ -294,11 +275,6 @@ namespace floating_island_detail
|| recipe.ownerToCoreScaleNumerator != ExpectedNumerator
|| recipe.ownerToCoreScaleDenominator != ExpectedDenominator
|| recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9
|| recipe.layoutChunkWidthSubvoxels <= 0
|| static_cast<long double>(recipe.layoutChunkWidthSubvoxels)*recipe.coreVoxelSizeMicrons
< 250000.0L*Q
|| static_cast<long double>(recipe.layoutChunkWidthSubvoxels)*recipe.coreVoxelSizeMicrons
> 64000000.0L*Q
|| recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels
|| recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q
|| std::any_of(values.begin(), values.end(), [](auto value) { return !ValidScalar(value); }))
@@ -340,21 +316,6 @@ namespace floating_island_detail
|| constraints.minimumClearanceSubvoxels < 0
|| constraints.minimumSupportProbes == 0 || constraints.minimumSupportProbes > 5)
{ error = "floating-island shape or route parameters are outside the supported range"; return false; }
if (recipe.geometryVersion >= 5
&& (recipe.anchorIslandOrdinal != recipe.primaryIslandCount - 1
|| recipe.anchorPlacementRadiusSubvoxels <= 0
|| recipe.anchorRootRadiusSubvoxels < recipe.anchorMouthOffsetSubvoxels
+ recipe.routeConstraints.capsuleRadiusSubvoxels + Q
|| recipe.anchorIslandMinimumRadiusSubvoxels
< recipe.anchorMouthOffsetSubvoxels
+ recipe.routeConstraints.capsuleRadiusSubvoxels + Q
|| recipe.anchorIslandMinimumRadiusSubvoxels > 64 * Q
|| recipe.anchorRootBottomZSubvoxels < recipe.layoutMinimumZSubvoxels
+ recipe.boundarySealSubvoxels
|| recipe.anchorRootBottomZSubvoxels >= recipe.layoutCenterTopZSubvoxels
|| recipe.anchorMouthOffsetSubvoxels <= 0
|| recipe.anchorMouthOffsetSubvoxels > 64 * Q))
{ error = "floating-island bedrock anchor is outside its passage or rock-thickness bounds"; return false; }
return true;
}
@@ -474,69 +435,13 @@ namespace floating_island_detail
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 =
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(
const FloatingIslandRecipeV1& recipe,const FloatingIslandPlanV1& plan,
const FloatingIslandAnchorV1& island,double x,double y)
{
FScopedIslandAttributionTimer columnTimer(EIslandAttributionBucket::Column);
// 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.
// Les echantillons suivent les colonnes Core entieres; reutiliser les memes metriques XY.
@@ -546,7 +451,6 @@ namespace floating_island_detail
static thread_local VoxelForgeCoreThreadLocal::FLazy<
std::array<FIslandColumnCacheEntry,SetCount*Ways>> Cache;
auto& cache=Cache.Get();
FScopedIslandAttributionTimer localProbeTimer(EIslandAttributionBucket::LocalProbe);
const std::int64_t SampleX=static_cast<std::int64_t>(x);
const std::int64_t SampleY=static_cast<std::int64_t>(y);
std::uint64_t IndexKey=island.stableId
@@ -561,7 +465,6 @@ namespace floating_island_detail
IndexKey^=IndexKey>>33;
const std::size_t Base=static_cast<std::size_t>(IndexKey&(SetCount-1))*Ways;
FIslandColumnCacheEntry* Replacement=&cache[Base+((IndexKey>>11)&(Ways-1))];
bool bFoundInvalid=false;
for(std::size_t Way=0;Way<Ways;++Way)
{
FIslandColumnCacheEntry& Entry=cache[Base+Way];
@@ -577,64 +480,8 @@ namespace floating_island_detail
&&Entry.TopHalf==island.topHalfSubvoxels
&&Entry.TopFlattenQ16==recipe.topFlattenQ16
&&Entry.ContourWarp==recipe.contourWarpSubvoxels;
if(bKeyMatches)
{
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));
}
}
if(bKeyMatches)return Entry;
if(!Entry.bValid)Replacement=&Entry;
}
const auto build=[&]()
@@ -661,14 +508,9 @@ namespace floating_island_detail
Entry.Radial=std::hypot(Entry.DeltaX,Entry.DeltaY);
Entry.Angle=std::atan2(Entry.DeltaY,Entry.DeltaX);
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)
+contourOffset);
+LobeOffsetWithPhase(recipe,Entry.Angle,static_cast<double>(island.topZSubvoxels),
Entry.LobePhase));
const double Edge=std::clamp(Entry.Radial/EffectiveRadius,0.0,1.0);
const double Drop=(1.0-Q16ToDouble(recipe.topFlattenQ16))
*static_cast<double>(island.topHalfSubvoxels)*2.0*Edge*Edge;
@@ -684,12 +526,7 @@ namespace floating_island_detail
static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),
plan.contentHash,SampleX,SampleY,island.stableId};
bool computed=false;
auto* stats=ActiveIslandColumnCacheInstrumentation();
{
FScopedIslandAttributionTimer sharedCacheTimer(EIslandAttributionBucket::SharedCache);
Entry=shared->getOrCompute(key,build,computed,
stats?&stats->shared:nullptr);
}
Entry=shared->getOrCompute(key,build,computed);
}
else Entry=build();
*Replacement=Entry;
@@ -752,7 +589,6 @@ namespace floating_island_detail
const FloatingIslandAnchorV1& island,
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
// 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.
@@ -823,38 +659,6 @@ namespace floating_island_detail
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,
std::uint64_t featureId, double structural,
double x, double y, double z, double detailScale = 1.0)
@@ -873,7 +677,6 @@ namespace floating_island_detail
const FloatingIslandAnchorV1& island,
double x, double y, double z)
{
FScopedIslandAttributionTimer islandTimer(EIslandAttributionBucket::Island);
if (recipe.geometryVersion < 2)
{
const double dx = x - static_cast<double>(island.centerSubvoxels[0]);
@@ -901,13 +704,8 @@ namespace floating_island_detail
const double radial=Column.Radial;
const double topZ = static_cast<double>(island.topZSubvoxels);
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,
radius + contourOffset);
radius + LobeOffsetWithPhase(recipe,Column.Angle,z,Column.LobePhase));
const double edge = std::clamp(radial / std::max(effectiveRadius, 1.0), 0.0, 1.0);
const double lowerSurface = static_cast<double>(IslandCrustBottom(recipe, island))
+ radius * 0.16 * edge * edge;
@@ -920,28 +718,6 @@ namespace floating_island_detail
const double rootBlend = std::clamp(radius * 0.12, 2.0 * Q, 5.0 * Q);
for (std::int32_t root = 0; root < rootCount; ++root)
sdf = SmoothMin(sdf, RootLobeDensity(recipe, island, root, x, y, z), rootBlend);
if (recipe.geometryVersion >= 5 && recipe.geometryVersion < 8
&& island.ordinal == recipe.anchorIslandOrdinal
&& recipe.anchorRootBottomZSubvoxels < island.bottomZSubvoxels)
{
// The central buttress must meet the island crust underside. IslandBottom
// also includes the four shorter root lobes and sits below that surface.
const double rootTop = static_cast<double>(IslandCrustBottom(recipe, island));
const double rootBottom = static_cast<double>(recipe.anchorRootBottomZSubvoxels);
const double depth = std::max(1.0, rootTop - rootBottom);
const double blend = std::clamp(static_cast<double>(recipe.anchorRootRadiusSubvoxels)
* 0.12, 3.0 * Q, 6.0 * Q);
// A slightly flared lower buttress joins the island's own root lobes to bedrock.
// Une base legerement evasee relie les racines de l'ile a la roche de fond.
const double t = std::clamp((rootTop - z) / depth, 0.0, 1.0);
const double rootRadius = static_cast<double>(recipe.anchorRootRadiusSubvoxels)
* (0.92 + 0.08 * SmoothStep(t));
double rootSdf = std::max({Column.Radial - rootRadius,
rootBottom - z, z - rootTop});
rootSdf = ApplySurfaceDetail(recipe, island.stableId ^ 0x414e43484f52524full,
rootSdf, x, y, z, 0.75);
sdf = SmoothMin(sdf, rootSdf, blend);
}
const double topDetailScale = topDistance >= std::max(sideDistance, undersideDistance)
|| z >= static_cast<double>(IslandCrustBottom(recipe, island)) + radius * 0.45
? 0.25 : 1.0;
@@ -954,7 +730,6 @@ namespace floating_island_detail
bool* topSurface = nullptr,
double* alongFraction = nullptr)
{
FScopedIslandAttributionTimer connectorTimer(EIslandAttributionBucket::Connector);
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]);
@@ -973,266 +748,15 @@ namespace floating_island_detail
const double t = std::clamp(along / length, 0.0, 1.0);
if (alongFraction) *alongFraction = 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
&& WorldAssemblyEvaluatorVersion < 172
? 2.0 * static_cast<double>(edge.thicknessSubvoxels) * (4.0 * t * (1.0 - t))
: 0.0;
? 2.0 * static_cast<double>(edge.thicknessSubvoxels) * (4.0 * t * (1.0 - t))
: 0.0;
const double upper = z - deck;
const double lower = deck - static_cast<double>(edge.thicknessSubvoxels) - arch - z;
if (topSurface) *topSurface = upper >= std::max(horizontal, 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,
const FloatingIslandPlanV1& plan,
std::int64_t x, std::int64_t y, std::int64_t z)
@@ -1364,15 +888,9 @@ namespace floating_island_detail
FloatingIslandAnchorV1& island)
{
const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels;
const bool isAnchorRoot=recipe.geometryVersion>=5&&recipe.geometryVersion<8
&&island.ordinal==recipe.anchorIslandOrdinal;
const std::int64_t reach = std::max(island.radiusSubvoxels,
isAnchorRoot?recipe.anchorRootRadiusSubvoxels:std::int64_t{0})
+ recipe.contourWarpSubvoxels + pad;
const std::int64_t reach = island.radiusSubvoxels + recipe.contourWarpSubvoxels + pad;
island.influenceMinimumSubvoxels = {island.centerSubvoxels[0] - reach,
island.centerSubvoxels[1] - reach,
(isAnchorRoot?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels)
:island.bottomZSubvoxels)-pad};
island.centerSubvoxels[1] - reach, island.bottomZSubvoxels - pad};
island.influenceMaximumSubvoxels = {island.centerSubvoxels[0] + reach,
island.centerSubvoxels[1] + reach, island.topZSubvoxels + pad};
}
@@ -1417,19 +935,12 @@ namespace floating_island_detail
std::unordered_set<std::uint64_t> islandIds;
std::unordered_set<std::uint32_t> ordinals;
const FloatingIslandAnchorV1* anchorIsland = nullptr;
for (const auto& island : plan.islands)
{
const bool isAnchor=recipe.geometryVersion>=5
&&island.ordinal==recipe.anchorIslandOrdinal;
if(isAnchor)anchorIsland=&island;
if (island.stableId == 0 || !islandIds.insert(island.stableId).second
|| !ordinals.insert(island.ordinal).second
|| island.radiusSubvoxels < (isAnchor?recipe.anchorIslandMinimumRadiusSubvoxels
:recipe.minimumRadiusSubvoxels)
|| island.radiusSubvoxels > (isAnchor
?std::max(recipe.anchorIslandMinimumRadiusSubvoxels,recipe.maximumRadiusSubvoxels)
:recipe.maximumRadiusSubvoxels)
|| island.radiusSubvoxels < recipe.minimumRadiusSubvoxels
|| island.radiusSubvoxels > recipe.maximumRadiusSubvoxels
|| island.topHalfSubvoxels <= 0 || island.topZSubvoxels <= island.bottomZSubvoxels
|| island.taperEndQ16 < recipe.taperEndMinimumQ16
|| island.taperEndQ16 > recipe.taperEndMaximumQ16
@@ -1446,17 +957,12 @@ namespace floating_island_detail
{ error = "floating-island anchor has invalid coordinates or an inverted bound"; return false; }
}
const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels;
const bool hasAnchorRoot=recipe.geometryVersion>=5&&recipe.geometryVersion<8&&isAnchor;
const std::int64_t reach = std::max(island.radiusSubvoxels,
hasAnchorRoot?recipe.anchorRootRadiusSubvoxels:std::int64_t{0})
+ recipe.contourWarpSubvoxels + pad;
const std::int64_t reach = island.radiusSubvoxels + recipe.contourWarpSubvoxels + pad;
if (island.influenceMinimumSubvoxels[0] > island.centerSubvoxels[0] - reach
|| island.influenceMaximumSubvoxels[0] < island.centerSubvoxels[0] + reach
|| island.influenceMinimumSubvoxels[1] > island.centerSubvoxels[1] - reach
|| island.influenceMaximumSubvoxels[1] < island.centerSubvoxels[1] + reach
|| island.influenceMinimumSubvoxels[2] > (hasAnchorRoot
?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels)
:island.bottomZSubvoxels) - pad
|| island.influenceMinimumSubvoxels[2] > island.bottomZSubvoxels - pad
|| island.influenceMaximumSubvoxels[2] < island.topZSubvoxels + pad
|| island.bottomZSubvoxels < recipe.layoutMinimumZSubvoxels + recipe.boundarySealSubvoxels
|| island.topZSubvoxels > recipe.layoutMaximumZSubvoxels - recipe.boundarySealSubvoxels)
@@ -1465,19 +971,6 @@ namespace floating_island_detail
for (std::uint32_t ordinal = 0; ordinal < recipe.primaryIslandCount; ++ordinal)
if (!ordinals.contains(ordinal))
{ error = "floating-island route chain is missing a primary seeded anchor"; return false; }
if(recipe.geometryVersion>=5)
{
if(!anchorIsland)
{error="floating-island plan is missing its unique passage anchor island";return false;}
const double anchorDistance=std::hypot(
static_cast<double>(anchorIsland->centerSubvoxels[0]),
static_cast<double>(anchorIsland->centerSubvoxels[1]));
if(anchorDistance>recipe.anchorPlacementRadiusSubvoxels
||recipe.anchorRootBottomZSubvoxels>=anchorIsland->bottomZSubvoxels
||recipe.anchorMouthOffsetSubvoxels+recipe.routeConstraints.capsuleRadiusSubvoxels
>=anchorIsland->radiusSubvoxels)
{error="floating-island anchor island or its mouth leaves the passage radius or rock envelope";return false;}
}
std::unordered_set<std::uint64_t> edgeIds;
std::unordered_map<std::uint64_t, std::vector<std::uint64_t>> adjacency;
@@ -1593,8 +1086,8 @@ namespace floating_island_detail
// 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.
// Les connecteurs recouvrent le plateau pour soutenir toute la capsule au raccord.
// Keep the capsule footprint supported while seating L37 connectors just below the native island tops.
// Garder l'appui de la capsule tout en enfouissant les connecteurs L37 juste sous les plateaux.
// Keep the narrow span exposed to open sky; a smaller fraction buries most of it in the island tops.
// Garder la passerelle etroite visible dans le vide ; une fraction plus faible l'enfouit dans les plateaux.
const double landingFraction = recipe.geometryVersion >= 2 ? 0.52 : 0.62;
const double fromReach = static_cast<double>(from.radiusSubvoxels) * landingFraction;
const double toReach = static_cast<double>(to.radiusSubvoxels) * landingFraction;
@@ -1602,19 +1095,7 @@ namespace floating_island_detail
const double sy = from.centerSubvoxels[1] + uy * fromReach;
const double ex = to.centerSubvoxels[0] - ux * toReach;
const double ey = to.centerSubvoxels[1] - uy * toReach;
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 routeDeckLift = recipe.geometryVersion >= 2 ? 1.0 * Q : 0.0;
const double sz = IslandTopSurface(recipe, from, sx, sy) + routeDeckLift;
const double ez = IslandTopSurface(recipe, to, ex, ey) + routeDeckLift;
edge.startSubvoxels = {RoundQ(sx), RoundQ(sy), RoundQ(sz)};
@@ -1715,8 +1196,7 @@ namespace floating_island_detail
}
std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
std::string_view json, std::string& error,
std::int64_t resolvedLayoutChunkWidthSubvoxels)
std::string_view json, std::string& error)
{
VFJson::Value root;
if (!VFJson::Parser(std::string(json)).Parse(root, error)) return std::nullopt;
@@ -1742,32 +1222,6 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
manifest.recipe.crustDepthBonusQ16 = 0;
manifest.recipe.rootDepthRatioQ16 = 0;
}
if(manifest.recipe.geometryVersion>=4)
{
if(!floating_island_detail::ReadInteger(recipeValue.Get("layout_chunk_width_q"),
manifest.recipe.layoutChunkWidthSubvoxels))
{error="floating-island recipe is missing its resolved layout chunk width";return std::nullopt;}
if(resolvedLayoutChunkWidthSubvoxels>0
&& manifest.recipe.layoutChunkWidthSubvoxels!=resolvedLayoutChunkWidthSubvoxels)
{error="floating-island recipe layout width disagrees with the resolved world layout";return std::nullopt;}
}
if(manifest.recipe.geometryVersion>=5)
{
if(!floating_island_detail::ReadU32(recipeValue.Get("anchor_island_ordinal"),
manifest.recipe.anchorIslandOrdinal)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_placement_radius_q"),
manifest.recipe.anchorPlacementRadiusSubvoxels)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_root_radius_q"),
manifest.recipe.anchorRootRadiusSubvoxels)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_island_minimum_radius_q"),
manifest.recipe.anchorIslandMinimumRadiusSubvoxels)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_root_bottom_z_q"),
manifest.recipe.anchorRootBottomZSubvoxels)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_mouth_offset_q"),
manifest.recipe.anchorMouthOffsetSubvoxels))
{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)
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|| !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion)
@@ -1803,10 +1257,6 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|| !floating_island_detail::ReadHash(planValue.Get("content_hash"), planHash)
|| schema != 1)
{ error = "floating-island manifest is missing a required versioned recipe or plan field"; return std::nullopt; }
if(manifest.recipe.geometryVersion<4 && resolvedLayoutChunkWidthSubvoxels>0)
manifest.recipe.layoutChunkWidthSubvoxels=resolvedLayoutChunkWidthSubvoxels;
else if(manifest.recipe.geometryVersion<4)
{ error="legacy floating-island manifest requires the world's resolved layout chunk width"; return std::nullopt; }
manifest.recipe.contentHash = recipeHash;
manifest.contentHash = manifestHash;
manifest.plan.contentHash = planHash;
@@ -1918,16 +1368,6 @@ std::string FloatingIslandManifestV1::ToJson() const
out << ",\"geometry_version\":" << recipe.geometryVersion
<< ",\"crust_depth_bonus_q16\":" << recipe.crustDepthBonusQ16
<< ",\"root_depth_ratio_q16\":" << recipe.rootDepthRatioQ16;
if(recipe.geometryVersion>=4)
out << ",\"layout_chunk_width_q\":" << recipe.layoutChunkWidthSubvoxels;
if(recipe.geometryVersion>=5)
out << ",\"anchor_island_ordinal\":" << recipe.anchorIslandOrdinal
<< ",\"anchor_placement_radius_q\":" << recipe.anchorPlacementRadiusSubvoxels
<< ",\"anchor_root_radius_q\":" << recipe.anchorRootRadiusSubvoxels
<< ",\"anchor_island_minimum_radius_q\":" << recipe.anchorIslandMinimumRadiusSubvoxels
<< ",\"anchor_root_bottom_z_q\":" << recipe.anchorRootBottomZSubvoxels
<< ",\"anchor_mouth_offset_q\":" << recipe.anchorMouthOffsetSubvoxels;
if(recipe.terrainFollowingRoute)out << ",\"terrain_following_route\":true";
out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels
<< ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels
<< ",\"roughness_q\":" << recipe.roughnessSubvoxels
@@ -2089,9 +1529,6 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
auto island = floating_island_detail::MakeIsland(recipe, i,
primaryXY[i][0], primaryXY[i][1], tops[i], site);
island.radiusSubvoxels = radii[i];
if(recipe.geometryVersion>=5&&i==recipe.anchorIslandOrdinal)
island.radiusSubvoxels=std::max(island.radiusSubvoxels,
recipe.anchorIslandMinimumRadiusSubvoxels);
island.topHalfSubvoxels = std::max<std::int64_t>(1, island.radiusSubvoxels / 5);
island.centerSubvoxels[2] = island.topZSubvoxels - island.topHalfSubvoxels;
island.bottomZSubvoxels = floating_island_detail::IslandBottom(recipe,
@@ -2178,54 +1615,14 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
static_cast<double>(a.centerSubvoxels[0]) + dx * t);
const std::int64_t y = floating_island_detail::RoundQ(
static_cast<double>(a.centerSubvoxels[1]) + dy * t);
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)
{
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 routeAngle=std::atan2(dy,dx);
const double angle=routeAngle+(side?1.0:-1.0)
*floating_island_detail::Pi/2.0;
const double mouthX=static_cast<double>(anchor.centerSubvoxels[0])
+static_cast<double>(recipe.anchorMouthOffsetSubvoxels)*std::cos(angle);
const double mouthY=static_cast<double>(anchor.centerSubvoxels[1])
+static_cast<double>(recipe.anchorMouthOffsetSubvoxels)*std::sin(angle);
routeX=floating_island_detail::RoundQ(mouthX);
routeY=floating_island_detail::RoundQ(mouthY);
}
const auto surface = WorldAssemblyEvaluatorVersion>=199
&&recipe.terrainFollowingRoute
?floating_island_detail::FindSurfaceZ(recipe,manifest.plan,routeX,routeY)
:floating_island_detail::FindCapsuleFootingTopZ(
recipe,manifest.plan,routeX,routeY);
const auto surface = floating_island_detail::FindCapsuleFootingTopZ(
recipe, manifest.plan, x, y);
if (!surface)
{ error = "island planner could not find a full capsule-width support surface along its connector chain"; return std::nullopt; }
WorldAssemblyRoutePointV1 point;
point.stableFeatureId = floating_island_detail::StableId(recipe.seed,
0x500000000ull + routeOrdinal, 0x524f555445ull);
point.positionSubvoxels = {routeX, routeY, *surface + capsuleHalf
point.positionSubvoxels = {x, y, *surface + capsuleHalf
+ recipe.routeConstraints.minimumClearanceSubvoxels
+ (recipe.geometryVersion >= 2 ? floating_island_detail::Q / 8 : 0)};
point.radiusSubvoxels = routeRadius;
@@ -2286,19 +1683,14 @@ bool FloatingIslandFieldV1::isValid() const noexcept
std::int64_t FloatingIslandFieldV1::densitySubvoxels(std::int64_t x,
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.
// 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))
density=densitySubvoxelsForTile(x, y, z, *list);
else if (!isValid() || !floating_island_detail::ValidScalar(x)
return densitySubvoxelsForTile(x, y, z, *list);
if (!isValid() || !floating_island_detail::ValidScalar(x)
|| !floating_island_detail::ValidScalar(y) || !floating_island_detail::ValidScalar(z))
density=floating_island_detail::MaxDensity;
else density=floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
fieldTimer.Stop();
return density;
return floating_island_detail::MaxDensity;
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
}
std::vector<WorldAssemblyFeatureBoundsV1> FloatingIslandFieldV1::solidSupportBounds() const
@@ -2497,46 +1889,19 @@ 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(minZ) || !floating_island_detail::ValidScalar(maxX)
|| !floating_island_detail::ValidScalar(maxY) || !floating_island_detail::ValidScalar(maxZ)) return result;
using namespace floating_island_detail;
if (plan_->islands.empty())
return {true,MaxDensity,MaxDensity};
const std::array<double,3> minimum{static_cast<double>(minX),
static_cast<double>(minY),static_cast<double>(minZ)};
const std::array<double,3> maximum{static_cast<double>(maxX),
static_cast<double>(maxY),static_cast<double>(maxZ)};
double fieldMinimum=static_cast<double>(MaxDensity);
double fieldMaximum=static_cast<double>(MaxDensity);
for(const auto& island:plan_->islands)
{
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);
const std::array<std::int64_t, 3> minimum{minX, minY, minZ};
const std::array<std::int64_t, 3> maximum{maxX, maxY, maxZ};
for (const auto& island : plan_->islands)
if (floating_island_detail::Intersects(minimum, maximum,
island.influenceMinimumSubvoxels, island.influenceMaximumSubvoxels)) return result;
for (const auto& edge : plan_->clusterEdges)
if (floating_island_detail::Intersects(minimum, maximum,
edge.influenceMinimumSubvoxels, edge.influenceMaximumSubvoxels)) return result;
// Root is void. A box outside every lobe/taper/blend/connector influence bound proves air.
// La racine est le vide : une boite hors des influences bornees prouve l'air.
result.known = true;
result.minimumDensitySubvoxels = 0;
result.maximumDensitySubvoxels = floating_island_detail::MaxDensity;
return result;
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -331,28 +331,6 @@ VF_FIELD_INLINE float VF_FieldPerlin3D(float x,float y,float z)
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_FieldI32 mode)
{
+22 -587
View File
@@ -145,12 +145,6 @@ namespace world_assembly_detail
};
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 MaxSerializedMagnitude = 1000000000000ll;
constexpr std::size_t MaxBiomeCount = 8;
@@ -234,114 +228,6 @@ namespace world_assembly_detail
inline bool OutsideRange(std::int64_t value)
{ 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)
{
if (recipe.schemaVersion != WorldAssemblySchemaVersion
@@ -349,9 +235,6 @@ namespace world_assembly_detail
|| recipe.evaluatorVersion > WorldAssemblyEvaluatorVersion
|| recipe.unitsPerVoxel != WorldAssemblySubvoxelsPerVoxel)
{ error = "world assembly recipe version, evaluator or fixed scale is unsupported"; return false; }
if (recipe.layoutChunkWidthSubvoxels <= 0
|| recipe.layoutChunkWidthSubvoxels > 256ll * 512ll)
{ error = "resolved strate-layout chunk width is outside the supported physical range"; return false; }
if (recipe.biomes.empty() || recipe.biomes.size() > MaxBiomeCount)
{ error = "world assembly recipe must contain one to eight biome profiles"; return false; }
if (recipe.biomeCellSizeSubvoxels <= 0 || recipe.biomeCellSizeSubvoxels > 100000000ll
@@ -410,57 +293,15 @@ namespace world_assembly_detail
|| recipe.biomeClimateMoistureWavelengthSubvoxels <= 0
|| recipe.biomeClimateReliefContrastQ16 < 0))
{ 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)
inline bool ValidateStratum(const WorldAssemblyStratumPlanV1& stratum, 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;
}
inline bool ValidateStratum(const WorldAssemblyStratumPlanV1& stratum,
const WorldAssemblyRecipeV1& recipe, std::string& error)
{
const std::int64_t layoutChunkWidthSubvoxels = recipe.layoutChunkWidthSubvoxels;
if (stratum.strateIndex < 0 || stratum.topChunkZ < stratum.bottomChunkZ
|| stratum.capWavelengthSubvoxels <= 0 || stratum.capUndulationSubvoxels < 0
|| stratum.features.size() > MaxFeatureCount || stratum.route.size() > MaxRoutePointCount)
{ 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)
|| !ValidScalar(stratum.capWavelengthSubvoxels)
|| !ValidScalar(stratum.capDetailAmplitudeSubvoxels)
@@ -489,9 +330,9 @@ namespace world_assembly_detail
if (feature.type == 2 || feature.type == 4)
{
const std::int64_t stratumMinZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
* layoutChunkWidthSubvoxels;
* 32 * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t stratumMaxZ = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
* layoutChunkWidthSubvoxels;
* 32 * WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t maximumExceptionExtent = 1024
* WorldAssemblySubvoxelsPerVoxel;
for (std::size_t axis = 0; axis < 3; ++axis)
@@ -579,7 +420,6 @@ namespace world_assembly_detail
std::uint64_t hash = StartHash();
HashWord(hash, recipe.schemaVersion, 4); HashWord(hash, recipe.evaluatorVersion, 4);
HashWord(hash, recipe.unitsPerVoxel, 4); HashWord(hash, recipe.seed, 4);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutChunkWidthSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeCellSizeSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeWarpSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeWarpWavelengthSubvoxels), 8);
@@ -621,8 +461,6 @@ namespace world_assembly_detail
HashWord(hash, static_cast<std::uint64_t>(value), 8);
}
}
if (recipe.surfaceProfile.version != 0)
HashSurfaceProfile(hash, recipe.surfaceProfile);
return hash;
}
@@ -682,8 +520,6 @@ namespace world_assembly_detail
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);
}
if (stratum.surfaceProfile.version != 0)
HashSurfaceProfile(hash, stratum.surfaceProfile);
return hash;
}
@@ -786,66 +622,12 @@ namespace world_assembly_detail
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,
std::string& error)
{
if (!value.IsObject() || !ReadU32(value.Get("schema_version"), recipe.schemaVersion)
|| !ReadU32(value.Get("evaluator_version"), recipe.evaluatorVersion)
|| !ReadU32(value.Get("units_per_voxel"), recipe.unitsPerVoxel)
|| !ReadInteger(value.Get("layout_chunk_width_q"), recipe.layoutChunkWidthSubvoxels)
|| !ReadU32(value.Get("seed"), recipe.seed)
|| !ReadInteger(value.Get("biome_cell_size_q"), recipe.biomeCellSizeSubvoxels)
|| !ReadInteger(value.Get("biome_warp_q"), recipe.biomeWarpSubvoxels)
@@ -859,10 +641,6 @@ namespace world_assembly_detail
|| !ReadInteger(value.Get("climate_moisture_wavelength_q"), recipe.biomeClimateMoistureWavelengthSubvoxels)
|| !ReadInteger(value.Get("climate_relief_contrast_q16"), recipe.biomeClimateReliefContrastQ16)))
{ 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())
{
WorldAssemblyBiomeProfileV1 biome;
@@ -915,7 +693,6 @@ namespace world_assembly_detail
out << "{\"schema_version\":" << recipe.schemaVersion
<< ",\"evaluator_version\":" << recipe.evaluatorVersion
<< ",\"units_per_voxel\":" << recipe.unitsPerVoxel
<< ",\"layout_chunk_width_q\":" << recipe.layoutChunkWidthSubvoxels
<< ",\"seed\":" << recipe.seed
<< ",\"biome_cell_size_q\":" << recipe.biomeCellSizeSubvoxels
<< ",\"biome_warp_q\":" << recipe.biomeWarpSubvoxels
@@ -925,8 +702,6 @@ namespace world_assembly_detail
out << ",\"climate_relief_wavelength_q\":" << recipe.biomeClimateReliefWavelengthSubvoxels
<< ",\"climate_moisture_wavelength_q\":" << recipe.biomeClimateMoistureWavelengthSubvoxels
<< ",\"climate_relief_contrast_q16\":" << recipe.biomeClimateReliefContrastQ16;
if (recipe.surfaceProfile.version != 0)
out << ",\"surface_profile\":" << SurfaceProfileJson(recipe.surfaceProfile);
out << ",\"content_hash\":\"" << HashText(recipe.contentHash) << "\",\"biomes\":[";
for (std::size_t i = 0; i < recipe.biomes.size(); ++i)
{
@@ -1015,10 +790,6 @@ namespace world_assembly_detail
{ error = "world assembly stratum has malformed optional evaluator-2 fields"; return false; }
if(hasWaterBoolean)stratum.waterEnabled=waterEnabled.Boolean(false);
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");
if (!std::holds_alternative<std::nullptr_t>(routeConstraintsValue.Data))
{
@@ -1090,8 +861,7 @@ namespace world_assembly_detail
|| !ReadI32(islandValue.Get("bottom_chunk_z"), entry.bottomChunkZ))
{ error = "world assembly floating-island slot is missing its resolved strate bounds"; return std::nullopt; }
const std::string nestedJson = JsonText(islandValue.Get("manifest"));
auto parsedIsland = FloatingIslandManifestV1::FromJson(
nestedJson, error, manifest.recipe.layoutChunkWidthSubvoxels);
auto parsedIsland = FloatingIslandManifestV1::FromJson(nestedJson, error);
if (!parsedIsland) return std::nullopt;
entry.manifest = std::move(*parsedIsland);
manifest.floatingIslands.push_back(std::move(entry));
@@ -1589,196 +1359,6 @@ namespace world_assembly_detail
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
{
const WorldAssemblyRecipeV1* recipe = nullptr;
@@ -1827,61 +1407,6 @@ namespace world_assembly_detail
HeightfieldPointSample out;
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 auto& primary=recipe.biomes[selection.primary];
const auto& neighbor=recipe.biomes[selection.neighbor];
@@ -2209,16 +1734,6 @@ std::optional<WorldAssemblyManifest> WorldAssemblyManifest::FromJson(
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
|| !world_assembly_detail::ValidateRecipe(recipe, error))
{ if (error.empty()) error = "world assembly manifest or plan version is unsupported"; return false; }
@@ -2236,15 +1751,15 @@ bool WorldAssemblyManifest::finalize(std::string& error)
{ error = "SurfaceWorld stratum recipe seed does not match the world assembly season seed"; return false; }
stratum.recipeOverride.contentHash = world_assembly_detail::HashRecipe(stratum.recipeOverride);
}
if (!world_assembly_detail::ValidateStratum(stratum, recipe, error)) return false;
if (!world_assembly_detail::ValidateStratum(stratum, error)) return false;
if (recipe.evaluatorVersion >= 4)
{
if (stratum.routeConstraints.version != 1 || stratum.route.size() < 2)
{ error = "world assembly evaluator 4 requires a versioned entrance-to-exit route"; return false; }
const std::int64_t bottomZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
* recipe.layoutChunkWidthSubvoxels;
* 32 * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t topZ = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
* recipe.layoutChunkWidthSubvoxels;
* 32 * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t halfHeight = stratum.routeConstraints.capsuleHeightSubvoxels / 2;
for (const auto& point : stratum.route)
{
@@ -2274,12 +1789,14 @@ bool WorldAssemblyManifest::finalize(std::string& error)
const auto& Scale = islands.manifest.recipe;
if (Scale.ownerToCoreScaleNumerator == 0 || Scale.ownerToCoreScaleDenominator == 0)
{ error = "floating-island owner-to-Core scale is invalid"; return false; }
if(Scale.layoutChunkWidthSubvoxels<=0)
{error="floating-island strate has no resolved layout chunk width";return false;}
const std::int64_t bottomQ = static_cast<std::int64_t>(islands.bottomChunkZ)
*Scale.layoutChunkWidthSubvoxels;
const std::int64_t topQ = (static_cast<std::int64_t>(islands.topChunkZ)+1)
*Scale.layoutChunkWidthSubvoxels;
const double CoreVoxelsPerOwnerChunk = static_cast<double>(CoreTileVoxelCount)
* Scale.ownerToCoreScaleNumerator / Scale.ownerToCoreScaleDenominator;
const std::int64_t bottomQ = static_cast<std::int64_t>(std::llround(
static_cast<double>(islands.bottomChunkZ) * CoreVoxelsPerOwnerChunk
* WorldAssemblySubvoxelsPerVoxel));
const std::int64_t topQ = static_cast<std::int64_t>(std::llround(
static_cast<double>(static_cast<std::int64_t>(islands.topChunkZ) + 1)
* CoreVoxelsPerOwnerChunk * WorldAssemblySubvoxelsPerVoxel));
if (islands.manifest.recipe.layoutMinimumZSubvoxels < bottomQ
|| islands.manifest.recipe.layoutMaximumZSubvoxels > topQ)
{ error = "floating-island recipe layout escapes the resolved owner strate bounds"; return false; }
@@ -2359,9 +1876,6 @@ std::string WorldAssemblyManifest::ToJson() const
if (stratum.hasRecipeOverride)
out << ",\"recipe_override\":"
<< world_assembly_detail::RecipeJson(stratum.recipeOverride);
if (stratum.surfaceProfile.version != 0)
out << ",\"surface_profile\":"
<< world_assembly_detail::SurfaceProfileJson(stratum.surfaceProfile);
out
<< ",\"content_hash\":\"" << world_assembly_detail::HashText(stratum.contentHash)
<< "\",\"features\":[";
@@ -2437,8 +1951,6 @@ bool Heightfield2D::isValid() const noexcept
|| recipe_->biomes.empty() || recipe_->biomes.size() > world_assembly_detail::MaxBiomeCount
|| recipe_->biomeCellSizeSubvoxels <= 0 || recipe_->biomeWarpWavelengthSubvoxels <= 0
|| recipe_->biomeWarpSubvoxels < 0 || recipe_->biomeBorderBlendSubvoxels < 0
|| recipe_->surfaceProfile != plan_->surfaceProfile
|| recipe_->surfaceProfile.version > 1
|| plan_->strateIndex < 0 || plan_->topChunkZ < plan_->bottomChunkZ
|| plan_->capWavelengthSubvoxels <= 0 || plan_->capUndulationSubvoxels < 0)
return false;
@@ -2455,32 +1967,6 @@ bool Heightfield2D::isValid() const noexcept
std::array<std::int64_t,2> Heightfield2D::capHeightBounds() const noexcept
{
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 Maximum=-std::numeric_limits<long double>::infinity();
const auto IncludeBiome=[&](const WorldAssemblyBiomeProfileV1& Biome)
@@ -2514,7 +2000,7 @@ std::array<std::int64_t,2> Heightfield2D::capHeightBounds() const noexcept
if(recipe_->evaluatorVersion>=5)
{
const long double TopSubvoxels=(static_cast<long double>(plan_->topChunkZ)+1.0L)
*recipe_->layoutChunkWidthSubvoxels;
*CoreTileVoxelCount*WorldAssemblySubvoxelsPerVoxel;
Minimum=std::min(Minimum,TopSubvoxels);
Maximum=std::min(Maximum,TopSubvoxels);
}
@@ -2533,7 +2019,7 @@ std::int64_t Heightfield2D::farSurfaceCapHeightSubvoxels(std::int64_t x,
// Clip only the far render surface. Keep the near density evaluator bit-identical.
// Borner uniquement le plafond lointain. L'evaluateur de densite proche reste identique.
const std::int64_t ownedUpperBoundary=(static_cast<std::int64_t>(plan_->topChunkZ)+1)
*recipe_->layoutChunkWidthSubvoxels;
*CoreTileVoxelCount*WorldAssemblySubvoxelsPerVoxel;
return std::min(column.capHeightSubvoxels,ownedUpperBoundary);
}
@@ -2686,15 +2172,6 @@ HeightfieldBoxClass Heightfield2D::classifyLatticeBox(std::int64_t minimumX,
&&feature.minimumSubvoxels[2]<=maximumZ&&feature.maximumSubvoxels[2]>=minimumZ)
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)
*WorldAssemblySubvoxelsPerVoxel;
if((maximumX-minimumX)%stride!=0||(maximumY-minimumY)%stride!=0
@@ -2737,29 +2214,7 @@ 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 capMin=plan_->capHeightSubvoxels-plan_->capUndulationSubvoxels;
std::int64_t capMax=plan_->capHeightSubvoxels+plan_->capUndulationSubvoxels;
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)
if(recipe_->evaluatorVersion>=2)
{
for(const auto& biome:recipe_->biomes)
{
@@ -2843,8 +2298,8 @@ FieldInfluenceBounds Heightfield2D::influenceBounds() const noexcept
{
FieldInfluenceBounds out;
if (!isValid()) return out;
out.minimumZSubvoxels = static_cast<std::int64_t>(plan_->bottomChunkZ) * recipe_->layoutChunkWidthSubvoxels;
out.maximumZSubvoxels = (static_cast<std::int64_t>(plan_->topChunkZ) + 1) * recipe_->layoutChunkWidthSubvoxels;
out.minimumZSubvoxels = static_cast<std::int64_t>(plan_->bottomChunkZ) * 32 * WorldAssemblySubvoxelsPerVoxel;
out.maximumZSubvoxels = (static_cast<std::int64_t>(plan_->topChunkZ) + 1) * 32 * WorldAssemblySubvoxelsPerVoxel;
return out;
}
@@ -2852,24 +2307,6 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
{
FieldDetailEnvelope 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
+plan_->capDetailAmplitudeSubvoxels*5/2+plan_->capRidgeAmplitudeSubvoxels;
std::int64_t minimumFeature = std::numeric_limits<std::int64_t>::max();
@@ -3005,8 +2442,7 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
&result.capBoxes,false,"cap");
}
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)
{
const std::int64_t d=16*WorldAssemblySubvoxelsPerVoxel;
@@ -3046,8 +2482,7 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
// Controle negatif : cette plage unilaterale exclut un echantillon connu; l'audit doit rejeter cette borne mensongere.
const FieldInterval deliberatelyWrong{true, controlPoint + 1, controlPoint + 2};
result.negativeControlDetected = !world_assembly_detail::InInterval(deliberatelyWrong, controlPoint);
const bool borderCoverage=recipe->surfaceProfile.version!=0
||recipe->biomes.size()<2||recipe->biomeBorderBlendSubvoxels==0
const bool borderCoverage=recipe->biomes.size()<2||recipe->biomeBorderBlendSubvoxels==0
||result.biomeBorderBoxes>0;
const bool cliffCoverage=!hasCliffProfile||result.cliffBoxes>0;
const bool exceptionCoverage=std::none_of(plan->features.begin(),plan->features.end(),
@@ -1,263 +0,0 @@
# VoxelForge strata, units, and generation layers
**Design round L20 — 2026-10-20**
## Summary
Author strate layout in metres and give each strate a voxel size. Keep Core's internal tile at 32³
voxels; its physical width follows that strate's voxel size and the tile's LOD. Use one director to
resolve the season, layout, seed, generator recipe, resolution, and interfaces before workers start.
Generators then evaluate an immutable, common field contract. A shared composition layer applies
strata boundaries, gaps, seals, edits, caches, and proofs. The mesher consumes that composed field
and generic surface hints; it should not branch on SurfaceWorld, FloatingIslands, or feature type IDs.
The code already has parts of this shape, but not the boundaries between them. In particular,
`FStandaloneShapeWorld` is both a dispatch layer and a collection of field adapters, while
`World::generateTile` contains generator-aware far-surface and feature refinement. The Unreal
adapter currently translates two different meanings of “chunk” into Core coordinates.
The L19 ledges are not explained by a different density recipe per LOD. The ring audit found
upward-facing terrain from the same lower SurfaceWorld field at different extracted heights on
adjacent LODs. At the default 25 cm Core voxel size, LOD1, LOD2, and LOD3 sample at 0.5 m, 1 m,
and 2 m. The maximum LOD1→2 ring difference is 1.49862 m; LOD2→3 reaches 7.73318 m, with 8
no-hit samples among 308. An explicit shared surface contract can give all LODs common boundary
geometry and a controlled transition band. A planar ceiling alone will not fix these ground ledges.
One conversion issue needs an explicit preservation target. The current Core path maps one
SurfaceWorld slot chunk to 32 Core voxels (8 m at 25 cm), but one FloatingIslands slot chunk to
32 owner voxels at 12.5 cm (4 m). Converting each current interpretation independently into metres
can preserve each field's current positions, but it does not make shared chunk indices one common
physical boundary. The converter must record the actual per-strate bounds and any resulting gap or
overlap before choosing a unified interface. Do not promise a no-movement migration from a single
global “metres per chunk” constant.
Metres are not a runtime cost concern. Resolution is. Halving voxel spacing in all three axes means
about eight times as many density samples for the same physical volume, plus more tiles, queue work,
and cache entries because each internal tile remains 32³ voxels. With 25 cm as the default for all
strates, the proposed starting resolution matches today's Core sampling resolution.
## 1. Current code mapped to the three layers
There is no single class called “director” today. The director work is split between Unreal's
season/layout code, the Core adapter, and Core's tile scheduler.
| Intended layer | Current location and symbols | Current responsibility and boundary issue |
|---|---|---|
| **Director / orchestrator** | `VoxelStrateComposer.cpp`; `VoxelSeasonManifest.cpp`; `VoxelStrateManager.cpp` (`Initialize`, `GetGeneratorTypeForChunk`, `GetGenerationParams`); `VoxelWorld.cpp` (`BuildDesiredTiles`, tile scheduling, epochs); `VoxelForgeCoreAdapter.cpp` (`BuildSurfaceStratumPlan`, `BuildFloatingIslandStratumPlan`, `AttachValidatedWorldAssembly`) | Composes season params and stacked slots, resolves generator kind and seed, and serializes the Core snapshot. `WorldAssemblyManifest`, `WorldAssemblyPlanV1`, and their `FromJson`/`finalize`/`validate` functions in `WorldAssemblerInternal.h` are the current plan/manifest format. Resolution and bounds are not fully resolved here: Core re-derives them using generator-specific conversions. `TopWorldZ`, `BottomWorldZ`, `HeightInChunks`, and `InterStrateGapChunks` remain chunk/voxel-coordinate based in owner-facing data. |
| **Generators** | SurfaceWorld: `Heightfield2D` in `VoxelForgeCore.h` and `WorldAssemblerInternal.h`; plan construction in `VoxelForgeCoreAdapter.cpp`. FloatingIslands: `FloatingIslandFieldV1` in `VoxelForgeCore.h` and `FloatingIslandsInternal.h`; plan construction in the adapter. Legacy: `UVoxelGenerator::GetDensityAt` in `VoxelGenerator.cpp`, `VoxelDensityOpStack.cpp`, and `VoxelCaveMorphology.cpp`. | SurfaceWorld and FloatingIslands have distinct plan and evaluator APIs. Legacy generation still asks the manager for the current chunk's params and generator type. In Core, the “generator” boundary is obscured by `FStandaloneShapeWorld`, which selects native fields or falls back to the copied owner generator. |
| **Shared field / composition library** | Noise and fixed-point evaluation: `WorldAssemblerInternal.h`, `FloatingIslandsInternal.h`. CPU/HLSL far-field math: `VoxelForgeFieldShared.h`. FP contract: `CoreNamespacePreamble.h`. Feature culling and caches: `BuildWorldAssemblyFeatureTileListV1`, `ScopedWorldAssemblyFeatureQueriesV1`, `SharedColumnCache.h`, `ThreadLocalStorage.h`. Field bounds/proofs: `Heightfield2D::interval`, FloatingIslands bounds, `FStandaloneShapeWorld::ClassifyBoxDetailed` and `BuildShapeDependencyProof`. Density seals: `VoxelDensityPrimitives.h`. | Useful shared pieces exist, but only for subsets of recipes. Noise is not one library used by every legacy and native generator. Bounds, density, and output identity still use subvoxels of a Core voxel. The canonical stratum bounds descriptor `FResolvedStratumBounds` is private to `ShapeWorldInternal.h`, not part of the plan contract. `AppendStratumBoundaryCeilings` is under far-shell/meshing code, even though the boundary meaning is shared composition policy. |
| **Mesher** | `UVoxelMarchingCubesMesher::GenerateMesh` in `VoxelMarchingCubesMesher.cpp`; dispatch and mesh assembly in `World::generateTile` in `VoxelForgeCore.cpp`; `FarShellsInternal.h`; hybrid sharp support in `VoxelHybridSharpMesher.h` and `VoxelMarchingCubesMesher.cpp`. | MC, skirts, far shells, connector repair panels, LOD refinement, and cap repair are spread across the production mesher and Core tile builder. `World::generateTile` scans recipe feature type IDs, refines type-4 shelves only at LOD0–2, selects a native SurfaceWorld horizon, and invokes separate ground/cap repair. `FStandaloneShapeWorld::BuildFarFieldTileInput` constructs FloatingIslands connector panels. These are recipe-aware mesh paths. |
### What crosses a boundary today that should not
- `FStandaloneShapeWorld` reads `UVoxelStrateManager`, `FStrateSlot`, `WorldAssemblyManifest`, legacy
generator params, and generator type. It selects a recipe, evaluates a field, resolves its bounds,
applies a seal, and builds GPU far-field input. That combines director, generator adapter, shared
field, and renderer responsibilities.
- `World::generateTile` knows `WorldAssemblyPlanV1`, SurfaceWorld heightfields, feature numeric IDs,
and FloatingIslands presence. The generic mesher should receive capabilities and surface data,
not know those recipe identities.
- The adapter exports one layout but encodes physical positions in `WorldAssemblySubvoxelsPerVoxel`.
That makes a physical length's fixed integer depend on the selected Core voxel size.
- Core `WorldAssemblyStratumPlanV1` carries `topChunkZ`/`bottomChunkZ`; validation in
`WorldAssemblerInternal.h` multiplies those values by `32`. FloatingIslands additionally stores
its own owner-to-Core size ratio and metric-equivalent bounds in Core-voxel units.
- The Core boundary path calls `GetResolvedStratumBounds` differently for SurfaceWorld and
FloatingIslands. The director should resolve a single physical boundary record once.
- Legacy density still reaches into manager-owned params and operations. It must become a generator
adapter that consumes the same resolved request as a native recipe, or remain explicitly marked as
a compatibility route until it can be ported.
## 2. Violations, ranked by risk
Rank 1 is the highest risk to correct physical layout and cross-LOD output.
| Rank | Risk | Violation and evidence | Why it matters |
|---:|---|---|---|
| 1 | **Critical** | **No single physical bounds/interface contract.** `FResolvedStratumBounds` in `ShapeWorldInternal.h` derives SurfaceWorld bounds from `Slot.*ChunkZ × 32`, but derives native FloatingIslands bounds from `layout*ZSubvoxels` and an owner/Core scale ratio. `GetResolvedStratumBounds` is then reused by field classification, far-field protection, and far ceiling generation. | A shared logical slot boundary can map to different metre positions depending on generator. This can make an apparent seam a gap, overlap, or misplaced terrain surface before the mesher samples it. It also makes “preserve the old heights” ambiguous unless the old per-field physical bounds are recorded. |
| 2 | **Critical** | **LOD extraction has no required shared surface representation.** `World::generateTile` uses normal MC sampling for LOD1–3; native SurfaceWorld ground refinement is only invoked at LOD4+. L19's strict ring probe excludes skirts and masked triangles and measured 149.862 cm max at LOD1→2 and 773.318 cm max at LOD2→3. | A coordinate-stable density function does not guarantee equal extracted surfaces when sample grids differ. Thin seals can be skipped, and a heightfield root can shift with coarse XY/Z samples. This is the measured ledge failure. |
| 3 | **High** | **Chunk, owner-voxel, Core-voxel, and fixed-point units are mixed.** `WorldAssemblyFixed` in the Unreal adapter serializes lengths as 1/256 Core voxel. SurfaceWorld plan bounds use `Slot.BottomChunkZ * 32` and `(TopChunkZ-BottomChunkZ+1)*32`. FloatingIslands uses `32 × ownerToCoreScaleNumerator / denominator`. `ShapeWorldInternal.h` reverses Core Z to legacy chunk coordinates with `/ GetCoreVoxelsPerOwnerChunk()`. | A parameter's physical interpretation depends on generator and runtime Core size. `/32`, `*32`, and scale-ratio logic are reasonable only in a single boundary adapter or for the internal fixed tile; they are incorrect as the authored layout contract. |
| 4 | **High** | **The Unreal adapter and manager both resolve the same layout.** `VoxelStrateManager.cpp` creates chunk slots and per-generator runtime params; `VoxelForgeCoreAdapter.cpp` re-reads those params and builds distinct native plans; `ShapeWorldInternal.h` maps Core samples back through the manager again. `WorldAssemblyStratumPlanV1::topChunkZ/bottomChunkZ` are checked against the manager in `AttachValidatedWorldAssembly`. | The runtime can validate matching integer slots without validating that the resulting physical bounds mean the same thing for both generators. The future adapter should serialize already-resolved metres and per-strate resolution. |
| 5 | **High** | **Generators read director state.** Legacy `UVoxelGenerator::GetDensityAt` asks the manager for type/params and builds/refetches operator stacks. Core native lookup also asks the manager which generator applies at a Core sample. | The generator cannot be a pure evaluator of a resolved recipe. It is hard to cache, audit, or replay independently, and unit conversion is repeated in worker code. |
| 6 | **High** | **Boundary behavior is split between recipe and shared paths.** FloatingIslands geometry v3 special-cases top seal evaluation in `NativeFloatingIslandDensityAt`; older geometry versions retain two-sided seals. `VF_ApplyBoundarySeal`/`VF_ApplyUpperBoundarySeal` provide shared density math, while `AppendStratumBoundaryCeilings` separately patches missing mesh coverage. SurfaceWorld also owns a heightfield cap. | Density closure, boundary ownership, and visible cap geometry can disagree. A shared operator alone does not guarantee a shared, LOD-stable surface. Preserve versioned historical fields during migration, then route new plans through one contract. |
| 7 | **Medium-high** | **Mesher contains recipe policy.** `World::generateTile` has SurfaceWorld native-horizon selection, a SurfaceWorld-only far skip condition, type-2 sharp boxes, type-4 shelf refinement, separate native ground/cap refinement, and a `farHorizonVersion` identity. `ShapeWorldInternal.h` creates connector panel repairs from FloatingIslands. | A new recipe requires mesher edits and generator details leak into extraction. The mesher should consume explicit surfaces, generic feature constraints, and a requested error/refinement hint. |
| 8 | **Medium-high** | **Legacy paths do not share the new generator format.** `UVoxelGenerator`, operator stacks, morphology caches, Core's native `Heightfield2D`, and `FloatingIslandFieldV1` have separate recipe/parameter routes. | “One generator format” is not yet true. A migration wrapper is needed so behavior can be ported in small, hash-checked steps instead of reimplementing all legacy archetypes at once. |
| 9 | **Medium** | **Shared helpers and numeric policies are partial.** Core's FP preamble/self-test applies to Core translation units; fixed-point noise is implemented in assembler code, island noise is in a separate implementation, and far CPU/HLSL math has its own shared header. Feature bounds/caches are more reusable than the field interfaces. | Keep the tested Core determinism contract, but promote the shared numeric/noise primitives only where their exact semantics can be versioned. “Shared” code is not automatically cross-platform identical if separate float evaluators remain. |
### Unit measurements relevant to migration
`CHUNK_SIZE` is 32 and `VOXEL_SIZE` is 12.5 cm, so the owner's legacy chunk is 4 m
([`VoxelTypes.h`](../Source/VoxelForge/Public/VoxelTypes.h), [`Round5`](Research/WorldAssemblerM3/Round5/REPORT.md)). The current default Core voxel is 25 cm. In the current native Core routes:
| Route | Current conversion | Physical chunk width at default | Evidence |
|---|---|---:|---|
| SurfaceWorld | `32` Core voxels per slot chunk | 8 m | `BuildSurfaceStratumPlan` uses chunk bounds directly in Core voxel units; L18 records the preserved `topChunkZ × 32` mapping. |
| FloatingIslands | `32 × 125000/250000 = 16` Core voxels per owner chunk | 4 m | `BuildFloatingIslandStratumPlan` serializes the ratio and applies it to layout bounds; `GetCoreVoxelsPerOwnerChunk` uses the same ratio. |
This confirms the claimed factor of two. It does **not** prove that this conversion alone caused
the L19 ring delta. L19 isolated the observed step to coarse extraction of lower SurfaceWorld
terrain, and the reported LOD4+ horizon refiner cannot affect those LOD1→2 and LOD2→3 values.
## 3. Generator contract and one-time interface handling
### What the director resolves before generation
The director should publish an immutable `ResolvedWorld` (exact type/schema is an implementation
choice) containing:
- a stable world/season identity and seed;
- an ordered list of strates with a stable ID, generator kind and recipe version;
- each strate's physical minimum and maximum bounds in metres, including height as their difference;
- a voxel size in metres per strate (25 cm default for every strate);
- route points, island anchors/connectors, and all top/bottom/ceiling positions in the same physical
coordinate space;
- one resolved interface record per adjacent pair, including any explicit void, bedrock gap,
sealed contact, upper cap, passage, or open boundary;
- any season-level values that affect world output, such as routes, islands, water datum, or biome
selection, already attached to their owning strate or interface;
- a canonical identity over the sorted resolved layout, seed, resolution map, recipes, and interface
records.
Do not make generators ask the director for the active `FStrateSlot`, chunk number, season, or
neighbour generator while evaluating a point.
### Request to every generator
A worker request should give the generator:
1. The immutable resolved recipe and stable stratum ID.
2. Physical query/tile bounds in metres, including the exact halo requested by the mesher.
3. The canonical sample coordinates or a clearly defined lattice origin and step. Voxel coordinates
are useful as an internal derived lattice, but metres/physical fixed units are the identity space.
4. The resolved per-strate voxel size and seed. LOD can be included as a sampling hint; the canonical
field value itself must not change just because the caller chose another LOD.
5. Read-only interface descriptors that touch the request bounds. Pass boundary records, not a
pointer back to the neighbour's manager or mutable generator.
### Result from every generator
The common result should provide a deterministic field evaluator and its declared capabilities:
- signed density using one documented solid/air sign convention;
- optional material or surface-class data;
- conservative interval/classification proofs and conservative feature/influence bounds, or an
explicit “unknown, evaluate” result;
- optional semantic 2D surfaces such as a ground heightfield or a cap, in physical coordinates;
- a detail envelope / maximum safe sample spacing hint. It is a hint to generic LOD/refinement
policy, not permission for a recipe to choose its own mesh path;
- generator/schema/evaluator versions for stable plan and output identity.
Keep the established fail-open behavior: an unknown interval or influence bound causes evaluation,
not a solid/air skip. Existing feature candidate lists and worker caches fit this contract as
optimizations beneath the evaluator.
### Interfaces, seals, caps, and voids belong to shared composition
The director resolves the meaning and exact physical location of each interface once. The shared
composition layer then combines generator fields and applies boundary semantics in a fixed order:
1. Evaluate each generator only for its declared physical domain, retaining its ordinary
field/feature behavior.
2. Apply the interface contract once: for example, a physically measured seal band, a cap surface,
open void, bedrock gap, or permitted passage. The contract declares ownership, sidedness, material
class, collision/support policy, and whether player edits may cut it. A gap is data, not an
accidental “no generator found” branch.
3. Apply world edits through the canonical composed field, so both sides see the same edit in
physical space.
4. Give the resulting field, proofs, and any explicit surfaces to the mesher.
This centralizes the semantics currently spread across generator density wrappers,
`FResolvedStratumBounds`, and `AppendStratumBoundaryCeilings`. Existing versioned manifests still
need compatibility adapters: do not silently reinterpret old `boundarySealSubvoxels` values.
For two different generator kinds that meet at a plane, both should reference the same stable
interface ID and exact physical boundary. Their density fields can remain independent; shared
composition owns the boundary rule. If the plan intentionally has open space between them, the
resolved layout records that metre-valued interval explicitly.
## 4. Why LOD changes thin features, and the interface surface contract
MC samples density at lattice corners. A thin seal or cap can be narrower than one coarse cell, so
the coarse grid may see no sign change at all. If it does see a crossing, interpolation uses the
coarse cell's endpoint values; its root can differ from the finer grid's root. A shared analytic
density function guarantees the same values at the same physical coordinates, not the same triangles
from different sample grids.
For a physical strate boundary, use an explicit surface descriptor with exact height/shape in metres
and a globally stable sample phase. Every LOD must use common anchors along tile and LOD transition
edges. The mesher can fill the interior at its chosen rate, then build a deterministic transition
band from those common anchors into its coarser interior. A seal/cap should not rely on a sub-cell
thin density strip surviving arbitrary MC sampling.
For the L19 lower-ground ledge, the same rule needs a SurfaceWorld ground profile available to the
mesher as semantic surface data. A shared planar boundary cap would only close the strate ceiling;
it would not align these upward-facing ground triangles. The surface can be band-limited by LOD, but
the adjacent LODs must share their transition samples and be checked against a physical error
tolerance. If that cannot be met with a transition band, refine only the affected boundary tiles.
The costs are extra boundary vertices/triangles and some CPU work to construct common transition
strips. Collision/support must consume the same interface representation where that surface is
collision-relevant. The owner would see caps and ledges hold one physical height as the camera moves
between LODs, instead of steps or holes appearing at the ring. Authoring remains in metres; internal
tile and transition details stay hidden.
This is not free geometry, but it is bounded to boundaries and LOD transition bands. L19 has already
shown why “add a thin density seal and sample it normally” is not a sufficient all-LOD guarantee.
## 5. Keep the per-strate resolution door open
Yes. A 25 cm per-strate default preserves the current Core sample spacing, and the common physical
field coordinate contract leaves room for later different sizes. Do not enable mixed resolutions
until the Round5 boundary contract is met:
1. Canonical physical-space field queries and explicit ownership at a shared boundary.
2. Deterministic transition meshes with common LOD ancestry on both resolutions.
3. Physical-space edit rasterization/replay that reaches both grids exactly once, with no holes or
double application.
4. A stable, canonically sorted strate-ID-to-size map included in plan/season identity and border
hashes.
5. Streaming and collision rules for tiles that touch or span two resolutions.
At today's one-size setting, the size map has one value (250,000 microns) for every strate. It adds
no mixed-resolution mesh, edit, or streaming cost. Tile width remains `32 × voxel size × 2^LOD`;
different future sizes change physical tile width and workload, but never change the 32³ internal
tile format.
## 6. Migration plan with “worlds don't move” checks
The checkpoints distinguish expected equality from a deliberately changed interface mesh. A plan
or schema identity can change even when field output remains equal; record both identities and mesh
hashes separately.
| Step | Work | Expected equality / check | Changes output? / Unreal PC? |
|---:|---|---|---|
| 0 | Freeze the L19 evidence as the baseline: W12, Clang with FP contraction off; retain the whole-view hashes, full-detail hashes, strict ceiling audit, captures, and FI/SW ring tables. | Baseline hashes recorded below; pin the protected SW LOD0–2 hashes too. Capture byte/pixel comparisons and ring samples must retain location, LOD, and skirt exclusion. | No. No new build needed for this design round. |
| 1 | Add a versioned resolved-metre layout representation and a read-only conversion/audit path. The audit reports, per strate, old input units, resolved old physical bounds, new metre bounds, voxel size, interfaces, and identity. Keep the old evaluator active. | Exact old-vs-new resolved positions at all authored top, bottom, height, route, island, and cap anchors. Old Core density/mesh/payload/topology hashes remain equal where the conversion is exactly representable; otherwise list the precise quantization delta before accepting a new baseline. | No. Core parser/audit can be Linux-only; adapter export needs Unreal BuildHost on the owner's PC. |
| 2 | Change owner-facing layout authoring and season serialization to metres. Convert each old slot using its **current resolved physical bounds**, not one global chunk multiplier. Represent any historical gap/overlap explicitly. Default each strate's voxel size to 25 cm. | Same physical top/bottom/height values and field anchors before/after. Run default W12 hashes and matched captures. The report must call out any interface whose two old generator bounds disagree instead of silently forcing them together. | Intended no. Touches `VoxelForgeCoreAdapter.cpp`, `VoxelStrateManager`, season manifest/composer and likely `VoxelWorld`; compile and actor validation require the owner's Unreal PC. |
| 3 | Add the common generator request/result contract and adapt `Heightfield2D` and `FloatingIslandFieldV1`; wrap the legacy `UVoxelGenerator` route without moving it yet. Move manager lookups out of evaluator calls in stages. | Per-strate exact density queries, proof classifications, full-detail hashes, whole-view hashes, W1/W8/W12 determinism, and generator-to-legacy route parity. Existing current output hashes remain equal for adapters that preserve their exact physical recipe. | No intended visual change. Unreal adapter/legacy wrappers need owner-PC UHT/build and actor validation; native Core pieces can be audited on Linux. |
| 4 | Resolve interface records once in the director; route new seal, cap, void, bedrock-gap, and passage behavior through shared composition. Keep old manifest versions on compatibility adapters. | Preserve density hashes where only a mesh closure was missing. Run the strict LOD0–8 ceiling interior/perimeter audit, edge pairing, same-LOD border hashes, matched captures, and field proofs. Any newly closed boundary is a named expected mesh/payload/topology delta. | Affected boundary mesh may change. Adapter serialization needs owner-PC validation; Core audit can run here. This step fixes thin seals/caps, not the L19 ground ledges. |
| 5 | Add the generic explicit-surface and LOD-transition contract. Feed SurfaceWorld ground-profile data into common transition bands; prototype on the measured FI/SW interface. | Produce before/after matched readable captures and full ring tables. Require zero no-hit interface samples and a chosen physical seam-error limit; retain L19 hash equality for unaffected tiles/LODs. For intended changes, list changed mesh/payload/topology hashes and keep density unchanged if the field itself is unchanged. | **This is the step that fixes the L19 ledges.** It changes protected SurfaceWorld LOD2 output, so implement only after the owner approves that specific output change. Unreal capture/collision review is on the owner's PC. |
| 6 | Enable a per-strate size map behind the already-resolved contract, initially set to 25 cm on all strates. Add one non-default diagnostic size case only after the default remains green. | At all-25-cm map, reproduce the approved default hashes and captures. Include sorted size-map identity in a negative control; boundary/transition hashes must change when one size changes. Re-run edit-across-boundary, collision, streaming, and W1/W8/W12 checks. | No default change; non-default diagnostic intentionally changes sampling. Touches Unreal reflected settings, snapshot adapter, and world identity; full compile/run requires the owner's PC. |
### L19 reference hashes
Representative W12 Clang `-ffp-contract=off`, density / mesh / payload / topology:
| Whole view | L19 hash |
|---|---|
| SurfaceWorld | `c10697a8f48380c7 / 4197fc1072f0ff58 / 7b3e616ec9459851 / c879e35592bf33e5` |
| FloatingIslands | `00bc3607a7839854 / 4baa66127c2b0892 / e11ef05e851a1517 / 1278bc4b1fffb869` |
Protected SurfaceWorld LOD0–2 hashes are in the L19 report and should be retained through steps
1–4 except for explicitly approved boundary geometry. The whole-view hash gate alone cannot prove
that a ring improved; step 5 needs the strict ring table and readable captures as well.
### Owner decisions this design needs
1. **Preservation target:** confirm that migration snapshots each current generator's resolved
physical bounds (including any current overlap or gap) before later tuning them into a single
authored metre layout. If instead the initial migration should force currently different bounds
to meet, that is a deliberate world change and needs its own baseline/capture review.
2. **Ledge output:** approve the specific SurfaceWorld LOD2/transition change before step 5
implementation, as required by the existing protected-output rule.
-6
View File
@@ -83,12 +83,6 @@ 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.
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 |
|---|---|---|
| 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,89 @@
#!/usr/bin/env python3
"""Measure upward mesh faces around the FI/SW stratum interface."""
import array
import collections
import math
import struct
import sys
def read_exact(stream, size):
data = stream.read(size)
if len(data) != size:
raise EOFError(f"wanted {size} bytes, got {len(data)}")
return data
path = sys.argv[1]
low_z = float(sys.argv[2]) if len(sys.argv) > 2 else -80.0
high_z = float(sys.argv[3]) if len(sys.argv) > 3 else -45.0
with open(path, "rb") as stream:
if read_exact(stream, 8) != b"VFMSH001":
raise SystemExit("bad VFMSH001 header")
version, voxel_um, tile_count = struct.unpack("<3I", read_exact(stream, 12))
camera = struct.unpack("<3d", read_exact(stream, 24))
yaw, pitch = struct.unpack("<2d", read_exact(stream, 16))
per_lod = collections.defaultdict(lambda: {"tiles": 0, "faces": [], "spans": []})
near_tiles = collections.defaultdict(list)
for _ in range(tile_count):
key = struct.unpack("<4i", read_exact(stream, 16))
surf_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", read_exact(stream, 20))
p = array.array("f")
p.frombytes(read_exact(stream, vertex_count * 12))
n = array.array("f")
n.frombytes(read_exact(stream, vertex_count * 12))
if sys.byteorder != "little":
p.byteswap(); n.byteswap()
indices = array.array("I")
indices.frombytes(read_exact(stream, index_count * 4))
masks = read_exact(stream, mask_count)
if sys.byteorder != "little":
indices.byteswap()
lod = key[3]
z_values = []
for tri in range(index_count // 3):
if masks and masks[tri] != 0:
continue
a, b, c = indices[tri * 3:tri * 3 + 3]
if max(a, b, c) >= surf_vertices:
continue
normal_z = (n[a * 3 + 2] + n[b * 3 + 2] + n[c * 3 + 2]) / 3.0
if normal_z < 0.65:
continue
z = (p[a * 3 + 2] + p[b * 3 + 2] + p[c * 3 + 2]) / 3.0
if low_z <= z <= high_z:
z_values.append(z)
if not z_values:
continue
z_values.sort()
row = per_lod[lod]
row["tiles"] += 1
row["faces"].extend(z_values)
span = z_values[math.floor(.95 * (len(z_values) - 1))] - z_values[math.floor(.05 * (len(z_values) - 1))]
row["spans"].append(span)
extent = 32 * (1 << lod) * voxel_um / 1_000_000.0
cx = (key[0] + 0.5) * extent
cy = (key[1] + 0.5) * extent
near_tiles[lod].append((math.hypot(cx - camera[0], cy - camera[1]), key,
z_values[len(z_values) // 2], span, len(z_values)))
def q(values, fraction):
return values[math.floor(fraction * (len(values) - 1))] if values else math.nan
print(f"camera_m={camera} yaw={yaw} pitch={pitch} boundary_band_m={low_z}..{high_z}")
print("lod tiles_with_boundary_faces faces z_m_p05/median/p95 tile_span_m_p50/p95/max")
for lod in sorted(per_lod):
row = per_lod[lod]
spans = sorted(row["spans"])
print(f"{lod} {row['tiles']} {len(row['faces'])} "
f"{q(sorted(row['faces']), .05):.4f}/{q(sorted(row['faces']), .5):.4f}/"
f"{q(sorted(row['faces']), .95):.4f} "
f"{q(spans, .5):.4f}/{q(spans, .95):.4f}/{max(spans):.4f}")
print("nearest_xy_boundary_tiles_by_lod:")
for lod in sorted(near_tiles):
for distance, key, median, span, faces in sorted(near_tiles[lod])[:3]:
print(f"lod={lod} key={key} xy_distance_m={distance:.2f} "
f"boundary_median_z={median:.4f} within_tile_p05_p95_m={span:.4f} faces={faces}")
@@ -0,0 +1,143 @@
#include "Json.h"
#include "StandaloneCoreScheduler.h"
#include "StandaloneSnapshotAdapter.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <iterator>
#include <string>
namespace
{
constexpr double CoreVoxelsPerMeter = 4.0;
constexpr std::int64_t QPerCoreVoxel = VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
std::int64_t ToQ(double Meters)
{
return static_cast<std::int64_t>(std::llround(Meters*CoreVoxelsPerMeter*QPerCoreVoxel));
}
void PrintLegacyBoundarySealTerms(const VoxelForgeCore::FloatingIslandManifestV1& Manifest,
double X, double Y, double Z)
{
using namespace VoxelForgeCore;
const auto Xq=ToQ(X),Yq=ToQ(Y),Zq=ToQ(Z);
const FloatingIslandFieldV1 field(Manifest.recipe,Manifest.plan);
const std::int64_t fieldQ=field.densitySubvoxels(Xq,Yq,Zq);
const std::int64_t bottom=Manifest.recipe.layoutMinimumZSubvoxels;
const std::int64_t top=Manifest.recipe.layoutMaximumZSubvoxels;
const std::int64_t seal=Manifest.recipe.boundarySealSubvoxels;
std::int64_t bridged=fieldQ;
bool applied=false;
if(Zq>=bottom&&Zq<bottom+seal)
{bridged=std::min(bridged,-std::max<std::int64_t>(1,Zq-bottom));applied=true;}
if(Zq<=top&&Zq>top-seal)
{bridged=std::min(bridged,-std::max<std::int64_t>(1,top-Zq));applied=true;}
std::printf("[PreFixIslandTerms] xyz_m=%.3f,%.3f,%.3f native_field_q=%lld "
"legacy_solid_seal_delta_q=%lld legacy_seal_active=%d pre_fix_wrapper_q=%lld\n",
X,Y,Z,static_cast<long long>(fieldQ),
static_cast<long long>(bridged-fieldQ),applied?1:0,static_cast<long long>(bridged));
for(const auto& island:Manifest.plan.islands)
{
const bool inBounds=Xq>=island.influenceMinimumSubvoxels[0]
&&Xq<=island.influenceMaximumSubvoxels[0]
&&Yq>=island.influenceMinimumSubvoxels[1]
&&Yq<=island.influenceMaximumSubvoxels[1]
&&Zq>=island.influenceMinimumSubvoxels[2]
&&Zq<=island.influenceMaximumSubvoxels[2];
std::printf("[IslandGroup] ordinal=%u center_m=%.3f,%.3f,%.3f "
"bounds_contains_sample=%d\n",
island.ordinal,island.centerSubvoxels[0]/1024.0,island.centerSubvoxels[1]/1024.0,
island.centerSubvoxels[2]/1024.0,inBounds?1:0);
}
for(const auto& edge:Manifest.plan.clusterEdges)
{
bool inBounds=true;
const std::int64_t q[3]={Xq,Yq,Zq};
for(std::size_t axis=0;axis<3;++axis)
inBounds=inBounds&&q[axis]>=edge.influenceMinimumSubvoxels[axis]
&&q[axis]<=edge.influenceMaximumSubvoxels[axis];
std::printf("[IslandConnector] id=%016llx bounds_contains_sample=%d\n",
static_cast<unsigned long long>(edge.stableId),inBounds?1:0);
}
}
}
void PrintDensityLine(VoxelForgeCore::World& World,const char* Label,
double X,double Y,double ZMinimum,double ZMaximum,double Step)
{
using namespace VoxelForgeCore;
std::printf("[DensityLine] label=%s xy_m=(%.6f,%.6f) z_m=%.2f..%.2f step_m=%.2f\n",
Label,X,Y,ZMinimum,ZMaximum,Step);
for(double Z=ZMinimum;Z<=ZMaximum+1.0e-9;Z+=Step)
{
const Vec3d Point{X*CoreVoxelsPerMeter,Y*CoreVoxelsPerMeter,
Z*CoreVoxelsPerMeter};
const double Density=World.densityAt(Point);
const double Unclamped=World.unclampedDensityAt(Point);
const DetailFieldSample Detail=World.detailSampleAt(Point);
std::printf("[DensitySample] label=%s xyz_m=%.3f,%.3f,%.3f density_core_vox=%.9f "
"density_cm=%.3f unclamped_core_vox=%.9f cave_influence=%.6f "
"floor_influence=%.6f detail_displacement=%.6f sign=%s\n",Label,X,Y,Z,
Density,Density*25.0,Unclamped,Detail.caveInfluence,Detail.floorInfluence,
Detail.displacement,Density<0.0?"solid":"air");
}
}
int main(int argc,char** argv)
{
if(argc!=2)
{
std::fprintf(stderr,"usage: %s owner-world-with-world-assembly.json\n",argv[0]);
return 2;
}
VFJson::Value root;
std::string error;
if(!VFJson::Load(argv[1],root,error))
{std::fprintf(stderr,"owner JSON parse failed: %s\n",error.c_str());return 2;}
auto snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(root,0,3,8,false,false,error);
if(!snapshot)
{std::fprintf(stderr,"snapshot resolve failed: %s\n",error.c_str());return 2;}
const auto* assembly=snapshot.worldAssembly();
if(!assembly||assembly->floatingIslands.empty())
{std::fprintf(stderr,"FloatingIslands manifest missing\n");return 2;}
VoxelForgeCore::World world(std::move(snapshot));
VoxelForgeCore::ShapeBuildRequest request;
request.minVoxel={-96,-512,-288};
request.maxVoxel={96,96,64};
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;}
constexpr double FlatX=-11.612443923950195;
constexpr double FlatY=-118.22123718261719;
constexpr double IslandX=-6.3;
constexpr double IslandY=-19.057;
constexpr double RingX=-4.889;
constexpr double RingY=-16.008;
std::printf("[DensityRoute] flat_square_xy_m=(%.6f,%.6f) island_ground_xy_m=(%.6f,%.6f) "
"compiled=%d core_voxel_size_m=0.25 owner_to_core_chunk_voxels=16\n",
FlatX,FlatY,IslandX,IslandY,world.isCompiled()?1:0);
PrintDensityLine(world,"flat-square",FlatX,FlatY,2.5,9.0,0.25);
PrintDensityLine(world,"fi-sw-boundary",FlatX,FlatY,-62.0,-58.0,0.25);
PrintDensityLine(world,"island-center",IslandX,IslandY,-39.0,-20.0,0.5);
PrintDensityLine(world,"lod0-lod1-ring-edge",RingX,RingY,-62.0,-20.0,1.0);
const auto& island=assembly->floatingIslands.front().manifest;
for(double z:{2.75,2.99,3.00,3.01,3.25,3.49,3.50,3.51,3.75,3.99,4.00,4.01,7.75,8.00,8.25})
PrintLegacyBoundarySealTerms(island,FlatX,FlatY,z);
for(double z:{-35.0,-30.0,-25.0})
PrintLegacyBoundarySealTerms(island,IslandX,IslandY,z);
for(double z:{-60.0,-35.0,-28.0,-25.0})
PrintLegacyBoundarySealTerms(island,RingX,RingY,z);
return 0;
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Run the L18 deterministic W1/W8/W12 x5 matrix on both supported Linux builds."""
"""Run the L17 deterministic W1/W8/W12 x5 matrix on both supported Linux builds."""
import os
import re
import signal
@@ -9,18 +9,18 @@ import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l18-matrix"
OUT = ROOT / "Saved/Linux/l17-matrix"
OUT.mkdir(parents=True, exist_ok=True)
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
BINARIES = {
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux18-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux18-gcc",
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux17-final-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux17-final-gcc",
}
RECIPES = {
"floating-islands": "-10,-31,-22,0,-8",
"surface-world": "0,-35,-487.94,0,-22",
"surface-world": "0,-35,-75,0,0",
}
HASH_PATTERNS = {
"full": re.compile(r"^\[PipelineFullDetailHash\].*$", re.M),
@@ -104,29 +104,17 @@ with summary.open("w", encoding="utf-8") as f:
f.write("\t".join(str(row[key]) for key in columns) + "\n")
for recipe in RECIPES:
for compiler in BINARIES:
reference = None
for row in rows:
if row["recipe"] != recipe or row["compiler"] != compiler:
continue
signature = (row["full"], row["whole"], row["lod"], row["exact"])
if reference is None:
reference = signature
elif signature != reference:
print(f"[MatrixMismatch] recipe={recipe} workers={row['workers']} "
f"compiler={compiler} repeat={row['repeat']}", flush=True)
all_pass = False
# Compiler defaults are intentionally recorded separately: Clang uses contraction off,
# while GCC default is the owner-requested control. Report rather than hide any cross-build
# difference; worker/repeat determinism is asserted within each compiler above.
compiler_signatures = {}
reference = None
for row in rows:
if row["recipe"] == recipe:
compiler_signatures.setdefault(row["compiler"],
(row["full"], row["whole"], row["lod"], row["exact"]))
if len(set(compiler_signatures.values())) > 1:
print(f"[MatrixCompilerDifference] recipe={recipe} compilers="
f"{','.join(compiler_signatures)}", flush=True)
if row["recipe"] != recipe:
continue
signature = (row["full"], row["whole"], row["lod"], row["exact"])
if reference is None:
reference = signature
elif signature != reference:
print(f"[MatrixMismatch] recipe={recipe} workers={row['workers']} "
f"compiler={row['compiler']} repeat={row['repeat']}", flush=True)
all_pass = False
print(f"[MatrixSummary] rows={len(rows)} expected=28 result={'PASS' if all_pass else 'FAIL'} "
f"path={summary}", flush=True)
raise SystemExit(0 if all_pass and len(rows) == 28 else 1)
@@ -0,0 +1,27 @@
camera_m=(-22.0, -19.0, -22.799999237060547) yaw=0.0 pitch=-4.0 boundary_band_m=-80.0..-45.0
lod tiles_with_boundary_faces faces z_m_p05/median/p95 tile_span_m_p50/p95/max
1 64 131072 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
2 40 20480 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
3 48 24576 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
4 48 22945 -60.0000/-60.0000/-60.0000 0.0000/0.9167/1.8008
5 48 19554 -60.0000/-60.0000/-60.0000 0.0000/1.0819/1.7900
6 9 134 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
nearest_xy_boundary_tiles_by_lod:
lod=1 key=(-2, -2, -4, 1) xy_distance_m=5.39 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=2048
lod=1 key=(-2, -1, -4, 1) xy_distance_m=11.18 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=2048
lod=1 key=(-1, -2, -4, 1) xy_distance_m=14.87 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=2048
lod=2 key=(-1, 1, -2, 2) xy_distance_m=67.27 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=2 key=(1, -1, -2, 2) xy_distance_m=70.06 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=2 key=(-2, 1, -2, 2) xy_distance_m=71.87 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=3 key=(-3, -1, -1, 3) xy_distance_m=138.61 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=3 key=(-1, -3, -1, 3) xy_distance_m=141.35 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=3 key=(-3, 0, -1, 3) xy_distance_m=147.12 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=4 key=(-3, -1, -1, 4) xy_distance_m=301.38 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=4 key=(-1, -3, -1, 4) xy_distance_m=303.92 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=4 key=(-3, 0, -1, 4) xy_distance_m=309.34 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=5 key=(-3, -1, -1, 5) xy_distance_m=627.54 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=343
lod=5 key=(-1, -3, -1, 5) xy_distance_m=629.98 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=512
lod=5 key=(-3, 0, -1, 5) xy_distance_m=635.24 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.4903 faces=142
lod=6 key=(-3, 0, -1, 6) xy_distance_m=1287.71 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=2
lod=6 key=(0, -3, -1, 6) xy_distance_m=1291.28 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=16
lod=6 key=(1, 2, -1, 6) xy_distance_m=1520.36 boundary_median_z=-60.0000 within_tile_p05_p95_m=0.0000 faces=3
@@ -0,0 +1,35 @@
camera_m=(-22.0, -19.0, -22.799999237060547) yaw=0.0 pitch=-4.0 boundary_band_m=-80.0..-45.0
lod tiles_with_boundary_faces faces z_m_p05/median/p95 tile_span_m_p50/p95/max
1 65 144957 -60.4936/-60.4872/-59.5000 0.0059/0.9906/2.2272
2 75 29057 -76.1525/-61.3304/-60.7788 0.8725/2.9863/8.5642
3 87 26484 -73.0968/-63.0185/-62.4964 1.3211/11.6236/14.1057
4 48 25679 -72.6651/-62.8690/-62.2182 3.2371/15.9700/17.0106
5 48 26381 -67.4390/-62.7489/-61.8320 4.2721/11.9903/14.5467
6 14 390 -73.7183/-67.0059/-62.3980 4.0318/11.7718/12.7473
7 18 483 -74.7834/-71.1169/-67.5754 4.0317/7.3030/8.1412
8 22 176 -75.3669/-69.9656/-63.4334 3.5327/3.5370/13.7478
nearest_xy_boundary_tiles_by_lod:
lod=1 key=(-2, -2, -4, 1) xy_distance_m=5.39 boundary_median_z=-60.4712 within_tile_p05_p95_m=0.0177 faces=2048
lod=1 key=(-2, -1, -4, 1) xy_distance_m=11.18 boundary_median_z=-60.4718 within_tile_p05_p95_m=0.0158 faces=2048
lod=1 key=(-1, -2, -4, 1) xy_distance_m=14.87 boundary_median_z=-60.4550 within_tile_p05_p95_m=0.0212 faces=2048
lod=2 key=(-1, 1, -2, 2) xy_distance_m=67.27 boundary_median_z=-60.7421 within_tile_p05_p95_m=0.2955 faces=496
lod=2 key=(1, -1, -3, 2) xy_distance_m=70.06 boundary_median_z=-74.2743 within_tile_p05_p95_m=2.9398 faces=97
lod=2 key=(1, -1, -2, 2) xy_distance_m=70.06 boundary_median_z=-60.7387 within_tile_p05_p95_m=1.2725 faces=496
lod=3 key=(-3, -1, -1, 3) xy_distance_m=138.61 boundary_median_z=-62.5408 within_tile_p05_p95_m=0.3465 faces=512
lod=3 key=(-1, -3, -1, 3) xy_distance_m=141.35 boundary_median_z=-62.5378 within_tile_p05_p95_m=1.6699 faces=512
lod=3 key=(-3, 0, -1, 3) xy_distance_m=147.12 boundary_median_z=-62.5837 within_tile_p05_p95_m=0.3165 faces=512
lod=4 key=(-3, -1, -1, 4) xy_distance_m=301.38 boundary_median_z=-62.3784 within_tile_p05_p95_m=4.5901 faces=546
lod=4 key=(-1, -3, -1, 4) xy_distance_m=303.92 boundary_median_z=-62.3534 within_tile_p05_p95_m=10.4664 faces=596
lod=4 key=(-3, 0, -1, 4) xy_distance_m=309.34 boundary_median_z=-62.3696 within_tile_p05_p95_m=6.1327 faces=544
lod=5 key=(-3, -1, -1, 5) xy_distance_m=627.54 boundary_median_z=-62.0651 within_tile_p05_p95_m=9.5826 faces=523
lod=5 key=(-1, -3, -1, 5) xy_distance_m=629.98 boundary_median_z=-61.9414 within_tile_p05_p95_m=2.3891 faces=526
lod=5 key=(-3, 0, -1, 5) xy_distance_m=635.24 boundary_median_z=-62.0996 within_tile_p05_p95_m=9.3777 faces=567
lod=6 key=(-3, 0, -1, 6) xy_distance_m=1287.71 boundary_median_z=-69.5358 within_tile_p05_p95_m=4.1243 faces=26
lod=6 key=(0, -3, -1, 6) xy_distance_m=1291.28 boundary_median_z=-70.4254 within_tile_p05_p95_m=12.7473 faces=41
lod=6 key=(0, 2, -1, 6) xy_distance_m=1328.41 boundary_median_z=-63.9805 within_tile_p05_p95_m=0.0000 faces=1
lod=7 key=(0, -3, -1, 7) xy_distance_m=2596.50 boundary_median_z=-70.2015 within_tile_p05_p95_m=4.1663 faces=36
lod=7 key=(2, -1, -1, 7) xy_distance_m=2628.64 boundary_median_z=-70.1072 within_tile_p05_p95_m=3.5366 faces=8
lod=7 key=(-3, 1, -1, 7) xy_distance_m=2976.49 boundary_median_z=-70.9568 within_tile_p05_p95_m=4.8223 faces=24
lod=8 key=(-1, 2, -1, 8) xy_distance_m=5235.77 boundary_median_z=-65.8628 within_tile_p05_p95_m=3.5343 faces=8
lod=8 key=(-1, -4, -1, 8) xy_distance_m=7218.88 boundary_median_z=-71.4663 within_tile_p05_p95_m=3.5332 faces=8
lod=8 key=(-4, 0, -1, 8) xy_distance_m=7221.71 boundary_median_z=-70.6999 within_tile_p05_p95_m=3.5335 faces=8
@@ -0,0 +1,13 @@
version=1 voxel_um=250000 tiles=2370 camera_m=(-22.0, -19.0, -22.799999237060547) yaw=0.0 pitch=-4.0
lod tiles mesh_tiles vertices triangles skirt_triangles upward_surface_triangles upward_centroid_z_m_p05/median/p95 tile_up_z_span_m_p50/p95/max
0 512 15 30819 54860 9176 12102 -29.7482/-28.0275/-25.2555 0.5335/1.1856/1.9265
1 448 64 94272 163840 32768 131072 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
2 320 40 28476 45376 15296 20480 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
3 208 48 58599 93736 30968 24576 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
4 240 48 105724 164240 27638 22945 -60.0000/-60.0000/-60.0000 0.0000/0.9167/1.8008
5 144 96 96543 156615 22706 109798 -506.3981/-502.5195/-60.0000 1.7900/110.1491/138.3581
6 96 48 114212 157583 28194 91871 -506.2913/-503.3460/-411.9775 78.1123/124.2575/136.4059
7 96 48 142940 234358 31690 95029 -506.1172/-503.3633/-412.8203 90.7064/105.1855/106.9902
8 306 153 290960 421667 73118 147296 -505.8558/-503.1657/-422.5029 82.1507/93.3314/110.2594
near_camera_up_tiles (sorted by XY tile-centre distance):
key=(-2, -2, -4, 1) xy_distance_m=5.39 vertices=1473 triangles=2560 skirt_triangles=512 upward_triangles=2048 z_m=-60.000/-60.000/-60.000 tile_span_p05_p95_m=0.000
@@ -0,0 +1,13 @@
version=1 voxel_um=250000 tiles=2370 camera_m=(-22.0, -19.0, -22.799999237060547) yaw=0.0 pitch=-4.0
lod tiles mesh_tiles vertices triangles skirt_triangles upward_surface_triangles upward_centroid_z_m_p05/median/p95 tile_up_z_span_m_p50/p95/max
0 512 15 30819 54860 9176 12102 -29.7482/-28.0275/-25.2555 0.5335/1.1856/1.9265
1 448 64 94272 163840 32768 131072 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
2 320 40 28476 45376 15296 20480 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
3 208 48 58599 93736 30968 24576 -60.0000/-60.0000/-60.0000 0.0000/0.0000/0.0000
4 240 48 105724 164240 27638 22945 -60.0000/-60.0000/-60.0000 0.0000/0.9167/1.8008
5 144 96 96543 156615 22706 109798 -506.3981/-502.5195/-60.0000 1.7900/110.1491/138.3581
6 96 48 114212 157583 28194 91871 -506.2913/-503.3460/-411.9775 78.1123/124.2575/136.4059
7 96 48 142940 234358 31690 95029 -506.1172/-503.3633/-412.8203 90.7064/105.1855/106.9902
8 306 153 290960 421667 73118 147296 -505.8558/-503.1657/-422.5029 82.1507/93.3314/110.2594
near_camera_up_tiles (sorted by XY tile-centre distance):
key=(-2, -2, -4, 1) xy_distance_m=5.39 vertices=1473 triangles=2560 skirt_triangles=512 upward_triangles=2048 z_m=-60.000/-60.000/-60.000 tile_span_p05_p95_m=0.000
@@ -0,0 +1,19 @@
version=1 voxel_um=250000 tiles=2370 camera_m=(-22.0, -19.0, -22.799999237060547) yaw=0.0 pitch=-4.0
lod tiles mesh_tiles vertices triangles skirt_triangles upward_surface_triangles upward_centroid_z_m_p05/median/p95 tile_up_z_span_m_p50/p95/max
0 512 143 588792 892284 293148 300091 3.5000/7.6738/8.2568 0.4215/4.4898/4.4913
1 448 119 509571 954666 323268 265175 -60.4932/-60.4491/8.1722 0.3356/0.9926/4.2986
2 320 130 93432 150346 51220 55675 -76.2021/-60.8983/8.0124 0.3035/2.9398/8.5642
3 208 135 91227 146168 33040 51843 -70.7997/-62.4979/8.0125 1.0249/11.2411/19.6108
4 240 103 173336 278284 61076 58421 -505.3757/-411.1270/-62.3657 8.2807/88.3431/118.1829
5 144 96 137096 173158 40930 117557 -506.3330/-502.1182/-62.4087 10.4471/110.1491/138.5524
6 96 48 151997 168987 43320 92193 -506.3014/-503.3398/-410.9469 78.1123/124.2575/136.4059
7 96 96 236120 321403 61348 120078 -505.8770/-502.4482/8.0020 0.0000/103.3187/110.4079
8 306 306 553491 741221 166096 225866 -505.4486/-472.2318/8.0020 0.0000/92.1234/110.0400
near_camera_up_tiles (sorted by XY tile-centre distance):
key=(-3, -3, 0, 0) xy_distance_m=2.24 vertices=8058 triangles=11660 skirt_triangles=3796 upward_triangles=3768 z_m=3.500/3.500/7.999 tile_span_p05_p95_m=4.445
key=(-3, -3, 1, 0) xy_distance_m=2.24 vertices=668 triangles=1052 skirt_triangles=376 upward_triangles=676 z_m=8.000/8.087/8.252 tile_span_p05_p95_m=0.203
key=(-2, -2, -4, 1) xy_distance_m=5.39 vertices=2562 triangles=8192 skirt_triangles=2048 upward_triangles=2048 z_m=-60.480/-60.471/-60.457 tile_span_p05_p95_m=0.018
key=(-4, -3, 0, 0) xy_distance_m=6.08 vertices=7502 triangles=10753 skirt_triangles=3764 upward_triangles=2893 z_m=3.500/3.500/8.000 tile_span_p05_p95_m=4.486
key=(-4, -3, 1, 0) xy_distance_m=6.08 vertices=1617 triangles=2507 skirt_triangles=928 upward_triangles=1579 z_m=8.000/8.099/8.372 tile_span_p05_p95_m=0.248
key=(-3, -2, 0, 0) xy_distance_m=7.28 vertices=7772 triangles=10994 skirt_triangles=4024 upward_triangles=2874 z_m=3.500/3.500/8.000 tile_span_p05_p95_m=4.491
key=(-3, -2, 1, 0) xy_distance_m=7.28 vertices=1883 triangles=2904 skirt_triangles=1116 upward_triangles=1788 z_m=8.000/8.115/8.363 tile_span_p05_p95_m=0.280
@@ -0,0 +1,19 @@
version=1 voxel_um=250000 tiles=2370 camera_m=(-11.612443923950195, -118.22123718261719, -19.84520721435547) yaw=0.0 pitch=0.0
lod tiles mesh_tiles vertices triangles skirt_triangles upward_surface_triangles upward_centroid_z_m_p05/median/p95 tile_up_z_span_m_p50/p95/max
0 512 128 579561 840946 290676 288126 3.5000/7.7495/8.2586 0.3781/4.4867/4.4932
1 448 124 503677 964366 331082 258850 -63.8505/-60.4908/8.1722 0.2965/2.9282/5.7538
2 320 127 83460 135059 46994 53146 -71.5347/-60.5904/8.0124 0.5224/2.7093/6.0349
3 208 126 93575 151669 35042 52200 -75.6995/-61.9814/8.0125 0.5630/13.5938/55.4526
4 240 103 172283 277754 60622 58086 -505.4072/-410.6667/-62.3188 6.7752/88.3431/118.1829
5 144 96 137096 173158 40930 117557 -506.3330/-502.1182/-62.4087 10.4471/110.1491/138.5524
6 96 48 151997 168987 43320 92193 -506.3014/-503.3398/-410.9469 78.1123/124.2575/136.4059
7 96 96 236120 321403 61348 120078 -505.8770/-502.4482/8.0020 0.0000/103.3187/110.4079
8 306 306 553491 741221 166096 225866 -505.4486/-472.2318/8.0020 0.0000/92.1234/110.0400
near_camera_up_tiles (sorted by XY tile-centre distance):
key=(-2, -15, 0, 0) xy_distance_m=2.25 vertices=7941 triangles=11569 skirt_triangles=3860 upward_triangles=3613 z_m=3.500/3.500/8.000 tile_span_p05_p95_m=4.472
key=(-2, -15, 1, 0) xy_distance_m=2.25 vertices=1159 triangles=1557 skirt_triangles=684 upward_triangles=873 z_m=8.000/8.082/8.275 tile_span_p05_p95_m=0.250
key=(-1, -8, -4, 1) xy_distance_m=4.03 vertices=2562 triangles=8192 skirt_triangles=2048 upward_triangles=2048 z_m=-60.494/-60.494/-60.493 tile_span_p05_p95_m=0.001
key=(-2, -16, 0, 0) xy_distance_m=5.79 vertices=8315 triangles=12274 skirt_triangles=3928 upward_triangles=4250 z_m=3.500/7.740/8.000 tile_span_p05_p95_m=4.459
key=(-2, -16, 1, 0) xy_distance_m=5.79 vertices=677 triangles=700 skirt_triangles=520 upward_triangles=180 z_m=8.000/8.008/8.057 tile_span_p05_p95_m=0.036
key=(-1, -15, 0, 0) xy_distance_m=7.93 vertices=6918 triangles=9878 skirt_triangles=3384 upward_triangles=2398 z_m=3.500/3.500/8.000 tile_span_p05_p95_m=4.453
key=(-1, -15, 1, 0) xy_distance_m=7.93 vertices=1611 triangles=2700 skirt_triangles=652 upward_triangles=2048 z_m=8.001/8.202/8.323 tile_span_p05_p95_m=0.242
@@ -0,0 +1,12 @@
version=1 voxel_um=250000 tiles=2553 camera_m=(0.0, -35.0, -487.94000244140625) yaw=0.0 pitch=-22.0
lod tiles mesh_tiles vertices triangles skirt_triangles upward_surface_triangles upward_centroid_z_m_p05/median/p95 tile_up_z_span_m_p50/p95/max
0 512 0 0 0 0 0 nan/nan/nan nan/nan/nan
1 448 0 0 0 0 0 nan/nan/nan nan/nan/nan
2 448 0 0 0 0 0 nan/nan/nan nan/nan/nan
3 384 0 0 0 0 0 nan/nan/nan nan/nan/nan
4 320 52 139437 209316 34996 8129 -56.2002/-42.7865/-32.7998 6.2138/32.5482/53.6770
5 144 102 100254 158707 25586 96013 -506.9092/-503.1813/-378.0397 12.8110/109.5153/138.3581
6 96 61 110399 149031 17088 92803 -506.4873/-503.3776/-412.3926 53.2054/124.2575/136.4059
7 48 48 132222 219052 16356 95003 -506.1172/-503.3639/-412.8861 90.7064/105.1855/106.9902
8 153 153 229080 384768 82518 147226 -505.8568/-503.1676/-422.8789 82.0687/93.3180/110.0400
near_camera_up_tiles (sorted by XY tile-centre distance):
@@ -0,0 +1,5 @@
version=1 voxel_um=250000 tiles=960 camera_m=(-11.612443923950195, -118.22123718261719, -506.7861328125) yaw=0.0 pitch=0.0
lod tiles mesh_tiles vertices triangles skirt_triangles upward_surface_triangles upward_centroid_z_m_p05/median/p95 tile_up_z_span_m_p50/p95/max
0 512 0 0 0 0 0 nan/nan/nan nan/nan/nan
1 448 0 0 0 0 0 nan/nan/nan nan/nan/nan
near_camera_up_tiles (sorted by XY tile-centre distance):
@@ -0,0 +1,5 @@
version=1 voxel_um=250000 tiles=960 camera_m=(0.0, -35.0, -75.0) yaw=0.0 pitch=0.0
lod tiles mesh_tiles vertices triangles skirt_triangles upward_surface_triangles upward_centroid_z_m_p05/median/p95 tile_up_z_span_m_p50/p95/max
0 512 0 0 0 0 0 nan/nan/nan nan/nan/nan
1 448 76 153596 265964 51168 82938 -43.9418/-14.9892/-6.3079 0.8803/5.3022/10.8345
near_camera_up_tiles (sorted by XY tile-centre distance):
@@ -0,0 +1,5 @@
version=1 voxel_um=250000 tiles=960 camera_m=(0.0, -35.0, -42.0) yaw=0.0 pitch=-8.0
lod tiles mesh_tiles vertices triangles skirt_triangles upward_surface_triangles upward_centroid_z_m_p05/median/p95 tile_up_z_span_m_p50/p95/max
0 512 25 80260 139176 28088 34692 -43.9640/-33.5509/-32.8013 0.7072/4.9851/7.0117
1 448 131 295583 474122 103438 230953 -33.6783/3.9507/4.1428 0.3514/4.4247/10.8345
near_camera_up_tiles (sorted by XY tile-centre distance):
@@ -1,9 +0,0 @@
# L18 build resources
Limits: CPUs 0–11, `-j12`, normal priority, 6 GiB address-space cap. Governor stayed `powersave`.
These are the final builds after the cell-coverage and owner ceiling corrections.
- Clang 19, `-ffp-contract=off`: exit 0, 31.689 s, peak process RSS 436,088 KiB, peak VMS 586,240 KiB.
- GCC 14, default FP contraction: exit 0, 48.648 s, peak process RSS 903,168 KiB, peak VMS 1,014,716 KiB.
- Largest measured runtime process was the final Clang gate run at 897,276 KiB RSS. The determinism matrix peaked at 603,172 KiB and the crossing matrix at 548,664 KiB.
- An earlier GCC build was interrupted by owner steering before producing a binary; the final GCC build above completed successfully.
@@ -1,52 +0,0 @@
#!/usr/bin/env python3
"""Render the matched L18 review views as small readable JPEGs."""
import os
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
CAPTURE = ROOT / "Standalone/bin/linux/VoxelForgeOffscreenCapture-linux18-clang"
EXPORTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux18/exports"
OUT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux18/captures"
LOGS = ROOT / "Saved/Linux"
OUT.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(ROOT / "Saved/Linux/mesa-cache")
views = [
("floating-islands-flat-square-after", "floating-islands-flat-square.vfmesh",
(-11.612444, -118.221237, -19.8452), 0, 0, True),
("floating-islands-flat-square-no-edges", "floating-islands-flat-square.vfmesh",
(-11.612444, -118.221237, -19.8452), 0, 0, False),
("floating-islands-island-view-after", "floating-islands-island-view.vfmesh",
(-22, -19, -22.8), 0, -4, True),
("floating-islands-ceiling-up", "floating-islands-island-view.vfmesh",
(-22, -19, -22.8), 0, 80, True),
("surface-world-l1-spawn-after", "surface-world-l1-spawn.vfmesh",
(0, -35, -487.94), 0, -22, False),
("surface-world-horizon-after", "surface-world-l1-spawn.vfmesh",
(0, -35, -487.94), 0, 0, False),
]
for name, scene_name, camera, yaw, pitch, tile_edges in views:
scene = EXPORTS / scene_name
png = LOGS / f"l18-{name}.png"
jpg = OUT / f"{name}.jpg"
render_log = LOGS / f"l18-{name}-render.log"
convert_log = LOGS / f"l18-{name}-jpeg.log"
command = [str(CAPTURE), str(scene), str(png), "--size=1600x900",
"--shade=readable", f"--tile-edges={'on' if tile_edges else 'off'}",
"--tile-edge-width-m=0.15", "--camera-m="+",".join(map(str, camera)),
f"--yaw-deg={yaw}", f"--pitch-deg={pitch}"]
with render_log.open("w", encoding="utf-8") as stream:
subprocess.run(command, cwd=ROOT, env=env, stdout=stream,
stderr=subprocess.STDOUT, check=True)
ffmpeg = ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-y",
"-i", str(png), "-frames:v", "1", "-q:v", "4", str(jpg)]
with convert_log.open("w", encoding="utf-8") as stream:
subprocess.run(ffmpeg, cwd=ROOT, env=env, stdout=stream,
stderr=subprocess.STDOUT, check=True)
if jpg.stat().st_size > 300_000:
raise SystemExit(f"{jpg.name} exceeds the 300 KB review limit")
print(f"[Capture] {jpg.relative_to(ROOT)} bytes={jpg.stat().st_size} status=PASS")
@@ -1,165 +0,0 @@
#!/usr/bin/env python3
"""Audit the emitted FloatingIslands roof faces and same-LOD tile-edge continuity."""
import array
import collections
import math
import struct
import sys
def read_exact(stream, size):
data = stream.read(size)
if len(data) != size:
raise EOFError(f"wanted {size} bytes, got {len(data)}")
return data
def height_at(triangles, x, y, preferred_z):
best = None
for (x0, y0, z0), (x1, y1, z1), (x2, y2, z2) in triangles:
den = (y1-y2)*(x0-x2)+(x2-x1)*(y0-y2)
if abs(den) < 1.0e-10:
continue
a = ((y1-y2)*(x-x2)+(x2-x1)*(y-y2))/den
b = ((y2-y0)*(x-x2)+(x0-x2)*(y-y2))/den
c = 1.0-a-b
if min(a,b,c) >= -1.0e-5:
z = a*z0+b*z1+c*z2
if best is None or abs(z-preferred_z) < abs(best-preferred_z):
best = z
return best
def quantile(values, fraction):
if not values:
return math.nan
ordered = sorted(values)
return ordered[math.floor(fraction*(len(ordered)-1))]
if len(sys.argv) != 3:
raise SystemExit("usage: CeilingContinuity.py scene.vfmesh expected-cap-plane-metres")
scene_path = sys.argv[1]
root_z = float(sys.argv[2])
tiles = {}
voxel_size_m = None
with open(scene_path, "rb") as stream:
if read_exact(stream, 8) != b"VFMSH001":
raise SystemExit("invalid VFMSH001 scene")
version, voxel_um, tile_count = struct.unpack("<3I", read_exact(stream, 12))
camera = struct.unpack("<3d", read_exact(stream, 24))
yaw, pitch = struct.unpack("<2d", read_exact(stream, 16))
voxel_size_m = voxel_um/1_000_000.0
for _ in range(tile_count):
key = struct.unpack("<4i", read_exact(stream, 16))
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", read_exact(stream, 20))
positions = array.array("f")
positions.frombytes(read_exact(stream, vertex_count*12))
normals = array.array("f")
normals.frombytes(read_exact(stream, vertex_count*12))
indices = array.array("I")
indices.frombytes(read_exact(stream, index_count*4))
masks = read_exact(stream, mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
extent = 32*(1 << key[3])*voxel_size_m
z0, z1 = key[2]*extent, (key[2]+1)*extent
ceiling = []
for triangle in range(index_count//3):
if masks and masks[triangle] != 0:
continue
a,b,c = indices[triangle*3:triangle*3+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.65:
continue
points = tuple((positions[v*3],positions[v*3+1],positions[v*3+2])
for v in (a,b,c))
center_z = sum(point[2] for point in points)/3.0
if abs(center_z-root_z) <= 1.0:
ceiling.append(points)
tiles[key] = {
"extent": extent, "x0": key[0]*extent, "y0": key[1]*extent,
"x1": (key[0]+1)*extent, "y1": (key[1]+1)*extent,
"z0": z0, "z1": z1, "ceiling": ceiling,
}
by_lod = collections.defaultdict(dict)
for key, tile in tiles.items():
if tile["z0"] <= root_z+1.0 and tile["z1"] >= root_z-1.0:
by_lod[key[3]][key] = tile
print(f"scene={scene_path} version={version} voxel_um={voxel_um} tiles={tile_count} "
f"camera_m={camera} yaw={yaw} pitch={pitch} expected_cap_m={root_z:.9f}")
print("lod zband_tiles cap_tiles missing_tiles cap_triangles face_z_m_p05/median/p95 "
"tile_samples hit_samples max_abs_root_delta_cm shared_edges shared_hits edge_gaps "
"edge_delta_cm_median/p95/max")
all_pass = True
for lod in range(9):
target = by_lod[lod]
cap_tiles = {key: tile for key,tile in target.items() if tile["ceiling"]}
face_z = [point[2] for tile in cap_tiles.values() for tri in tile["ceiling"]
for point in tri]
deltas = []
sample_count = 0
hit_count = 0
for key,tile in target.items():
if not tile["ceiling"]:
continue
for iy in range(16):
for ix in range(16):
x = tile["x0"]+(ix+0.5)*tile["extent"]/16
y = tile["y0"]+(iy+0.5)*tile["extent"]/16
sample_count += 1
height = height_at(tile["ceiling"],x,y,root_z)
if height is not None:
hit_count += 1
deltas.append(abs(height-root_z)*100.0)
edge_deltas = []
edge_count = 0
edge_gaps = 0
for key,a in cap_tiles.items():
x,y,z,level = key
for neighbor_key,axis in (((x+1,y,z,level),"x"),((x,y+1,z,level),"y")):
b = cap_tiles.get(neighbor_key)
if b is None:
continue
edge_count += 1
epsilon = min(0.02,a["extent"]*1.0e-4)
matched = 0
for index in range(33):
t = (index+0.5)/33
if axis == "x":
fixed = a["x1"]
varying = a["y0"]+(a["y1"]-a["y0"])*t
za = height_at(a["ceiling"],fixed-epsilon,varying,root_z)
zb = height_at(b["ceiling"],fixed+epsilon,varying,root_z)
else:
fixed = a["y1"]
varying = a["x0"]+(a["x1"]-a["x0"])*t
za = height_at(a["ceiling"],varying,fixed-epsilon,root_z)
zb = height_at(b["ceiling"],varying,fixed+epsilon,root_z)
if za is not None and zb is not None:
edge_deltas.append(abs(za-zb)*100.0)
matched += 1
if matched == 0:
edge_gaps += 1
missing = len(target)-len(cap_tiles)
max_root = max(deltas) if deltas else math.nan
print(f"{lod} {len(target)} {len(cap_tiles)} {missing} "
f"{sum(len(tile['ceiling']) for tile in cap_tiles.values())} "
f"{quantile(face_z,.05):.6f}/{quantile(face_z,.5):.6f}/{quantile(face_z,.95):.6f} "
f"{sample_count} {hit_count} {max_root:.3f} {edge_count} {len(edge_deltas)} "
f"{edge_gaps} {quantile(edge_deltas,.5):.3f}/{quantile(edge_deltas,.95):.3f}/"
f"{max(edge_deltas) if edge_deltas else math.nan:.3f}")
if lod >= 4 and target and (missing or hit_count != sample_count or edge_gaps):
all_pass = False
print(f"[CeilingContinuity] result={'PASS' if all_pass else 'FAIL'} "
"criteria=LOD4-8 requested z-band tiles emit an upper-boundary ceiling; 16x16 ray samples hit; "
"shared same-LOD tile edges have paired hits")
raise SystemExit(0 if all_pass else 1)
@@ -1,25 +0,0 @@
#!/usr/bin/env python3
"""Compare all L18 crossing configurations to the L14 24 m crossing hashes."""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RUNS = ROOT / "Saved/Linux/l18-crossing"
PATTERN = re.compile(
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
failed = []
logs = sorted(RUNS.glob("*-w*-r*.log"))
for path in logs:
parts = path.stem.split("-")
compiler = parts[0]
reference_compiler = "clang" if compiler == "clang" else "gcc"
reference_path = ROOT / f"Saved/Linux/l14-final-crossing-24m-{reference_compiler}-w12.log"
expected = PATTERN.findall(reference_path.read_text(encoding="utf-8"))
actual = PATTERN.findall(path.read_text(encoding="utf-8", errors="replace"))
if actual != expected:
failed.append(path.name)
print(f"[CrossingReferenceMismatch] {path.name} expected_rows={len(expected)} "
f"actual_rows={len(actual)}")
print(f"[CrossingSurfaceHashGate] reference=L14-final-crossing-24m-W12 rows={len(logs)} "
f"result={'FAIL' if failed else 'PASS'}")
raise SystemExit(1 if failed or len(logs) != 14 else 0)
@@ -1,25 +0,0 @@
#!/usr/bin/env python3
"""Compare every L18 SurfaceWorld matrix hash row with the approved L14 reference."""
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RUNS = ROOT / "Saved/Linux/l18-matrix"
REFERENCE = ROOT / "Saved/Linux/l14-final-hash-sw-clang-off-w12-r5.log"
PATTERN = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
expected = PATTERN.findall(REFERENCE.read_text(encoding="utf-8"))
if not expected or len(expected) != 12:
raise SystemExit(f"invalid L14 reference hash rows: {len(expected)}")
logs = sorted(RUNS.glob("surface-world-*.log"))
if len(logs) != 14:
raise SystemExit(f"expected 14 SurfaceWorld matrix logs, found {len(logs)}")
failed = []
for path in logs:
actual = PATTERN.findall(path.read_text(encoding="utf-8", errors="replace"))
if actual != expected:
failed.append(path.name)
print(f"[SurfaceHashMismatch] {path.name} expected={len(expected)} actual={len(actual)}")
print(f"[SurfaceHashGate] reference={REFERENCE.name} rows={len(logs)} expected_rows=14 "
f"result={'FAIL' if failed else 'PASS'}")
raise SystemExit(1 if failed else 0)
@@ -1,149 +0,0 @@
#include "Json.h"
#include "OwnerWorldLoader.h"
#include "StandaloneCoreScheduler.h"
#include "WorldAssemblyPrototype.h"
#include "StandaloneSnapshotAdapter.h"
#include <cmath>
#include <cstdio>
#include <fstream>
#include <iterator>
#include <memory>
#include <string>
namespace
{
constexpr double CoreVoxelsPerMeter=4.0;
constexpr std::int64_t SubvoxelsPerCoreVoxel=
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
void PrintDensity(VoxelForgeCore::World& World,const char* Label,double X,double Y,double Z)
{
const VoxelForgeCore::Vec3d Point{X*CoreVoxelsPerMeter,Y*CoreVoxelsPerMeter,
Z*CoreVoxelsPerMeter};
const double Density=World.densityAt(Point);
const double Raw=World.unclampedDensityAt(Point);
std::printf("[DensitySample] label=%s xyz_m=%.6f,%.6f,%.6f density_core_vox=%.9f "
"density_cm=%.4f unclamped_core_vox=%.9f sign=%s\n",Label,X,Y,Z,Density,
Density*25.0,Raw,Density<0.0?"solid":"air");
}
void PrintCrossing(VoxelForgeCore::World& World,const char* Label,double X,double Y,
double BottomM,double TopM)
{
const auto DensityAt=[&](double Z)
{
return World.densityAt({X*CoreVoxelsPerMeter,Y*CoreVoxelsPerMeter,
Z*CoreVoxelsPerMeter});
};
double Lo=BottomM,Hi=TopM;
double LoDensity=DensityAt(Lo),HiDensity=DensityAt(Hi);
if((LoDensity<0.0)==(HiDensity<0.0))
{
std::printf("[DensityCrossing] label=%s bracket_m=%.6f..%.6f signs=%s/%s result=none\n",
Label,Lo,Hi,LoDensity<0.0?"solid":"air",HiDensity<0.0?"solid":"air");
return;
}
for(int Iteration=0;Iteration<64;++Iteration)
{
const double Mid=(Lo+Hi)*0.5;
const double MidDensity=DensityAt(Mid);
if((MidDensity<0.0)==(LoDensity<0.0))
{Lo=Mid;LoDensity=MidDensity;}
else
{Hi=Mid;HiDensity=MidDensity;}
}
std::printf("[DensityCrossing] label=%s z_m=%.9f bracket_m=%.6f..%.6f "
"density_core_vox=%.9f/%.9f\n",Label,(Lo+Hi)*0.5,BottomM,TopM,
LoDensity,HiDensity);
}
}
int main(int argc,char** argv)
{
if(argc!=2)
{
std::fprintf(stderr,"usage: %s owner-world.json\n",argv[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(!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)
||!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly(
*Owner,Root,Assembly,Error))
{std::fprintf(stderr,"plan construction failed: %s\n",Error.c_str());return 2;}
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* ResolvedAssembly=Snapshot.worldAssembly();
if(!ResolvedAssembly||ResolvedAssembly->floatingIslands.empty())
{std::fprintf(stderr,"resolved FloatingIslands plan missing\n");return 2;}
VoxelForgeCore::World World(std::move(Snapshot));
VoxelForgeCore::ShapeBuildRequest Request;
Request.minVoxel={-96,-512,-288};
Request.maxVoxel={96,96,64};
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;}
const auto& Plan=ResolvedAssembly->floatingIslands.front();
const auto& Recipe=Plan.manifest.recipe;
std::printf("[FloatingIslandBounds] geometry_version=%u owner_voxel_um=%u core_voxel_um=%u "
"owner_to_core=%u/%u chunks=%d..%d layout_z_m=%.6f..%.6f ceiling_start_m=%.6f "
"seal_m=%.6f bottom_cap=%s\n",Recipe.geometryVersion,Recipe.ownerVoxelSizeMicrons,
Recipe.coreVoxelSizeMicrons,Recipe.ownerToCoreScaleNumerator,
Recipe.ownerToCoreScaleDenominator,Plan.topChunkZ,Plan.bottomChunkZ,
static_cast<double>(Recipe.layoutMinimumZSubvoxels)/SubvoxelsPerCoreVoxel/4.0,
static_cast<double>(Recipe.layoutMaximumZSubvoxels)/SubvoxelsPerCoreVoxel/4.0,
static_cast<double>(Recipe.layoutMaximumZSubvoxels-Recipe.boundarySealSubvoxels)
/SubvoxelsPerCoreVoxel/4.0,
static_cast<double>(Recipe.boundarySealSubvoxels)/SubvoxelsPerCoreVoxel/4.0,
Recipe.geometryVersion<3?"legacy":"removed");
std::printf("[DensityProbe] flat_square_xy_m=-11.612444,-118.221237 "
"compiled=%d core_voxel_size_cm=25\n",World.isCompiled()?1:0);
constexpr double X=-11.612443923950195;
constexpr double Y=-118.22123718261719;
for(double Z:{2.75,2.99,3.00,3.01,3.25,3.49,3.50,3.51,3.75,3.99,4.00,4.01,
4.05,4.125,4.25,4.50,5.00,
7.75,8.00,8.25,-60.25,-60.00,-59.75,-59.50,-59.25})
PrintDensity(World,"flat-square",X,Y,Z);
PrintCrossing(World,"shared-upper-ceiling",X,Y,3.50,3.75);
PrintCrossing(World,"upper-boundary-cap",X,Y,4.00,4.25);
PrintCrossing(World,"old-eight-metre-wrapper",X,Y,7.75,8.25);
PrintCrossing(World,"removed-lower-boundary-seal",X,Y,-60.25,-59.75);
for(const auto& Probe:std::array<std::array<double,2>,3>{{
{{-66.3,-38.7}},{{-62.3,-54.6}},{{-53.34,-22.84}}}})
{
std::printf("[DensityProbeXY] xy_m=%.3f,%.3f\n",Probe[0],Probe[1]);
for(double Z:{3.49,3.50,3.51,3.75,3.875,3.99,4.00,4.01,4.05,4.125})
PrintDensity(World,"boundary-ray",Probe[0],Probe[1],Z);
PrintCrossing(World,"boundary-ray",Probe[0],Probe[1],3.49,4.125);
}
for(const auto& Probe:std::array<std::array<double,2>,4>{{
{{-67.0,-39.0}},{{-66.0,-39.0}},{{-67.0,-38.0}},{{-66.0,-38.0}}}})
{
PrintDensity(World,"boundary-grid-node",Probe[0],Probe[1],3.875);
PrintDensity(World,"boundary-grid-node",Probe[0],Probe[1],4.125);
}
return 0;
}
@@ -1,152 +0,0 @@
#!/usr/bin/env python3
"""Trace review-image pixels through the exported clipmap mesh."""
import array
import math
import struct
import sys
def read_exact(f, count):
data = f.read(count)
if len(data) != count:
raise EOFError((len(data), count))
return data
def floor_div(n, d):
q, r = divmod(n, d)
return q - (r < 0)
def visible_mask(key, desired):
x, y, z, lod = key
extent = 32 << lod
origin = (x * extent, y * extent, z * extent)
mask = 0
for axis in range(3):
for side in range(2):
probe = [origin[i] + extent // 2 for i in range(3)]
probe[axis] = origin[axis] + (extent if side else -1)
neighbor = None
for level in range(9):
neighbor_extent = 32 << level
coord = tuple(floor_div(probe[i], neighbor_extent) for i in range(3)) + (level,)
if coord in desired:
neighbor = level
break
if neighbor is None or neighbor != lod:
mask |= 1 << (axis * 2 + side)
return mask
def ray_box(origin, direction, low, high):
lo, hi = 0.1, 20000.0
for axis in range(3):
if abs(direction[axis]) < 1e-12:
if origin[axis] < low[axis] or origin[axis] > high[axis]:
return None
else:
a = (low[axis] - origin[axis]) / direction[axis]
b = (high[axis] - origin[axis]) / direction[axis]
if a > b:
a, b = b, a
lo, hi = max(lo, a), min(hi, b)
if lo > hi:
return None
return lo, hi
def ray_tri(origin, direction, a, b, c):
e1 = tuple(b[i] - a[i] for i in range(3))
e2 = tuple(c[i] - a[i] for i in range(3))
p = (direction[1]*e2[2]-direction[2]*e2[1],
direction[2]*e2[0]-direction[0]*e2[2],
direction[0]*e2[1]-direction[1]*e2[0])
det = sum(e1[i]*p[i] for i in range(3))
if abs(det) < 1e-10:
return None
inv = 1.0/det
tvec = tuple(origin[i]-a[i] for i in range(3))
u = sum(tvec[i]*p[i] for i in range(3))*inv
if u < 0 or u > 1:
return None
q = (tvec[1]*e1[2]-tvec[2]*e1[1],
tvec[2]*e1[0]-tvec[0]*e1[2],
tvec[0]*e1[1]-tvec[1]*e1[0])
v = sum(direction[i]*q[i] for i in range(3))*inv
if v < 0 or u+v > 1:
return None
t = sum(e2[i]*q[i] for i in range(3))*inv
return t if t >= 0.1 else None
if len(sys.argv) < 8 or len(sys.argv) % 2 != 1:
raise SystemExit("usage: ProbeCaptureRays.py scene.vfmesh camX camY camZ yaw pitch pixelX pixelY [pixelX pixelY ...]")
scene_path = sys.argv[1]
cam = tuple(map(float, sys.argv[2:5]))
yaw, pitch = map(math.radians, map(float, sys.argv[5:7]))
pixels = [(int(sys.argv[i]), int(sys.argv[i+1])) for i in range(7, len(sys.argv), 2)]
width, height, fov = 1600, 900, math.radians(90)
tan_h = math.tan(fov/2)
tan_v = tan_h / (width/height)
forward = (math.sin(yaw)*math.cos(pitch), math.cos(yaw)*math.cos(pitch), math.sin(pitch))
right = (math.cos(yaw), -math.sin(yaw), 0.0)
up = (right[1]*forward[2]-right[2]*forward[1], right[2]*forward[0]-right[0]*forward[2], right[0]*forward[1]-right[1]*forward[0])
with open(scene_path, "rb") as f:
if read_exact(f, 8) != b"VFMSH001":
raise SystemExit("bad scene")
version, voxel_um, count = struct.unpack("<3I", read_exact(f, 12))
scene_cam = struct.unpack("<3d", read_exact(f, 24))
scene_yaw, scene_pitch = struct.unpack("<2d", read_exact(f, 16))
voxel_m = voxel_um / 1_000_000.0
tiles = []
desired = set()
for _ in range(count):
key = struct.unpack("<4i", read_exact(f, 16))
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack("<5I", read_exact(f, 20))
positions = array.array("f"); positions.frombytes(read_exact(f, vertex_count*12))
normals = array.array("f"); normals.frombytes(read_exact(f, vertex_count*12))
indices = array.array("I"); indices.frombytes(read_exact(f, index_count*4))
masks = read_exact(f, mask_count)
if sys.byteorder != "little":
positions.byteswap(); normals.byteswap(); indices.byteswap()
desired.add(key)
tiles.append((key, surface_vertices, skirt_count, positions, normals, indices, masks))
print(f"scene={scene_path} tiles={count} camera={cam} yaw={math.degrees(yaw):.3f} pitch={math.degrees(pitch):.3f}")
for px, py in pixels:
nx = 2*px/width - 1
ny = 1 - 2*py/height
direction0 = tuple(forward[i] + nx*tan_h*right[i] + ny*tan_v*up[i] for i in range(3))
length = math.sqrt(sum(v*v for v in direction0))
direction = tuple(v/length for v in direction0)
hits = []
candidate_tiles = 0
for key, surface_vertices, skirt_count, positions, normals, indices, masks in tiles:
x,y,z,lod = key
extent = 32*(1<<lod)*voxel_m
low = (x*extent, y*extent, z*extent)
high = tuple(v+extent for v in low)
if ray_box(cam, direction, low, high) is None:
continue
candidate_tiles += 1
show_mask = visible_mask(key, desired)
for tri in range(len(indices)//3):
m = masks[tri] if masks else 0
if m and (m & show_mask) == 0:
continue
ia,ib,ic = indices[tri*3:tri*3+3]
a,b,c = [],[],[]
for idx, out in ((ia,a),(ib,b),(ic,c)):
base=idx*3
out.extend((positions[base],positions[base+1],positions[base+2]))
t=ray_tri(cam,direction,a,b,c)
if t is None: continue
p=tuple(cam[i]+t*direction[i] for i in range(3))
nz=(normals[ia*3+2]+normals[ib*3+2]+normals[ic*3+2])/3
hits.append((t,key,tri,m,p,nz,ia>=surface_vertices or ib>=surface_vertices or ic>=surface_vertices))
hits.sort(key=lambda h:h[0])
print(f"pixel=({px},{py}) ray={direction} candidate_tiles={candidate_tiles} hits={len(hits)}")
for t,key,tri,m,p,nz,skirt in hits[:12]:
print(f" t={t:.3f}m key={key} tri={tri} mask={m} skirt={skirt} z={p[2]:.3f} normal_z={nz:.3f} xyz=({p[0]:.3f},{p[1]:.3f},{p[2]:.3f})")
@@ -1,132 +0,0 @@
# Linux18 — FloatingIslands stratum boundaries
**Result:** FloatingIslands geometry v3 removes its false lower all-XY seal, keeps the ceiling from the shared upper-boundary seal, and projects missing ceiling cells for every generator. SurfaceWorld hashes match the approved L14 baseline at `8704129` in every checked configuration.
## Scale in the Unreal route
`AVoxelWorld::CoreVoxelSizeCm` defaults to 25 cm, and `GetActiveVoxelSizeCm()` resolves the runtime value ([VoxelWorld.h:300](../../../../Source/VoxelForge/Public/VoxelWorld.h), [VoxelWorld.cpp:7495](../../../../Source/VoxelForge/Private/VoxelWorld.cpp)). The adapter writes the resolved size to the snapshot ([VoxelForgeCoreAdapter.cpp:1019](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp)); `AVoxelWorld` imports it with `ResolvedWorldSnapshot::FromJson` ([VoxelWorld.cpp:7597](../../../../Source/VoxelForge/Private/VoxelWorld.cpp), [VoxelForgeCore.cpp:916](../../../../Standalone/Core/src/VoxelForgeCore.cpp)). S1 records this single active size source and the 32-Core-voxel tile rule ([Round5 REPORT.md](../Round5/REPORT.md#1-one-core-voxel-size-source)).
At the current 25 cm setting, **SurfaceWorld strata use 8 m per slot chunk**: the Unreal surface plan multiplies the slot range by 32 Core voxels ([VoxelForgeCoreAdapter.cpp:280](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp); `CHUNK_SIZE=32` in [VoxelTypes.h:211](../../../../Source/VoxelForge/Public/VoxelTypes.h)). **FloatingIslands uses 4 m per owner-authored chunk**: the adapter’s owner size is `VOXEL_SIZE` ([VoxelForgeCoreAdapter.cpp:33](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp); 12.5 cm in [VoxelTypes.h:213](../../../../Source/VoxelForge/Public/VoxelTypes.h)), and its plan multiplies 32 owner voxels by the serialized owner/Core ratio, currently 12.5/25 = 1/2, or 16 Core voxels ([VoxelForgeCoreAdapter.cpp:441](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp)). For chunks 0…−15 that gives z bounds −60…+4 m. This is the two explicit coordinate conventions in the game route, not a global change from 8 m to 4 m.
Standalone uses the same owner/Core ratio and chunk formula ([OwnerWorldLoader.h:302](../../../../Standalone/Source/OwnerWorldLoader.h), [WorldAssemblyPrototype.h:488](../../../../Standalone/Source/WorldAssemblyPrototype.h)); no current Unreal/standalone scale disagreement was found. L18 also removed the standalone-only two-Core-voxel bounds inset so recipe bounds match the Unreal plan. The owner decides whether either authored scale changes.
## Fix and ceiling
Geometry version 3 preserves the island shapes and removes only the lower all-XY seal. The upper seal calls the shared `VF_ApplyUpperBoundarySeal` density primitive; the shared two-sided seal uses the same upper-band math ([VoxelDensityPrimitives.h:44](../../../../Source/VoxelForge/Public/VoxelDensityPrimitives.h)). Resolved bounds come from each strate’s own range. The shared `AppendStratumBoundaryCeilings` mesh path runs for every generator and LOD, adding a plane at the resolved upper boundary where the mesh lacks coverage. It suppresses an existing plane only when nine probes show full cell coverage ([ShapeWorldInternal.h:36](../../../../Standalone/Core/src/ShapeWorldInternal.h), [FarShellsInternal.h:894](../../../../Standalone/Core/src/FarShellsInternal.h), [VoxelForgeCore.cpp:2207](../../../../Standalone/Core/src/VoxelForgeCore.cpp)). The underside normal points into the room, so the existing material lighting renders the interior face. This adds no FloatingIslands-specific height.
For the captured FloatingIslands strate, the upper bound is **z=+4 m** and its configured 0.5 m seal gives an interior-facing ceiling surface beginning around **z=+3.5 m**. The density probe crosses into the seal at 3.5 m and exits at z=4.019 m; z=7.75, 8.0, and 8.25 m remain air with no crossing. The lower all-XY seal is removed; remaining geometry below −60 m belongs to the lower world and is visible in the ray traces. Thus the apparent 3.5 m “floor” is the retained ceiling when viewed from inside; the 8 m floor is absent. No chunk size changed.
The owner’s decision and version-3 recipe say an underground strate has a ceiling at its configured upper boundary. “Farther to breathe” comes from a taller configured range. I updated the stale architecture-table wording “open void” to say the islands sit beneath the shared ceiling.
## Ceiling continuity
The final audit used 16×16 interior samples per tile and paired samples along same-LOD tile edges. Far LODs pass continuously:
| LOD | Tiles with cap | Interior hits | Max root delta | Edge gaps |
|---:|---:|---:|---:|---:|
| 4 | 48/48 | 12,288/12,288 | 0 cm | 0 |
| 5 | 48/48 | 12,288/12,288 | 0 cm | 0 |
| 6 | 48/48 | 12,288/12,288 | 0 cm | 0 |
| 7 | 48/48 | 12,288/12,288 | 0 cm | 0 |
| 8 | 153/153 | 39,168/39,168 | 0 cm | 0 |
LOD0, 2, and 3 also hit all tested samples. LOD1 missed 3 of 12,288 interior samples (0.387 cm maximum root delta) and had no shared-edge gaps. The readable upward view is `captures/floating-islands-ceiling-up.jpg`.
## Dark tile-aligned patches
They are lower SurfaceWorld triangles visible below the removed false lower cap, not empty rays and not a viewer dark-square overlay. Traces in the final island export found:
- Pixel (700,720): first hit at 104.924 m on non-skirt LOD2 tile `(-2,2,-2,2)`, z=−62.925 m, normal-z 0.266; another LOD2 tile hits z=−69.942 m.
- Pixel (1050,650): first hit at 168.051 m on non-skirt LOD3 tile `(0,2,-2,3)`, z=−72.594 m, normal-z 0.895; lower LOD4 geometry is also hit at z=−112.229 m and −120 m.
The ring probe measures real LOD height steps on those lower surfaces. I left them visible: suppressing those triangles would hide valid lower-stratum geometry, while changing the lower strata or SurfaceWorld would exceed this local fix. The sampled rectangles are not missing tiles or floor holes.
## Cross-LOD ring steps
Entries are median / p95 / maximum absolute edge-height difference in centimetres, then gap count. The L18 column is the final matched island camera. The large remaining steps are lower-stratum terrain, not the upper ceiling.
| LOD edge | L17 before | L17 after | L18 final |
|---|---:|---:|---:|
| 0→1 | 3.451 / 8.920 / 449.158; 14 | —; 0 samples | —; 0 samples |
| 1→2 | 26.642 / 148.262 / 149.862; 0 | 0 / 0 / 0; 0 | 147.141 / 148.403 / 149.862; 0 |
| 2→3 | 0.134 / 198.730 / 773.318; 8 | 0 / 0 / 0; 0 | 128.603 / 200.789 / 773.318; 8 |
| 3→4 | 3.269 / 47.875 / 188.531; 27 | 0 / 0 / 0; 0 | 3.269 / 47.774 / 188.531; 27 |
| 4→5 | 29.975 / 130.496 / 445.444; 6 | 0 / 0 / 30.957; 1 | 29.992 / 124.373 / 443.482; 6 |
| 5→6 | 12.234 / 127.224 / 536.573; 32 | 12.234 / 127.224 / 536.573; 32 | 12.234 / 127.224 / 536.573; 21 |
| 6→7 | 29.013 / 301.752 / 42,624.479; 0 | 28.892 / 291.861 / 945.477; 0 | 29.013 / 301.752 / 42,624.479; 0 |
| 7→8 | 83.843 / 1,225.416 / 3,755.407; 32 | 83.843 / 1,225.416 / 3,755.407; 0 | 83.843 / 1,225.416 / 3,755.407; 0 |
## SurfaceWorld regression hashes
The 14-run check compared each final output to `Saved/Linux/l14-final-hash-sw-clang-off-w12-r5.log` from the L14/`8704129` baseline: Clang `-ffp-contract=off` and GCC default, W1/W8/W12, with five W12 repeats. All 14 matched the whole-view, full-detail, LOD0–8, and exact-query rows. The 24 m crossing check likewise passed all 14 compiler/worker configurations against their L14 compiler-specific references.
Whole view D/M/P/T: `c10697a8f48380c7 / e75c9119a1498cd5 / 49a9a8d1835a6c8c / edcb23288b5e8724`.
Full detail D/M/P/T: `0ee0579bd15cbbe4 / 32e5d4964d610fee / 1932b96ee4b1d910 / 69743c6bad3447dc`.
| LOD | Density | Mesh | Payload | Topology |
|---:|---|---|---|---|
| 0 | `769f2a706d585a72` | `33eeeb39fa443816` | `645621a1d968983f` | `c2457ed905036c9d` |
| 1 | `69cc5a5c0a2c60af` | `b48335f5410c57d5` | `d15c5480162ab8ba` | `feca1bd025823604` |
| 2 | `85deed5683a77202` | `53469895987aeea2` | `e55f8ae7e1532033` | `d5feb12a0e85c09b` |
| 3 | `6518caefb3be7e7f` | `ec513c90b853a614` | `f62fa1137ff17f82` | `ccf1dcf681965816` |
| 4 | `62d4be96021992f4` | `f1e4c6efcc2615a9` | `9dc5f9c88be02f33` | `fe6f55125a07c3bf` |
| 5 | `5e4a8c38b8cea6fc` | `08ee56998aaf21e5` | `541eea8558f26cc8` | `6539374a8b3336d8` |
| 6 | `e036e95b9006945b` | `624a33f941a99b02` | `81b73e14f68bdf86` | `4de4c63ce1f80a56` |
| 7 | `8ee358af56a30d8d` | `46ab3c6184048b7c` | `0a6164b7e68e80c5` | `0c26b85bb03bc831` |
| 8 | `c3458e1cbdb9c471` | `46c03d0994d75192` | `8f89fc5bde12d230` | `d16dc0cf62031898` |
Exact query: 9 probes; density/unclamped `e4d4a8eb95b35933`, detail `efcba20c0c70c415`, collision `e604823a249029bf`.
The 24 m crossing W12×5 hashes match those same aggregate whole-view, full-detail, and exact-query values on both compiler references; the full check compared all 12 rows per run.
FloatingIslands’ representative W12×5 whole-view D/M/P/T is `ca7f375cded78f1d / da003331c554f8f3 / 0d2e74862b4ee500 / b7a7177af8915a21`; full-detail is `fc71abf85f5b9373 / e139b32d42ecfa29 / 28a2414e41de59bd / d61ca68ca64f74ca`. Exact query is `4ef614485815ee30` density/unclamped, `efcba20c0c70c415` detail, `e604823a249029bf` collision. Clang and GCC representative hashes agree, and all nine LOD rows stayed stable across the 28-run matrix.
## Other invariants and Unreal edits
- Final matrix: 28/28 rows passed; repeated runs were stable within each compiler. Route parity had 0 mismatches across 782,026 FI and 910,651 SurfaceWorld exact samples. Proof audits had 0 violations: FI 9,048 blocks / 12,042,888 samples; SurfaceWorld 27,479 / 36,574,549. Readiness negative controls were caught; walkability passed 4,000/4,000 FI and 5,642/5,642 SurfaceWorld samples.
- L3 FP-contract preamble/self-test, L4 `FLazy`, L7 shared cache, L9/L12 proofs, L11 readiness/crash handling, L13 route binding, ARCHITECTURE §8.10, and async/MPSC/Epoch paths remain intact. Both final compiler runs started without the Core FP-contract self-test refusing the build. No async route was added.
- Unreal-side edits are limited to [VoxelForgeCoreAdapter.cpp](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:453), bumping FI geometry version 2→3 so serialized recipes pick up the lower-seal removal, and the shared [VoxelDensityPrimitives.h](../../../../Source/VoxelForge/Public/VoxelDensityPrimitives.h:44), extracting the common upper-seal math used by v3. The Unreal target could not be compiled on this Linux host.
## Captures
All JPEGs are below 300 KB. Before/after pairs use matched cameras: FI flat square `(-11.6124,-118.2212,-19.8452), yaw/pitch 0/0`; FI island `(-22,-19,-22.8), 0/−4`; SurfaceWorld spawn `(0,-35,-487.94), 0/−22`; horizon uses the same spawn camera with pitch 0. The ceiling view uses the island camera with pitch 80. The SurfaceWorld pairs look the same; the exact hash checks certify its output bit-for-bit.
- FI flat square: `captures/floating-islands-flat-square-before.jpg`, `captures/floating-islands-flat-square-after.jpg`.
- FI low island: `captures/floating-islands-island-view-before.jpg`, `captures/floating-islands-island-view-after.jpg`.
- FI upward ceiling: `captures/floating-islands-ceiling-up.jpg`.
- SurfaceWorld L1 spawn: `captures/surface-world-l1-spawn-before.jpg`, `captures/surface-world-l1-spawn-after.jpg`.
- SurfaceWorld horizon: `captures/surface-world-horizon-before.jpg`, `captures/surface-world-horizon-after.jpg`.
- Flat-square diagnostic without tile edges: `captures/floating-islands-flat-square-no-edges.jpg`.
## Failed or retried runs
- The first density-probe link omitted legacy compatibility objects and failed with undefined references. Relinking with the legacy implementation objects succeeded; both boundary probes then ran.
- The initial 4×4 continuity audit failed at far LODs 4–6 and found edge gaps. The shared all-LOD boundary cap and final full-cell coverage check replaced the center-only deduplication; final 16×16 LOD4–8 continuity passed with zero edge gaps.
- An early matrix checker compared Clang contraction-off hashes directly with GCC default hashes and reported FI mismatches. The checker was corrected to test repeated runs within each compiler and the L14 compiler-specific references; the final 28-row matrix passed.
- An early offscreen capture drew all skirts and showed an artificial slab. The capture now applies the viewer’s hidden-skirt mask; final matched captures were regenerated.
- A GCC build interrupted by owner steering produced no binary and was rerun. A pre-correction Clang build was superseded by the final build. No final build or matrix run failed or exceeded the runtime limits.
## Resources and owner note
Final builds: Clang 19 exit 0 in 31.689 s, peak RSS 436,088 KiB; GCC 14 exit 0 in 48.648 s, peak RSS 903,168 KiB. The largest measured runtime process was 897,276 KiB RSS. All processes stayed below the 6 GiB address-space cap; CPU affinity was 0–11, worker maximum 12, normal priority, and governor `powersave`. No run exceeded 30 minutes or stalled for 15 minutes. See [BuildResources.md](BuildResources.md).
**Owner note:** SurfaceWorld stayed bit-identical to L14. FloatingIslands now retains a ceiling at its configured upper strate boundary while removing the false lower all-XY floor. At the current plan that is a z=+4 m boundary with a 0.5 m seal; its distance above the islands comes from the configured range. The small Unreal edits are listed above and need an owner-machine UBT compile.
## Orchestrator review (Claude)
- **Merged.** SurfaceWorld is bit-identical (14 configs plus the 24 m crossing); the FI false all-XY lower seal
is removed; the ceiling comes from the shared, strate-parameter upper-boundary seal and ceiling path
(owner rule: every strate has a ceiling, height from its own parameters). The Unreal edits are a pure
extraction of the existing upper-seal math plus the FI `geometryVersion` 2→3; they need a UBT compile on
the owner PC.
- **The scale answer:** both conventions are deliberate and agree between Unreal and the standalone
(SurfaceWorld 32 Core voxels per chunk; FloatingIslands 32 owner voxels = 16 Core voxels). Nothing
rescaled.
- **Still open (L19):**
1. Small black holes in the FI ceiling, visible near the top of `floating-islands-flat-square-after.jpg`;
compare LOD1 missing 3/12,288 continuity samples.
2. The owner's ledges are most likely the LOWER stratum's terrain seen through the floorless FI void:
ring steps of 147 cm median at LOD1→2 and 129 cm at 2→3 remain in that terrain. "Valid lower-stratum
geometry" with metre-scale cross-LOD steps is still a bug the owner can see.
- Exports (229 MB `.vfmesh`) are not committed.
@@ -1,82 +0,0 @@
#!/usr/bin/env python3
"""Run the L18 24 m SurfaceWorld crossing hash matrix."""
import os
import re
import signal
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l18-crossing"
OUT.mkdir(parents=True, exist_ok=True)
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
BINARIES = {
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux18-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux18-gcc",
}
PATTERN = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
rows = []
passed = True
for compiler, binary in BINARIES.items():
for workers, repeat in [(1, 1), (8, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 5)]:
name = f"{compiler}-w{workers}-r{repeat}"
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=0,-35,-487.94,0,-22",
"--camera-step=24,0,0", "--region-crossing", "--recipe=surface-world"]
started = time.monotonic()
peak_rss = 0
with logfile.open("w", encoding="utf-8") as stream:
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
start_new_session=True)
while process.poll() is None:
pending = [process.pid]
seen = set()
while pending:
pid = pending.pop()
if pid in seen:
continue
seen.add(pid)
try:
pending.extend(int(value) for value in
Path(f"/proc/{pid}/task/{pid}/children").read_text().split())
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
if line.startswith("VmRSS:"):
peak_rss = max(peak_rss, int(line.split()[1]))
break
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
pass
if time.monotonic() - started > 900:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
passed = False
break
time.sleep(0.1)
code = process.wait()
elapsed = time.monotonic() - started
actual = PATTERN.findall(logfile.read_text(encoding="utf-8", errors="replace"))
ok = code == 0 and len(actual) == 12
passed &= ok
rows.append((compiler, workers, repeat, code, elapsed, peak_rss, ok, actual))
print(f"[CrossingRun] {name} exit={code} seconds={elapsed:.3f} "
f"peak_rss_kib={peak_rss} hash_rows={len(actual)} status={'PASS' if ok else 'FAIL'}",
flush=True)
reference = None
for compiler, workers, repeat, code, elapsed, peak_rss, ok, hashes in rows:
if reference is None:
reference = hashes
elif hashes != reference:
passed = False
print(f"[CrossingHashMismatch] {compiler} workers={workers} repeat={repeat}", flush=True)
print(f"[CrossingSummary] rows={len(rows)} expected=14 result={'PASS' if passed else 'FAIL'} "
f"path={OUT}", flush=True)
raise SystemExit(0 if passed and len(rows) == 14 else 1)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

@@ -1,70 +0,0 @@
#!/usr/bin/env python3
"""Render matched L19 review captures from the L18 and L19 scene exports."""
import os
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
CAPTURE = ROOT / "Standalone/bin/linux/VoxelForgeOffscreenCapture-linux19-clang"
BEFORE = ROOT / "Standalone/Research/WorldAssemblerM3/Linux18/exports"
AFTER = ROOT / "Saved/Linux/l19-diagnostics"
OUT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux19/captures"
PNG_OUT = ROOT / "Saved/Linux/l19-captures-temp"
LOGS = ROOT / "Saved/Linux/l19-diagnostics"
OUT.mkdir(parents=True, exist_ok=True)
PNG_OUT.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(ROOT / "Saved/Linux/mesa-cache")
views = [
("floating-islands-flat-square-before", BEFORE / "floating-islands-flat-square.vfmesh",
(-11.612444, -118.221237, -19.8452), 0, 0, True),
("floating-islands-flat-square-after", AFTER / "floating-islands-flat-square-after.vfmesh",
(-11.612444, -118.221237, -19.8452), 0, 0, True),
("floating-islands-owner-angle-before", BEFORE / "floating-islands-island-view.vfmesh",
(-22, -19, -22.8), 0, -4, True),
("floating-islands-owner-angle-after", AFTER / "floating-islands-island-view-after.vfmesh",
(-22, -19, -22.8), 0, -4, True),
("floating-islands-owner-ground-before", AFTER / "floating-islands-owner-ground-before.vfmesh",
(-6.3, -19.06, -21.0), 0, -4, True),
("floating-islands-owner-ground-after", AFTER / "floating-islands-owner-ground-after.vfmesh",
(-6.3, -19.06, -21.0), 0, -4, True),
("floating-islands-ceiling-up-after", AFTER / "floating-islands-island-view-after.vfmesh",
(-22, -19, -22.8), 0, 80, True),
("surface-world-l1-spawn-before", BEFORE / "surface-world-l1-spawn.vfmesh",
(0, -35, -487.94), 0, -22, False),
("surface-world-l1-spawn-after", AFTER / "surface-world-l1-spawn-after.vfmesh",
(0, -35, -487.94), 0, -22, False),
("surface-world-horizon-before", BEFORE / "surface-world-l1-spawn.vfmesh",
(0, -35, -487.94), 0, 0, False),
("surface-world-horizon-after", AFTER / "surface-world-l1-spawn-after.vfmesh",
(0, -35, -487.94), 0, 0, False),
("surface-world-rings-above-before", BEFORE / "surface-world-l1-spawn.vfmesh",
(0, -35, -280), 0, -90, True),
("surface-world-rings-above-after", AFTER / "surface-world-l1-spawn-after.vfmesh",
(0, -35, -280), 0, -90, True),
]
for name, scene, camera, yaw, pitch, tile_edges in views:
if not scene.exists():
raise SystemExit(f"missing scene: {scene}")
png = PNG_OUT / f"{name}.png"
jpg = OUT / f"{name}.jpg"
render_log = LOGS / f"{name}-render.log"
convert_log = LOGS / f"{name}-jpeg.log"
command = [str(CAPTURE), str(scene), str(png), "--size=1600x900",
"--shade=readable", f"--tile-edges={'on' if tile_edges else 'off'}",
"--tile-edge-width-m=0.15", "--camera-m="+",".join(map(str, camera)),
f"--yaw-deg={yaw}", f"--pitch-deg={pitch}"]
with render_log.open("w", encoding="utf-8") as stream:
subprocess.run(command, cwd=ROOT, env=env, stdout=stream,
stderr=subprocess.STDOUT, check=True)
ffmpeg = ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-y",
"-i", str(png), "-frames:v", "1", "-q:v", "4", str(jpg)]
with convert_log.open("w", encoding="utf-8") as stream:
subprocess.run(ffmpeg, cwd=ROOT, env=env, stdout=stream,
stderr=subprocess.STDOUT, check=True)
if jpg.stat().st_size > 300_000:
raise SystemExit(f"{jpg.name} exceeds the 300 KB review limit")
print(f"[Capture] {jpg.relative_to(ROOT)} bytes={jpg.stat().st_size} status=PASS", flush=True)
@@ -1,172 +0,0 @@
#!/usr/bin/env python3
"""Strict L19 audit of FloatingIslands roof coverage at every LOD."""
import array
import collections
import math
import struct
import sys
def read_exact(stream, size):
data = stream.read(size)
if len(data) != size:
raise EOFError(f"wanted {size} bytes, got {len(data)}")
return data
def height_at(triangles, x, y, preferred_z):
best = None
for (x0, y0, z0), (x1, y1, z1), (x2, y2, z2) in triangles:
den = (y1-y2)*(x0-x2)+(x2-x1)*(y0-y2)
if abs(den) < 1.0e-10:
continue
a = ((y1-y2)*(x-x2)+(x2-x1)*(y-y2))/den
b = ((y2-y0)*(x-x2)+(x0-x2)*(y-y2))/den
c = 1.0-a-b
if min(a,b,c) >= -1.0e-5:
z = a*z0+b*z1+c*z2
if best is None or abs(z-preferred_z) < abs(best-preferred_z):
best = z
return best
def quantile(values, fraction):
if not values:
return math.nan
ordered = sorted(values)
return ordered[math.floor(fraction*(len(ordered)-1))]
if len(sys.argv) != 3:
raise SystemExit("usage: CeilingContinuity.py scene.vfmesh expected-cap-plane-metres")
scene_path = sys.argv[1]
root_z = float(sys.argv[2])
tiles = {}
voxel_size_m = None
with open(scene_path, "rb") as stream:
if read_exact(stream, 8) != b"VFMSH001":
raise SystemExit("invalid VFMSH001 scene")
version, voxel_um, tile_count = struct.unpack("<3I", read_exact(stream, 12))
camera = struct.unpack("<3d", read_exact(stream, 24))
yaw, pitch = struct.unpack("<2d", read_exact(stream, 16))
voxel_size_m = voxel_um/1_000_000.0
for _ in range(tile_count):
key = struct.unpack("<4i", read_exact(stream, 16))
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", read_exact(stream, 20))
positions = array.array("f")
positions.frombytes(read_exact(stream, vertex_count*12))
normals = array.array("f")
normals.frombytes(read_exact(stream, vertex_count*12))
indices = array.array("I")
indices.frombytes(read_exact(stream, index_count*4))
masks = read_exact(stream, mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
extent = 32*(1 << key[3])*voxel_size_m
z0, z1 = key[2]*extent, (key[2]+1)*extent
ceiling = []
for triangle in range(index_count//3):
if masks and masks[triangle] != 0:
continue
a,b,c = indices[triangle*3:triangle*3+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.65:
continue
points = tuple((positions[v*3],positions[v*3+1],positions[v*3+2])
for v in (a,b,c))
center_z = sum(point[2] for point in points)/3.0
if abs(center_z-root_z) <= 1.0:
ceiling.append(points)
tiles[key] = {
"extent": extent, "x0": key[0]*extent, "y0": key[1]*extent,
"x1": (key[0]+1)*extent, "y1": (key[1]+1)*extent,
"z0": z0, "z1": z1, "ceiling": ceiling,
}
by_lod = collections.defaultdict(dict)
for key, tile in tiles.items():
if tile["z0"] <= root_z+1.0 and tile["z1"] >= root_z-1.0:
by_lod[key[3]][key] = tile
print(f"scene={scene_path} version={version} voxel_um={voxel_um} tiles={tile_count} "
f"camera_m={camera} yaw={yaw} pitch={pitch} expected_cap_m={root_z:.9f}")
print("lod zband_tiles cap_tiles missing_tiles cap_triangles face_z_m_p05/median/p95 "
"tile_samples hit_samples max_abs_root_delta_cm shared_edges expected_edge_samples "
"paired_edge_samples edge_gaps edge_delta_cm_median/p95/max")
all_pass = True
all_misses = []
for lod in range(9):
target = by_lod[lod]
cap_tiles = {key: tile for key,tile in target.items() if tile["ceiling"]}
face_z = [point[2] for tile in cap_tiles.values() for tri in tile["ceiling"]
for point in tri]
deltas = []
sample_count = 0
hit_count = 0
for key,tile in target.items():
if not tile["ceiling"]:
continue
for iy in range(16):
for ix in range(16):
x = tile["x0"]+(ix+0.5)*tile["extent"]/16
y = tile["y0"]+(iy+0.5)*tile["extent"]/16
sample_count += 1
height = height_at(tile["ceiling"],x,y,root_z)
if height is not None:
hit_count += 1
deltas.append(abs(height-root_z)*100.0)
else:
all_misses.append((lod,key,ix,iy,x,y))
edge_deltas = []
edge_count = 0
edge_gaps = 0
edge_gap_samples = 0
for key,a in cap_tiles.items():
x,y,z,level = key
for neighbor_key,axis in (((x+1,y,z,level),"x"),((x,y+1,z,level),"y")):
b = cap_tiles.get(neighbor_key)
if b is None:
continue
edge_count += 1
epsilon = min(0.02,a["extent"]*1.0e-4)
matched = 0
for index in range(33):
t = (index+0.5)/33
if axis == "x":
fixed = a["x1"]
varying = a["y0"]+(a["y1"]-a["y0"])*t
za = height_at(a["ceiling"],fixed-epsilon,varying,root_z)
zb = height_at(b["ceiling"],fixed+epsilon,varying,root_z)
else:
fixed = a["y1"]
varying = a["x0"]+(a["x1"]-a["x0"])*t
za = height_at(a["ceiling"],varying,fixed-epsilon,root_z)
zb = height_at(b["ceiling"],varying,fixed+epsilon,root_z)
if za is not None and zb is not None:
edge_deltas.append(abs(za-zb)*100.0)
matched += 1
edge_gap_samples += 33-matched
if matched != 33:
edge_gaps += 1
missing = len(target)-len(cap_tiles)
max_root = max(deltas) if deltas else math.nan
print(f"{lod} {len(target)} {len(cap_tiles)} {missing} "
f"{sum(len(tile['ceiling']) for tile in cap_tiles.values())} "
f"{quantile(face_z,.05):.6f}/{quantile(face_z,.5):.6f}/{quantile(face_z,.95):.6f} "
f"{sample_count} {hit_count} {max_root:.3f} {edge_count} {edge_count*33} {len(edge_deltas)} "
f"{edge_gaps} {quantile(edge_deltas,.5):.3f}/{quantile(edge_deltas,.95):.3f}/"
f"{max(edge_deltas) if edge_deltas else math.nan:.3f}")
if target and (missing or hit_count != sample_count or edge_gap_samples):
all_pass = False
for lod,key,ix,iy,x,y in all_misses:
print(f" miss lod={lod} key={key} sample=({ix},{iy}) xy_m=({x:.6f},{y:.6f})")
print(f"[CeilingContinuityL19] total_misses={len(all_misses)} "
f"result={'PASS' if all_pass else 'FAIL'} criteria=LOD0-8 every 16x16 interior ray hits; "
"every shared same-LOD edge sample has paired hits")
raise SystemExit(0 if all_pass else 1)
@@ -1,50 +0,0 @@
#!/usr/bin/env python3
"""Check deterministic matrices and protected SurfaceWorld identities against L14."""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
MATRIX = ROOT / "Saved/Linux/l19-matrix"
CROSSING = ROOT / "Saved/Linux/l19-crossing"
ROW = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
LOD = re.compile(r"^\[PipelineLODHash\] lod=(\d+) .*$", re.M)
failed = []
def stable_rows(path):
rows = ROW.findall(path.read_text(encoding="utf-8", errors="replace"))
full = next((row for row in rows if row.startswith("[PipelineFullDetailHash]")), None)
exact = next((row for row in rows if row.startswith("[PipelineExactQueryHash]")), None)
lods = {int(match.group(1)): match.group(0) for match in LOD.finditer(
path.read_text(encoding="utf-8", errors="replace"))}
return rows, full, exact, {level: lods.get(level) for level in range(3)}
for compiler in ("clang-off", "gcc-default"):
baseline_name = "clang-off" if compiler == "clang-off" else "gcc-default"
baseline = ROOT / f"Saved/Linux/l14-final-hash-sw-{baseline_name}-w12-r5.log"
expected = stable_rows(baseline)
logs = sorted(MATRIX.glob(f"surface-world-{compiler}-w*-r*.log"))
if len(logs) != 7:
failed.append(f"matrix {compiler}: expected 7 rows, found {len(logs)}")
for path in logs:
actual = stable_rows(path)
if len(actual[0]) != 12 or actual[1:] != expected[1:]:
failed.append(f"protected SurfaceWorld mismatch: {path.name}")
crossing_baseline_name = "clang" if compiler == "clang-off" else "gcc"
crossing_baseline = ROOT / f"Saved/Linux/l14-final-crossing-24m-{crossing_baseline_name}-w12.log"
expected_crossing = stable_rows(crossing_baseline)
crossing_logs = sorted(CROSSING.glob(f"{compiler}-w*-r*.log"))
if len(crossing_logs) != 7:
failed.append(f"crossing {compiler}: expected 7 rows, found {len(crossing_logs)}")
for path in crossing_logs:
actual = stable_rows(path)
if len(actual[0]) != 12 or actual[1:] != expected_crossing[1:]:
failed.append(f"protected crossing identity mismatch: {path.name}")
print(f"[L19ProtectedHashGate] SurfaceWorld LOD0-2, full-detail, and exact query hashes "
f"result={'FAIL' if failed else 'PASS'}")
for failure in failed:
print(f"[L19HashMismatch] {failure}")
raise SystemExit(1 if failed else 0)
@@ -1,123 +0,0 @@
# Linux19 — FloatingIslands ceiling and LOD ledges
**Status:** The ceiling is now closed at every LOD. The lower-terrain ledges are diagnosed but remain visible: the required change affects protected SurfaceWorld LOD2 output, so I stopped before changing it and asked for your decision.
## Findings and changes
The ceiling holes came from two checks in the shared boundary-cap path. It required density to cross from solid just below a strate's configured top to air just above it. Some of the field remains solid above that top, so the old test omitted those cap cells. The duplicate-cap check also sampled only inside each cell and could miss partial perimeter coverage. The path now emits a cap wherever the field is solid just below the configured top, and checks a 5×5 grid including the perimeter before suppressing a fallback cap.
The ledges are real upward-facing, non-skirt terrain triangles from the lower SurfaceWorld stratum visible below the FloatingIslands stratum. The combined recipe log identifies the SurfaceWorld field as strate index 1, chunks −16…−79, built from the owner surface parameters and biome map; the FloatingIslands plan is index 0, chunks 0…−15. At the worst LOD1→2 sample, the fine mesh is at z=−60.497 m and the coarse mesh at z=−61.996 m (149.862 cm apart). The LOD2→3 maximum is 773.318 cm, at z=−73.080 m versus −65.347 m.
The ring probe excludes skirts and masked triangles. The far-ground refinement routine only runs at LOD4 and above, so it cannot cause the LOD1→2 or LOD2→3 steps. Density dispatch is coordinate-based rather than LOD-based; the meshes sample that field on progressively coarser grids (0.5 m, 1 m, and 2 m at LOD1–3). The evidence points to resolution-dependent surface extraction of the lower SurfaceWorld terrain near the strate transition, not a different LOD recipe or a ceiling triangle. The L13 route audit also found no page/native or exact-query mismatches. The owner’s lower-stratum reading is supported.
SurfaceWorld at its L1 spawn has small L1→2 and L2→3 median differences, but it has larger outliers at farther rings: L3→4 reaches 1,190.974 cm, and L4→5 reaches 491.570 cm. Those high far-ring values are in the table below; the matched spawn, horizon, and above-ring captures are byte-identical before and after.
I did not change the ledge geometry. The most direct fix needs to change SurfaceWorld LOD2, which your rule protects pending an owner decision. The pending question is whether to allow that change.
The shared Core fix changes the recipe output identity version from `farHorizonVersion` 3 to 4. It changes no density query, stratum position, scale, or async route. No Unreal files were edited.
## Ring tables
Values are median / p95 / maximum absolute edge-height difference in cm, then the probe's no-hit edge count. `Before → after` is identical for every row because this round changed the ceiling path, not the lower upward-facing terrain.
### FloatingIslands camera
| LOD edge | Edges / samples | Before (cm; gaps) | After (cm; gaps) |
|---|---:|---:|---:|
| 0→1 | 0 / 0 | — | — |
| 1→2 | 33 / 231 | 147.141 / 148.403 / 149.862; 0 | 147.141 / 148.403 / 149.862; 0 |
| 2→3 | 49 / 308 | 128.603 / 200.789 / 773.318; 8 | 128.603 / 200.789 / 773.318; 8 |
| 3→4 | 64 / 277 | 3.269 / 47.774 / 188.531; 27 | 3.269 / 47.774 / 188.531; 27 |
| 4→5 | 72 / 544 | 29.992 / 124.373 / 443.482; 6 | 29.992 / 124.373 / 443.482; 6 |
| 5→6 | 64 / 266 | 12.234 / 127.224 / 536.573; 32 | 12.234 / 127.224 / 536.573; 32 |
| 6→7 | 32 / 264 | 29.013 / 301.752 / 42,624.479; 0 | 29.013 / 301.752 / 42,624.479; 0 |
| 7→8 | 64 / 275 | 83.843 / 1,225.416 / 3,755.407; 32 | 83.843 / 1,225.416 / 3,755.407; 32 |
### SurfaceWorld L1 spawn camera
| LOD edge | Edges / samples | Before (cm; gaps) | After (cm; gaps) |
|---|---:|---:|---:|
| 0→1 | 46 / 288 | 0.178 / 0.594 / 1.171; 10 | 0.178 / 0.594 / 1.171; 10 |
| 1→2 | 32 / 288 | 1.419 / 4.990 / 10.438; 0 | 1.419 / 4.990 / 10.438; 0 |
| 2→3 | 34 / 256 | 2.179 / 12.648 / 35.355; 2 | 2.179 / 12.648 / 35.355; 2 |
| 3→4 | 40 / 265 | 5.099 / 35.999 / 1,190.974; 5 | 5.099 / 35.999 / 1,190.974; 5 |
| 4→5 | 58 / 270 | 0.235 / 3.694 / 491.570; 14 | 0.235 / 3.694 / 491.570; 14 |
| 5→6 | 65 / 266 | 12.234 / 127.224 / 536.573; 33 | 12.234 / 127.224 / 536.573; 33 |
| 6→7 | 41 / 269 | 28.238 / 291.861 / 945.477; 7 | 28.238 / 291.861 / 945.477; 7 |
| 7→8 | 32 / 275 | 83.843 / 1,225.416 / 3,755.407; 0 | 83.843 / 1,225.416 / 3,755.407; 0 |
## Ceiling continuity
The strict audit casts 16×16 interior rays per tile and pairs every shared same-LOD edge sample. Before the fix it found three LOD1 interior misses, one LOD2 interior miss, and 10 unpaired LOD1 perimeter samples. After the fix it found zero misses and zero edge gaps at all LODs.
| LOD | Cap tiles | Interior hits | Paired edge samples | Max root delta (cm) | Misses / gaps |
|---:|---:|---:|---:|---:|---:|
| 0 | 64/64 | 16,384/16,384 | 3,696/3,696 | 0 | 0 / 0 |
| 1 | 48/48 | 12,288/12,288 | 2,376/2,376 | 0 | 0 / 0 |
| 2 | 48/48 | 12,288/12,288 | 2,376/2,376 | 0.025 | 0 / 0 |
| 3 | 48/48 | 12,288/12,288 | 2,376/2,376 | 0.025 | 0 / 0 |
| 4 | 48/48 | 12,288/12,288 | 2,376/2,376 | 0 | 0 / 0 |
| 5 | 48/48 | 12,288/12,288 | 2,376/2,376 | 0 | 0 / 0 |
| 6 | 48/48 | 12,288/12,288 | 2,376/2,376 | 0 | 0 / 0 |
| 7 | 48/48 | 12,288/12,288 | 2,376/2,376 | 0 | 0 / 0 |
| 8 | 153/153 | 39,168/39,168 | 8,976/8,976 | 0 | 0 / 0 |
Full output is in `Saved/Linux/l19-diagnostics/fi_roof_after.log`; the pre-fix strict audit is `Saved/Linux/l19-diagnostics/baseline-fi-ceiling-strict.log`.
## Hashes and audits
Clang 19 (`-ffp-contract=off`) and GCC 14 (default) passed 28/28 runs: FI and SW, W1/W8/W12, with W12 repeated five times. The 24 m crossing passed 14/14 runs, with 12 matching hash rows each; its W1/W8/W12×5 hashes also matched the L14 compiler-specific references. The protected SurfaceWorld hash gate passed across the matrix and crossing. SurfaceWorld LOD0–2, full-detail, and exact-query hashes match L14 across the matrix and crossing; the representative W12 LOD3 hash also matches. LOD4–8 mesh/payload/topology hashes include the version-4 salt, and the tested captures show no visible SW change.
Representative Clang W12 hashes (density / mesh / payload / topology):
| Output | Full detail | Whole view |
|---|---|---|
| FloatingIslands | `43034299d33f2536 / a41e8353ec4bbe9c / cd0e3e15eef827b0 / 0b3c3f13cc1d6f8a` | `00bc3607a7839854 / 4baa66127c2b0892 / e11ef05e851a1517 / 1278bc4b1fffb869` |
| SurfaceWorld | `0ee0579bd15cbbe4 / 32e5d4964d610fee / 1932b96ee4b1d910 / 69743c6bad3447dc` | `c10697a8f48380c7 / 4197fc1072f0ff58 / 7b3e616ec9459851 / c879e35592bf33e5` |
SurfaceWorld LOD0–2 hashes (density / mesh / payload / topology):
| LOD | Hash |
|---:|---|
| 0 | `769f2a706d585a72 / 33eeeb39fa443816 / 645621a1d968983f / c2457ed905036c9d` |
| 1 | `69cc5a5c0a2c60af / b48335f5410c57d5 / d15c5480162ab8ba / feca1bd025823604` |
| 2 | `85deed5683a77202 / 53469895987aeea2 / e55f8ae7e1532033 / d5feb12a0e85c09b` |
Exact queries: FI density/unclamped `87e5c0dbf9ee9ee2`; SW density/unclamped `e4d4a8eb95b35933`; both detail `efcba20c0c70c415`, collision `e604823a249029bf`.
L13 route parity passed in all four audit runs (both recipes, both compilers), with zero mismatches. FI checked 782,026 exact-query samples; SW checked 910,651. L9 proof audits reported zero violations: FI 9,192 blocks / 12,234,552 samples; SW 27,479 / 36,574,549 samples. Full logs are in `Saved/Linux/l19-audits/`.
L3 FP-contract preamble/self-test, L4 `FLazy`, L7 shared cache, L9/L12 proofs, L11 readiness and crash handling, L13 route binding, the L18 shared ceiling, `ARCHITECTURE.md` §8.10, and the CLAUDE.md async rules remain intact. The only source edits are in shared Core; the only architecture addition is §8.38.
## Captures
Every JPEG is at most 50 KiB (limit 300 KB). `floating-islands-owner-ground-after.jpg` is the low view across the FI ground, matching the owner's angle. SurfaceWorld spawn, horizon, and above-ring before/after JPEGs are byte-identical.
- FI: `captures/floating-islands-flat-square-before.jpg`, `captures/floating-islands-flat-square-after.jpg`, `captures/floating-islands-owner-angle-before.jpg`, `captures/floating-islands-owner-angle-after.jpg`, `captures/floating-islands-owner-ground-before.jpg`, `captures/floating-islands-owner-ground-after.jpg`, `captures/floating-islands-ceiling-up-after.jpg`.
- SW: `captures/surface-world-l1-spawn-before.jpg`, `captures/surface-world-l1-spawn-after.jpg`, `captures/surface-world-horizon-before.jpg`, `captures/surface-world-horizon-after.jpg`, `captures/surface-world-rings-above-before.jpg`, `captures/surface-world-rings-above-after.jpg`.
## Failed and retried runs
- The pre-fix strict FI ceiling audit failed as expected: 4 interior misses and 10 unpaired LOD1 edge samples. The earlier loose report had counted only the 3 LOD1 interior misses. The final strict audit passed.
- A ceiling audit was mistakenly run on the ordinary SW terrain export; it reported 47,837 non-roof ray misses. That was the wrong target for this audit and was discarded.
- The first continuity-audit attempt was interrupted before results. `/usr/bin/time` was unavailable (exit 127); the repository `MeasureBuild.py` wrapper was used instead.
- The first FI auto-camera export used clip radius 0 / max LOD 0 and exited with `std::bad_alloc` (1.710 s, peak RSS 2,151,292 KiB). Retrying at radius 3 / LOD8 / W12 succeeded (10.786 s, peak RSS 597,976 KiB).
- An exploratory native-heightfield refinement extension did not change the FI L1→2 or L2→3 ring values and was reverted. A temporary debug probe/build used to inspect the ceiling field was also removed before the final build.
- The first hash-gate run happened before crossing logs existed and failed for zero crossing rows. After the 24 m crossing completed, the gate passed.
- The first SW ring probe did not inherit the CPU affinity limit; it was rerun under `taskset -c 0-11`, and the constrained table is unchanged.
- `renice -n 0` was denied by the environment. Runs stayed at inherited nice level 5 (below normal priority); the governor was left unchanged at `powersave`.
No build or run exceeded 15 minutes or 30 minutes. All final builds and run children used CPUs 0–11, at most 12 workers, `LP_NUM_THREADS=12` for llvmpipe, and the 6 GiB address-space cap. Peak RSS was 903,324 KiB (GCC build); the matrix, crossing, audits, roof audit, and captures peaked at 583,180, 550,832, 857,964, 541,972, and 510,680 KiB respectively. The outer matrix took 343.684 s; the crossing took 46.354 s. Governor: `powersave`.
**Owner note:** The FI ceiling now closes cleanly at z=+4 m. The visible ledges are lower SurfaceWorld terrain and are a real LOD sampling mismatch, not skirts. SurfaceWorld L1→2 and L2→3 agree closely at the spawn, but its far rings have larger outliers. The ledge fix needs permission to change SurfaceWorld LOD2; no such mesh change is included here. Nothing was committed, pushed, or stashed.
## Orchestrator review (Claude)
- Merged. The ceiling specks seen in L18's flat-square capture are gone in the L19 capture (checked by me),
and the strict audit reads 0 misses / 0 gaps at all LODs. SurfaceWorld LOD0–2, full-detail and exact
queries are unchanged; the far mesh hashes carry the `farHorizonVersion` 3→4 salt, with byte-identical
captures.
- Luna correctly stopped at the protected SurfaceWorld LOD2 rule. Ledge decision: the defect is at the
strate interface (lower stratum top near z≈−60…−73 m, sampled on 0.5/1/2 m grids at LOD1–3), which is
where the two chunk-unit conventions meet. It goes into L20 (the units/layers design round) as its
concrete test case, rather than being patched alone first.
@@ -1,79 +0,0 @@
#!/usr/bin/env python3
"""Run the L9 uniform-block proof and L13 route parity audits."""
import os
import signal
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l19-audits"
OUT.mkdir(parents=True, exist_ok=True)
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
BINARIES = {
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-gcc",
}
RECIPES = {
"floating-islands": ("-22,-19,-22.8,0,-4", "--recipe=floating-islands"),
"surface-world": ("0,-35,-487.94,0,-22", "--recipe=surface-world"),
}
passed = True
for recipe, (camera, recipe_flag) in RECIPES.items():
for compiler, binary in BINARIES.items():
name = f"{recipe}-{compiler}"
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",
"--workers=12", f"--camera={camera}", recipe_flag,
"--audit-uniform-block-proofs", "--audit-route-parity"]
started = time.monotonic()
peak_rss = 0
timed_out = False
with logfile.open("w", encoding="utf-8") as stream:
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
start_new_session=True)
while process.poll() is None:
pending, seen = [process.pid], set()
while pending:
pid = pending.pop()
if pid in seen:
continue
seen.add(pid)
try:
pending.extend(int(value) for value in
Path(f"/proc/{pid}/task/{pid}/children").read_text().split())
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
if line.startswith("VmRSS:"):
peak_rss = max(peak_rss, int(line.split()[1]))
break
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
pass
if time.monotonic() - started > 900:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
timed_out = True
passed = False
break
time.sleep(0.1)
code = process.wait()
elapsed = time.monotonic() - started
log = logfile.read_text(encoding="utf-8", errors="replace")
route_lines = [line for line in log.splitlines() if line.startswith("[RouteParityAudit]")]
proof_lines = [line for line in log.splitlines() if line.startswith("[UniformBlockProofAudit]")]
route_ok = bool(route_lines) and route_lines[-1].endswith("status=PASS")
proof_ok = bool(proof_lines) and "violations=0" in proof_lines[-1]
ok = code == 0 and not timed_out and route_ok and proof_ok
passed &= ok
print(f"[AuditRun] {name} exit={code} seconds={elapsed:.3f} peak_rss_kib={peak_rss} "
f"route={route_ok} proofs={proof_ok} status={'PASS' if ok else 'FAIL'}", flush=True)
print(f"[AuditSummary] rows={len(RECIPES)*len(BINARIES)} expected=4 "
f"result={'PASS' if passed else 'FAIL'} path={OUT}", flush=True)
raise SystemExit(0 if passed else 1)
@@ -1,81 +0,0 @@
#!/usr/bin/env python3
"""Run the L19 24 m SurfaceWorld crossing hash matrix."""
import os
import re
import signal
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l19-crossing"
OUT.mkdir(parents=True, exist_ok=True)
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
BINARIES = {
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-gcc",
}
PATTERN = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
RUNS = [(1, 1), (8, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 5)]
passed = True
for compiler, binary in BINARIES.items():
reference = None
for workers, repeat in RUNS:
name = f"{compiler}-w{workers}-r{repeat}"
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=0,-35,-487.94,0,-22",
"--camera-step=24,0,0", "--region-crossing", "--recipe=surface-world"]
started = time.monotonic()
peak_rss = 0
timed_out = False
with logfile.open("w", encoding="utf-8") as stream:
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
start_new_session=True)
while process.poll() is None:
pending, seen = [process.pid], set()
while pending:
pid = pending.pop()
if pid in seen:
continue
seen.add(pid)
try:
pending.extend(int(value) for value in
Path(f"/proc/{pid}/task/{pid}/children").read_text().split())
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
if line.startswith("VmRSS:"):
peak_rss = max(peak_rss, int(line.split()[1]))
break
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
pass
if time.monotonic() - started > 900:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
timed_out = True
passed = False
break
time.sleep(0.1)
code = process.wait()
elapsed = time.monotonic() - started
hashes = PATTERN.findall(logfile.read_text(encoding="utf-8", errors="replace"))
ok = code == 0 and not timed_out and len(hashes) == 12
if reference is None:
reference = hashes
elif hashes != reference:
ok = False
print(f"[CrossingMismatch] {name}", flush=True)
passed &= ok
print(f"[CrossingRun] {name} exit={code} seconds={elapsed:.3f} "
f"peak_rss_kib={peak_rss} hash_rows={len(hashes)} "
f"status={'PASS' if ok else 'FAIL'}", flush=True)
print(f"[CrossingSummary] rows={len(BINARIES)*len(RUNS)} expected=14 "
f"result={'PASS' if passed else 'FAIL'} path={OUT}", flush=True)
raise SystemExit(0 if passed else 1)
@@ -1,88 +0,0 @@
#!/usr/bin/env python3
"""Run the L19 W1/W8/W12 x5 matrix under inherited taskset limits."""
import os
import re
import signal
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l19-matrix"
OUT.mkdir(parents=True, exist_ok=True)
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
BINARIES = {
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-gcc",
}
RECIPES = {
"floating-islands": "-22,-19,-22.8,0,-4",
"surface-world": "0,-35,-487.94,0,-22",
}
PATTERN = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
RUNS = [(1, 1), (8, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 5)]
passed = True
for recipe, camera in RECIPES.items():
for compiler, binary in BINARIES.items():
if not binary.exists():
raise SystemExit(f"missing binary: {binary}")
reference = None
for workers, repeat in RUNS:
name = f"{recipe}-{compiler}-w{workers}-r{repeat}"
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}", f"--camera={camera}",
f"--recipe={recipe}"]
started = time.monotonic()
peak_rss = 0
timed_out = False
with logfile.open("w", encoding="utf-8") as stream:
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
start_new_session=True)
while process.poll() is None:
pending, seen = [process.pid], set()
while pending:
pid = pending.pop()
if pid in seen:
continue
seen.add(pid)
try:
pending.extend(int(value) for value in
Path(f"/proc/{pid}/task/{pid}/children").read_text().split())
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
if line.startswith("VmRSS:"):
peak_rss = max(peak_rss, int(line.split()[1]))
break
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
pass
if time.monotonic() - started > 900:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
timed_out = True
passed = False
break
time.sleep(0.1)
code = process.wait()
elapsed = time.monotonic() - started
hashes = PATTERN.findall(logfile.read_text(encoding="utf-8", errors="replace"))
ok = code == 0 and not timed_out and len(hashes) == 12
if reference is None:
reference = hashes
elif hashes != reference:
ok = False
print(f"[MatrixMismatch] {name}", flush=True)
passed &= ok
print(f"[MatrixRun] {name} exit={code} seconds={elapsed:.3f} "
f"peak_rss_kib={peak_rss} hash_rows={len(hashes)} "
f"status={'PASS' if ok else 'FAIL'}", flush=True)
print(f"[MatrixSummary] rows={len(RECIPES)*len(BINARIES)*len(RUNS)} expected=28 "
f"result={'PASS' if passed else 'FAIL'} path={OUT}", flush=True)
raise SystemExit(0 if passed else 1)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

@@ -1,81 +0,0 @@
# Linux20 — design research for metre-based strata and generation layers
**Date:** 2026-10-20
**Scope:** read current owner/Core code and L19 evidence; measure unit conversions and LOD ring
errors; write the L20 design only.
## Findings
- `CHUNK_SIZE` is 32 and legacy `VOXEL_SIZE` is 12.5 cm, so the owner chunk is 4 m
(`Source/VoxelForge/Public/VoxelTypes.h`). Core defaults to 25 cm and keeps 32 voxels per tile.
- `BuildSurfaceStratumPlan` computes SurfaceWorld layout from `Slot.*ChunkZ × CHUNK_SIZE` in Core
voxel units. This is 32 × 25 cm = **8 m per slot chunk**. L18 records this as the retained
SurfaceWorld `topChunkZ × 32` interpretation.
- `BuildFloatingIslandStratumPlan` serializes owner/Core voxel size and the reduced ratio. At the
default 12.5/25 cm ratio, its slot chunk is 32 × 0.5 = 16 Core voxels = **4 m**. Core reverses
sample Z using `GetCoreVoxelsPerOwnerChunk` in `ShapeWorldInternal.h`.
- This factor-of-two difference is real, but it is not enough to claim that it caused the L19 ring
heights. L19's ring evidence points to LOD-dependent extraction of the lower SurfaceWorld field.
It does mean there is no single metres-per-chunk conversion that both preserves the current native
bounds and makes their shared slot indices one physical interface. The design records the current
resolved bounds and treats a later interface unification as an explicit change.
- The L19 FloatingIslands-camera table reports LOD1→2 median / p95 / max = 147.141 / 148.403 /
149.862 cm, zero no-hit samples; LOD2→3 = 128.603 / 200.789 / 773.318 cm, with 8 no-hit samples
among 308. LOD1/2/3 step sizes at 25 cm are 0.5/1/2 m. The ring audit excludes skirts and masked
triangles. Native far-ground refinement starts at LOD4, so it cannot fix those two transitions.
- The exact location and current geometry evidence are from `Linux19/REPORT.md`: lower SurfaceWorld
terrain at approximately −60…−73 m; its terrain triangles remain visible below FloatingIslands.
The worst sampled LOD1→2 roots are −60.497 m and −61.996 m. The LOD2→3 pair is −73.080 m and
−65.347 m.
## Layer map
The owner-facing layout is composed by `VoxelStrateComposer.cpp`, stored/loaded by
`VoxelSeasonManifest.cpp`, and resolved into `FStrateSlot`s by `VoxelStrateManager.cpp`. The Core
snapshot adapter (`VoxelForgeCoreAdapter.cpp`) builds separate SurfaceWorld and FloatingIslands
plans. Core streams work from `VoxelForgeCore.cpp` and evaluates native fields in
`WorldAssemblerInternal.h` / `FloatingIslandsInternal.h`; legacy `UVoxelGenerator::GetDensityAt`
and operator-stack/cave paths remain in `VoxelGenerator.cpp`, `VoxelDensityOpStack.cpp`, and
`VoxelCaveMorphology.cpp`.
Shared pieces already include Core's FP-contract preamble, fixed-point field code, feature bounds,
column caches, and interval proofs. `ShapeWorldInternal.h` still chooses among native fields, manager
state, and the copied legacy evaluator. `World::generateTile` in `VoxelForgeCore.cpp` owns MC setup,
recipe feature branches, native SurfaceWorld far-ground/cap refinement, and recipe identity salts.
`FarShellsInternal.h` adds shared boundary ceilings and GPU far-shell repairs; its connector repair
input is prepared from FloatingIslands geometry in `ShapeWorldInternal.h`.
The complete mapping, boundary proposal, ranked violations, generator contract, and migration plan
are in [`DESIGN_STRATA_UNITS_AND_LAYERS.md`](../../../DESIGN_STRATA_UNITS_AND_LAYERS.md).
## Direction review
The proposed metre authoring, 25 cm per-strate default, fixed 32³ internal tiles, and separation of
director/generators/shared composition/mesher are sound. Two qualifications are recorded in the
design:
1. “Units cost nothing” is true for metre conversion itself. Voxel size affects the physical width
of 32³ tiles and the number of tiles/tasks/caches; halving spacing in XYZ implies about 8× the
density samples per physical volume, with more per-tile overhead on top.
2. A no-movement migration cannot use one global chunk-to-metre multiplier. Current Core paths use
8 m per SurfaceWorld slot chunk and 4 m per FloatingIslands slot chunk at defaults. Preserve and
report those current resolved bounds first; decide separately whether to leave any gap/overlap or
intentionally make the new metre-authored interface contiguous.
The L19 density/mesh evidence supports LOD extraction as the direct cause of the measured lower-ground
ring steps. A shared explicit ceiling surface is the right approach for seals/caps, but not sufficient
for those upward-facing ground ledges. Step 5 therefore exposes the SurfaceWorld ground profile to a
generic explicit-surface/transition contract and remains subject to the protected LOD2 approval.
## Output and worktree status
No source, manifest, generator, mesher, or build input was changed. No build or world run was made.
Thus no new W12 hash claim is needed for this documentation-only round. The L19 reference hashes
remain the expected equality gates for later source changes:
- SurfaceWorld whole view: `c10697a8f48380c7 / 4197fc1072f0ff58 / 7b3e616ec9459851 / c879e35592bf33e5`
- FloatingIslands whole view: `00bc3607a7839854 / 4baa66127c2b0892 / e11ef05e851a1517 / 1278bc4b1fffb869`
The pre-existing untracked `Standalone/Research/WorldAssemblerM3/Linux18/exports/` directory was
left untouched. No commit, push, or stash was made.
@@ -1,233 +0,0 @@
# Sol 6.1 (medium) read-only review of the L20 design — 2026-10-20
Requested by the owner. Prompt: `Saved/Linux/claude/sol-l20-review.md` (copied below the answer). Session `01a1582c-547a-7701-9d9e-35239b7f0a26`, 68,458 tokens, sandbox read-only.
**The direction is right. Preserve today’s world during conversion, but treat inconsistent bounds as compatibility data, not valid new layout. Allow the SurfaceWorld LOD2 boundary fix, with a precisely defined affected region and unchanged density.**
L20 needs a stronger bedrock model and an earlier ownership contract. Adding floor bedrock everywhere is also a deliberate geometry change: current FloatingIslands v3 explicitly omits its lower seal.
I only read files; I did not build, run, or modify anything.
**1. The two decisions**
**Decision 1: preserve first, repair separately.** L17 demonstrates why: unifying the chunk scale removed the approved SurfaceWorld ground and moved the world ([Linux17/REPORT.md:168](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux17/REPORT.md:168)).
The mismatch is real. SurfaceWorld uses slot chunks directly as 32 Core voxels; FloatingIslands applies the owner/Core ratio ([VoxelForgeCoreAdapter.cpp:290](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:290), [VoxelForgeCoreAdapter.cpp:464](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:464)).
However, preserve **effective behavior**, not just converted endpoints. Dispatch itself uses different conversions: SurfaceWorld divides Z by 32, while FloatingIslands uses the owner/Core scale ([ShapeWorldInternal.h:2130](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2130), [ShapeWorldInternal.h:2158](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2158)). An apparent interval between bounds therefore does not prove that interval currently generates air.
Keep historical overlaps and ambiguities in a versioned compatibility snapshot. **Reject overlaps in newly authored layouts.** Otherwise preservation becomes permanent acceptance of the bug.
**Decision 2: approve the change in principle.** L19 measured actual lower-ground extraction differences, not ceiling triangles: up to 1.50 m and 7.73 m ([Linux19/REPORT.md:9](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux19/REPORT.md:9)). An explicit ceiling cannot fix these.
Define the affected tiles from the observed surface and transition support, including halos—not merely distance from a nominal strate plane. Preserve exact queries and unaffected near-player geometry. Validate against the same physical surface so the audit cannot “improve” by pairing different surfaces or hiding terrain.
Metre authoring and default 25 cm voxels are sound. Fixed 32³ tiles mean physical tile width still follows voxel size; if Jahni wants fixed physical streaming regions, introduce those separately from internal tiles.
**2. A concrete strate structure**
Use one immutable, versioned director output. Distinguish a strate’s **generator interior** from the bedrock separating interiors. This makes thickness and ownership unambiguous.
```text
ResolvedWorld
schemaVersion, evaluatorVersion, seed, worldIdentity
physicalUnit = fixed integer unit independent of voxel size
strates[] // ordered downward
verticalSegments[] // exact partition of declared world extent
interfaces[]
passages[]
Strate
stableId // persisted; independent of array position
order
bottomM, topM // generator interior: [bottomM, topM)
voxelSizeM = 0.25
latticeOriginM
generatorKind
recipeId, recipeVersion, resolvedRecipe, seedKey
floorBandId, ceilingBandId
interfaceAboveId, interfaceBelowId
VerticalSegment
stableId, bottomM, topM
kind = StrateInterior | Bedrock | VoidGap
ownerId
BedrockBand
stableId, bottomM, topM
thicknessM = topM - bottomM
volumeMaterialId
upperFaceMaterialId, lowerFaceMaterialId
digPolicy = Diggable | Protected | Restricted
allowedPassageIds[]
surfaceDescriptorIds[]
Interface
stableId, upperStrateId, lowerStrateId
orderedSegmentIds[] // bedrock, optional void, further bedrock
passagePlacementRecipe
resolvedPassageIds[]
Passage
stableId, interfaceId
physical tunnel shape, aperture boundaries
upper/lower endpoints, landing/support geometry
crossedBandIds[], clearance, carvePermission
```
Author in metres; serialize canonical fixed physical units with explicit rounding. The resolved fields above are produced once, not independently editable copies.
For a shared slab, the upper strate’s floor and lower strate’s ceiling reference **the same band**. Its thickness and digging policy cannot disagree between sides. Separate skins with a void between them require three explicit segments.
I recommend ordinary inter-strate bedrock remain diggable, preserving the established player-dug descent behavior ([ARCHITECTURE.md:472](/home/jahni/VoxelM/Plugins/VoxelForge/ARCHITECTURE.md:472)). Protection should be an explicit band policy.
The invariants are:
- Unique stable IDs; explicit order; finite bounds; positive interior height and bedrock thickness.
- Every physical height within the declared stack belongs to exactly one segment. No overlaps or implicit gaps.
- Every strate has floor and ceiling bedrock, including terminal strates.
- Interiors remain closed except through registered passages or authorized player-created apertures.
- A void gap lies between closed skins. A freely open connection requires an explicit aperture, potentially covering the whole interface.
- Passages cross every intervening band and provide usable entrances, exits, and support.
- All generators, proofs, streaming, meshing, collision, and material queries consume this snapshot. None reconstruct boundaries from chunk indices.
Keep legacy recipe seed keys frozen during migration. Switching randomness from strate index to stable ID must be versioned separately to avoid changing the world.
**3. Bedrock should be both volume and explicit geometry**
Use an authoritative solid volume for density, digging, collision queries, and conservative proofs, plus explicit boundary surfaces for reliable extraction. Both representations derive from the same band and aperture descriptors.
Density alone cannot guarantee a thin band survives coarse sampling. Conversely, a visual plane alone supplies no solid interior and can disagree with collision or digging.
Current code illustrates the mismatch: the upper seal fills inward over its thickness, while fallback ceiling vertices are placed at `Bounds.TopZ` ([VoxelDensityPrimitives.h:44](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Public/VoxelDensityPrimitives.h:44), [FarShellsInternal.h:1238](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:1238)). The new contract must distinguish the band’s outer boundary from its room-facing surface.
For a ceiling above an interior, its lower face is at `topM`; for a floor below an interior, its upper face is at `bottomM`. Thickness extends away from the interior.
The mesher should own each exposed face once, suppress overlapping ordinary extraction, and use shared edge anchors across LODs. Passage mouths need explicit clipping and wall geometry; corner sampling alone can miss a small aperture.
Authorized edits must modify both density and surface extraction. Protected bands require explicit edit masking. Generated passages carve after bedrock composition, consistent with today’s passage ordering ([VoxelDensityPrimitives.h:86](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Public/VoxelDensityPrimitives.h:86)).
Collision uses the same geometry semantics at a gameplay resolution independent of camera LOD. Peers reproduce bands and generated passages from the versioned plan; only player edits need synchronization.
Cost stays local: cheap band queries and flat faces generally, extra refinement around openings and edited areas.
**4. What I would change in L20**
My top three risks are:
1. **Migrating bounds without migrating dispatch.** Today’s “resolved” bounds still consult the manager and branch by generator ([ShapeWorldInternal.h:67](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:67)). Snapshot ownership, fallback behavior, and recipe anchors together.
2. **Changing gameplay under structural cleanup.** FloatingIslands v3 applies only an upper seal ([ShapeWorldInternal.h:2192](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2192)). Restoring a floor changes visibility and descent. Require a new layout version; do not silently reintroduce the removed floor.
3. **Maintaining conflicting density, mesh, and collision boundaries.** Explicit surfaces must be authoritative constraints derived from bands, rather than another repair overlay.
Reorder the migration to: compatibility snapshot → canonical ownership and bedrock schema → minimal shared boundary composition → targeted LOD transition prototype → metre authoring and generator adapters. Keep the broad generator/helper refactor incremental. Mixed resolutions remain last.
**5. Smallest first implementation**
Implement a **versioned resolved-layout snapshot and validator**, populated by the existing director/adapter. Record actual physical bounds, recipe/cap anchors, voxel sizes, current floor/ceiling behavior, and dispatch provenance. Flag incompatible intervals explicitly. Leave evaluation unchanged.
Its acceptance test should require:
- Exact preservation of existing resolved anchors and recipe values, including routes and cap profiles.
- Boundary probes immediately below, at, and above every endpoint, comparing actual old dispatch with the snapshot’s recorded behavior.
- Deterministic identity across worker counts and serialization round trips.
- Rejection of overlapping or uncovered new layouts.
- Existing SurfaceWorld density and geometry hashes unchanged; L19 ceiling coverage unchanged.
This delivers inspectable strate structure first. Enforcing new floors and repairing ledges then become separate, measurable world changes.
---
## Prompt
# VoxelForge — design review (Sol, read-only): strate structure, bedrock and the L20 decisions
You are reviewing a design for VoxelForge, a voxel terrain plugin for a multiplayer Unreal Engine
game, with its generation done in a C++ Core library. The game is underground: the world is a
vertical stack of **strates** (layers). Each strate has its own generator type:
- SurfaceWorld: an enclosed surface-like heightfield world;
- FloatingIslands;
- caves, and future types.
Players travel between strates through passages. Every machine generates the same terrain from a
seed. Only player edits cross the network.
**This is a read-only review.** Read files, but don't build, run, edit or create anything. The
owner pays for this review, so be efficient: read what you need, and keep the answer focused and
concrete.
## Read first
- `/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/DESIGN_STRATA_UNITS_AND_LAYERS.md`: the design
under review (round L20).
- `/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux20/REPORT.md`
- For the bugs that motivated it: `.../Linux17/REPORT.md`, `.../Linux18/REPORT.md` and
`.../Linux19/REPORT.md`, in the same folder. Each has an "Orchestrator review" section at the end.
- Code, as needed:
- `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` (plan building: SurfaceWorld vs
FloatingIslands chunk conversion);
- `Source/VoxelForge/Private/VoxelStrateManager.cpp` and `Source/VoxelForge/Public/VoxelStrateTypes.h`
(strate slots and params);
- `Standalone/Core/src/ShapeWorldInternal.h` (`FResolvedStratumBounds`, `GetResolvedStratumBounds`);
- `Standalone/Core/src/FarShellsInternal.h` (`AppendStratumBoundaryCeilings`);
- `Source/VoxelForge/Public/VoxelDensityPrimitives.h` (boundary seals);
- `CLAUDE.md`, and `ARCHITECTURE.md` (§8) for the hard rules.
## The owner's direction
The owner is Jahni. Quotes:
- "Everything should use a parameter like on the strate, saying which voxel size it is, by default
25 for everyone, same for island."
- "Our system should be in different categories: the director/orchestrator, the generators (all
using the same kind of format, helper functions … from a shared library like noise functions) and
the mesher; the generators are dictated by our current strate parameters."
- "The voxel size shouldn't dictate the size of a chunk … wouldn't it be better to have like Unreal
unit distance, and have x voxels fill it?"
- "Any strate should have a ceiling, since we're underground."
- "The ceiling height is decided by the strate parameter, it's not something unique to sky island."
- Now: **"we need proper strate structure, knowing where each starts etc."**, including the strate
**bedrocks (ceiling and floor)**.
## What I (the orchestrator) recommended, which he wants judged
1. **Layout is authored in metres;** each strate has a voxel size (default 25 cm); internal tiles
stay 32³ voxels. Units cost nothing at run time, while the voxel count does.
2. **Decision 1 (preservation):** when converting today's chunk-based strate parameters to metres,
keep every strate exactly where it currently is in the generated world, including any gap or
overlap that the current 8 m (SurfaceWorld) vs 4 m (FloatingIslands) per-chunk mismatch creates.
Report those gaps and overlaps, then tune in metres. The alternative is to force them to meet
during the migration.
3. **Decision 2:** allow step 5 of the migration (an explicit, LOD-stable interface surface) to
change SurfaceWorld LOD2 output near strate boundaries, to fix the owner's "ledges" (cross-LOD
ring steps of up to 1.5 m and 7.7 m at the FloatingIslands/SurfaceWorld interface). Near-player
terrain away from boundaries must stay identical.
## Questions (answer each)
1. **Are the two decisions and the direction right?** Say where you'd decide differently, and why.
Is preserving today's possibly-inconsistent bounds the right first step, or does it entrench a
bug? Is there a better migration order?
2. **Propose a proper strate structure.** A concrete data model the director owns. For each strate:
- its stable ID and order;
- top and bottom in metres;
- **floor bedrock and ceiling bedrock:** thickness in metres, material, whether players can dig
through, and how passages pierce them;
- its voxel size;
- its generator and recipe;
- its interfaces to the strate above and below: solid bedrock band, open void, passage
placement.
Define the invariants: no overlaps; every metre owned by exactly one strate or one explicit
bedrock/gap band; ceiling and floor always closed, except at passages; contiguous or explicit
gaps. Show how "where each strate starts and ends" becomes a single source of truth that every
generator and the mesher read, never re-derive.
3. **Bedrock as geometry.** Should a ceiling or floor be a density band (a seal), an explicit
surface the mesher knows about, or both? Weigh LOD stability (the ledge problem: thin seals
extract at different heights per LOD), collision, player digging, multiplayer determinism, and
cost. Give a recommendation.
4. **What in the L20 design would you change, cut, or reorder?** Name the top 3 risks.
5. **The smallest first implementation step** that delivers the strate structure without moving the
approved SurfaceWorld world, and its acceptance test.
## Answer format
Plain English. The owner reads it, so lead with your verdicts in a few lines, then the details.
Around 1,500 words at most, plus the data-model sketch. Cite file:line where you rely on code.
@@ -1,203 +0,0 @@
# World Assembler L21 — resolved strate layout
This round adds a read-only layout snapshot and validator. It does not change density or mesh
evaluation.
## Snapshot and current stack
World identity is `owner-world/seed/0`; Core voxel size is 0.25 m, owner voxel size is 0.125 m, and
all lattice origins are `(0,0,0)`. Intervals below are half-open `[bottom, top)` metres. Stable strate
IDs are `owner-world/seed/0/strate/<index>/generator/<kind>`. The shared Core record uses canonical
JSON schema v1 and an FNV-1a payload identity.
The required structure, top to bottom, is:
```text
strate N ceiling band
content
bedrock band
strate N+1 ceiling band
content
bedrock band
```
Ceiling and bedrock remain distinct bands owned by their own strate. Their nominal edges touch; a
separate `EVoxelStrateTransition` declaration describes possible interpenetration.
| Prototype | Order / stable suffix | Generator; recipe version | Interior/content (m) | Current ceiling | Current bedrock | Transition at lower interface |
|---|---|---|---|---|---|---|
| SurfaceWorld | 0 / `TunnelNetwork` | TunnelNetwork; `legacy.TunnelNetwork` v0 | `[-120, 8)` | No Core density band. Manager configures a 1 m upper `VF_ApplyBoundarySeal` at `[8,9)`, but the compiled analytic Core path does not apply it. | No Core density band. Manager configures a 1 m lower `VF_ApplyBoundarySeal` at `[-121,-120)`, but the compiled analytic Core path does not apply it. | Gradient, 2 chunks = 8 m by the 4 m manager chunk. Core does not apply it; manager/dispatch chunk widths are 4/8 m. |
| SurfaceWorld | 1 / `SurfaceWorld` | SurfaceWorld; `SurfaceWorld.Heightfield2D` v1 | `[-632,-120)` | No separate volumetric band; cap profile is in content, with `AppendStratumBoundaryCeilings` mesh fallback. | None. | Gradient, 1 chunk = 4 m by manager units; no lower strate in this plan. Manager/dispatch widths are 4/8 m. |
| FloatingIslands | 0 / `FloatingIslands` | FloatingIslands; `FloatingIslands.ClusterField` geometry v3 | `[-60,4)` | Active one-sided upper seal `[3.5,4)` = 0.5 m. It overlaps the content range by 0.5 m. | None in geometry v3. | Gradient, 2 chunks = 8 m. Core does not apply it; manager/dispatch widths are both 4 m. |
| FloatingIslands | 1 / `SurfaceWorld` | SurfaceWorld; `SurfaceWorld.Heightfield2D` v1 | `[-632,-120)` | No separate volumetric band; cap profile is in content, with mesh fallback. | None. | Gradient, 1 chunk = 4 m; no lower strate in this plan. Manager/dispatch widths are 4/8 m. |
Dispatch provenance is recorded per stratum in the JSON snapshot:
- SurfaceWorld bounds come from `BuildSurfaceStratumPlan`, which converts each chunk with 32 Core
voxels = 8 m. `NativeSurfaceDensityAt` selects with `floor(CoreZ/32)`, then calls
`GetGeneratorTypeForChunk` / `GetStrateIndex`. The manager's owner chunk is 4 m.
- FI bounds come from `BuildFloatingIslandStratumPlan`, which converts each 32-voxel owner chunk
through the 12.5/25 cm owner/Core ratio = 16 Core voxels = 4 m. `NativeFloatingIslandDensityAt`
uses the same conversion before the manager lookup.
- Legacy TunnelNetwork selection uses the Core 32-voxel chunk lookup. The manager helper assigns
the upper strate's transition to its lower interface.
SurfaceWorld strata carry four cap profiles, 33 route points and their attached passages. The FI
strate carries four island anchors and its attached passages. The snapshot preserves these recipe
anchors in their source fixed-point units.
The owner configuration’s non-Hard transitions are currently not honored by Core at these interfaces.
`UVoxelStrateManager::GetGenerationParams` contains legacy vector blending, but Core’s compiled
dispatcher selects the resolved/native per-strate fields without applying that blend. FI v3 calls
`VF_ApplyUpperBoundarySeal` for its ceiling; player edit brushes are applied after the native field,
so that seal remains diggable. Bands default to `diggable=true`, `protected=false`; protection is an
explicit option.
```text
Current SurfaceWorld plan, top → bottom
TunnelNetwork ceiling [8,9) (configured; inactive in Core density)
TunnelNetwork content [-120,8)
TunnelNetwork bedrock [-121,-120) (configured; inactive in Core)
SurfaceWorld ceiling: none
SurfaceWorld content [-632,-120)
SurfaceWorld bedrock: none
Current FI prototype, top → bottom
FI ceiling [3.5,4) active, inset into FI content by 0.5 m
FI content [-60,4)
FI bedrock: missing
Unassigned interval [-120,-60) = 60 m
SurfaceWorld ceiling: none
SurfaceWorld content [-632,-120)
SurfaceWorld bedrock: none
```
The SurfaceWorld and FI content interiors therefore do not overlap: the FI prototype has a 60 m gap.
The owner manager slots meet at −60 m when both use 4 m owner chunks, but SurfaceWorld plan/query
dispatch converts chunks at 8 m while FI converts at 4 m. In the gap, Core answers through the
compiled analytic FloatingIslands fallback, not either planned recipe; probes at −68 m returned
solid density −4.125. SurfaceWorld and TunnelNetwork content interiors touch at −120 m in the
SurfaceWorld prototype, but Core does not apply the declared Gradient there.
## Compatibility validation and boundary probes
Compatibility mode accepted both layouts with seven reported issues apiece. SurfaceWorld reports
unhonored transition, 4/8 m dispatch-unit ambiguity on both strates, and missing current-field
ceiling and bedrock bands on both. FI reports its unhonored transition, missing FI bedrock, the
0.5 m ceiling/content overlap, the 60 m uncovered interval, and missing SurfaceWorld ceiling and
bedrock. These are recorded as compatibility deviations, not world changes.
Strict mode accepted a valid synthetic two-strate fixture. Its seven negative controls rejected:
| Control | Strict check that rejects it |
|---|---|
| Nominal band overlap | Bedrock(N) bottom must equal ceiling(N+1) top. |
| Nominal band gap | The same exact nominal contiguity check. |
| Missing bedrock | `bedrock.present` is required. |
| Missing ceiling | `ceiling.present` is required. |
| Wrong band order | Each band kind and the ceiling → content → bedrock edges must match. |
| Gap between bedrock(N) and ceiling(N+1) | Their nominal endpoints must be equal. |
| Interpenetration wider than transition | Explicit overlap must be at most the mode’s limit; this control uses 4,000,001 µm against a 4,000,000 µm Gradient zone. |
Hard has a 0 m limit. Gradient uses `TransitionBlendChunks × 4 m`; Interleaved adds its recorded
two-manager-chunk warp amplitude to the blend width. The nominal bands still touch exactly; the
separate interpenetration field records any permitted physical crossing. Current Core
interpenetration is 0 m because it does not apply the declared blends.
Each plan audited 21 points: one Core voxel (0.25 m) below, at, and above every distinct content,
active-band, configured-band, and transition-zone endpoint. The remaining points agreed with the
snapshot. Every disagreement was an uncovered snapshot point answered by a named compiled analytic
fallback:
| Prototype | Probe Z (m) | Actual Core dispatch | Density / occupancy |
|---|---|---|---|
| SurfaceWorld | −632.250 | `compiled-analytic/TunnelNetwork` | `+1`, air |
| SurfaceWorld | 8.000, 8.250, 8.750, 9.000, 9.250 | `compiled-analytic/TunnelNetwork` | `+9.648438`, `+10.64844`, `+12.64844`, `+13.64844`, `+14.64844`; all air |
| FI | −632.250 | `compiled-analytic/TunnelNetwork` | `+1`, air |
| FI | −120.000, −119.750 | `compiled-analytic/FloatingIslands` | `−108.4793`, `−107.4969`; solid |
| FI | −68.250, −68.000, −67.750, −60.250 | `compiled-analytic/FloatingIslands` | `−4.125`; solid at each point |
| FI | 4.000, 4.250 | `compiled-analytic/FloatingIslands` | `+0.671875`, `+2.253906`; air |
Probe summaries: SurfaceWorld 6/21 disagreements; FI 9/21. Full per-point records are in
`Saved/Linux/linux21/boundary-probes-{surface-world,floating-islands}.log`.
## Identity, output parity and cost
Snapshots round-tripped through canonical JSON. At W1/W8/W12 their bytes were identical per recipe:
| Recipe | Stable identity | Canonical JSON SHA-256 |
|---|---|---|
| SurfaceWorld | `resolved-layout-v1-fnv64:63cf4d2661149fcd` | `267586b3da9bc5c08c654c097559b205222f53c594c4c4332cb4929fd3d75c05` |
| FloatingIslands | `resolved-layout-v1-fnv64:1d90167f37ef9323` | `7bb9e9d745de3a2edf4276f507a5e4c082ff86aa73d9c1ad7e77c1b4d8b8bea8` |
The 28 hash runs (both recipes, Clang `fp-contract=off` and GCC default, W1/W8/W12 and W12×5)
matched all 12 L19 rows per run. The 14 dedicated 24 m crossing runs also matched all 12 L19
crossing rows. The table gives the final Clang W12×5 rows, in density/mesh/payload/topology order;
each equals its L19 reference.
| Scope | SurfaceWorld | FloatingIslands |
|---|---|---|
| Full detail | `0ee0579bd15cbbe4 / 32e5d4964d610fee / 1932b96ee4b1d910 / 69743c6bad3447dc` | `43034299d33f2536 / a41e8353ec4bbe9c / cd0e3e15eef827b0 / 0b3c3f13cc1d6f8a` |
| Whole view | `c10697a8f48380c7 / 4197fc1072f0ff58 / 7b3e616ec9459851 / c879e35592bf33e5` | `00bc3607a7839854 / 4baa66127c2b0892 / e11ef05e851a1517 / 1278bc4b1fffb869` |
| LOD 0 | `769f2a706d585a72 / 33eeeb39fa443816 / 645621a1d968983f / c2457ed905036c9d` | `ccf97d8e95163a35 / 3333fa0ddebdece2 / d1906bffc2b9d6f2 / a261f3d965e15c12` |
| LOD 1 | `69cc5a5c0a2c60af / b48335f5410c57d5 / d15c5480162ab8ba / feca1bd025823604` | `25fa2f0fa7fbb6e6 / c549c519707ac1d7 / 66f797cde4ebdecf / 2383982a12a27ee5` |
| LOD 2 | `85deed5683a77202 / 53469895987aeea2 / e55f8ae7e1532033 / d5feb12a0e85c09b` | `c0577decd8a02cab / 3ccf08b7be6edc5b / 0abcbbda5a06c110 / 11c77b71b606fe75` |
| LOD 3 | `6518caefb3be7e7f / ec513c90b853a614 / f62fa1137ff17f82 / ccf1dcf681965816` | `386e0c0ef2d7c920 / 1ee2c82d34ad9918 / 000a6514ba018406 / 1b4523b19e90f305` |
| LOD 4 | `62d4be96021992f4 / 42706847cb9c744a / eab76f9634e4b9bd / 207ffbb6ef184397` | `e8117a3a803be9f8 / 8a6c36cae1fe0e30 / c839f1b1629eff8c / 0ce9d7ea77b4f62b` |
| LOD 5 | `5e4a8c38b8cea6fc / 76adc9ae86988e27 / 938975bf7cc6d94d / 42e135856abdbcac` | `ca6d1df453c6b600 / 02c90d834d9a3fb6 / 4364c37d526f4104 / 1b1cf40d25e50ddf` |
| LOD 6 | `e036e95b9006945b / a880cc101a4ed957 / bd82f1aefad6dd89 / 5e9f3408345d95b3` | `6764e347b08603a0 / 46c6ed9a1f029b94 / 5b8dc9d5b7cb0d3d / a10a4a69a93eac4d` |
| LOD 7 | `8ee358af56a30d8d / 5cbd8e5965a2cc3f / d4a7fe5d9e4bde30 / 4dd52cd090a9af78` | `c7cd1b483c598595 / a545eb567dc4a0df / b0a1fb225437e05a / 301d1b884c509550` |
| LOD 8 | `c3458e1cbdb9c471 / fba3698d21dd1be0 / 77dfb814eac3a24c / c9514f1580c42449` | `7b6e9279b4993109 / cc63440d9867a18d / 12f5cec78b38fa1b / eed919db6db6b2a7` |
Exact-query rows (density/unclamped, detail, collision): SurfaceWorld `e4d4a8eb95b35933 / e4d4a8eb95b35933`,
`efcba20c0c70c415`, `e604823a249029bf`; FloatingIslands `87e5c0dbf9ee9ee2 / 87e5c0dbf9ee9ee2`,
`efcba20c0c70c415`, `e604823a249029bf`. The representative 24 m crossing W12×5 whole-view row is the
SurfaceWorld row above; all crossing full-detail, LOD and exact-query rows matched the L19 crossing
reference.
Five interleaved hashing-off W12 pairs against L19 measured SurfaceWorld medians 1.510 s old / 1.512 s
new (+0.14%) and FI 13.487 s old / 13.381 s new (−0.79%). The opt-in snapshot is constructed once
at plan time, outside tile workers; ordinary standalone evaluation does not request it. No hash rows
were produced during these pairs.
Clang and GCC builds passed at 12 jobs. Final build peak process-tree/single-process RSS was
2,697,676/450,248 KiB (Clang) and 4,570,704/897,024 KiB (GCC). The final evidence runner's maximum
single-process RSS was 583,736 KiB. All runs used CPUs 0–11 and stayed below the per-run limit; no
governor or system changes were made.
## Failed and retried runs
- An initial snapshot invocation omitted `--stream-benchmark --stream-pipeline` and exited 2; adding
the required flags passed.
- Early Clang builds caught a `strates` member typo, then an audit initializer/type error. Both were
fixed; the final Clang builds passed.
- The first complete evidence pass exited 1 at boundary probing because the audit read the host
manager after the harness had released it. Dispatch provenance was moved to Core's live selector;
the final audits passed.
- One GCC build wrapper was interrupted (exit 130) while diagnosing an empty progress log, then
rerun successfully. No runtime/hash run timed out or exceeded 15 minutes, and no L19 hash mismatch
remained.
## Owner note
This records the present strata and confirms the selected ceiling → content → bedrock model. FI still
has no floor in v3; SurfaceWorld has no volumetric floor or separate ceiling; the 8 m / 4 m dispatch
conversions leave a 60 m FI-prototype gap answered by analytic fallback. Core currently ignores the
configured transition blend at these interfaces. Bedrock and ceiling protection remain explicit
options with diggable defaults. This round changes no world output.
The Unreal adapter adds only a separate `resolved_layout` JSON field beside `world_assembly`; that
small edit could not be compiled on this Linux machine. No async path, density evaluator, hash input,
or recipe version changed. No commit, push, or stash was made.
## Orchestrator review (Claude)
- Merged: read-only snapshot + validator, no world change (28 hash runs and 14 crossing runs equal L19;
cost within noise). The Unreal adapter gains a 193-line `resolved_layout` export beside
`world_assembly`. It is export-only, but UNCOMPILED here; it must be built on the owner PC.
- Key facts for the owner: the authored params put the SW top at −60 m (4 m owner chunks), Core
builds SW at −120…−632 m (8 m conversion); the 60 m between FI and SW is unowned and answered by
the analytic FloatingIslands fallback as solid. No strate has an active bedrock band; SW has no
ceiling band (mesh fallback only); configured Gradient transitions are ignored by Core.
- To check next: whether the "ledges" ground seen under the islands is this fallback solid in the
unowned gap rather than SW terrain (L19 attributed them to SW at about −60…−73 m).
@@ -1,165 +0,0 @@
#!/usr/bin/env python3
"""Constrained L21 snapshot, output-parity, crossing, and off-cost evidence."""
import os
import re
import signal
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/linux21"
OUT.mkdir(parents=True, exist_ok=True)
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
BIN = {
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux21-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux21-gcc",
}
OLD_CLANG = ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-clang"
OLD_GCC = ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux19-gcc"
DATA = ROOT / "Standalone/data/owner_world.json"
HASH_ROW = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
LODS = [(1, 1), (8, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 5)]
RECIPES = {
"surface-world": "0,-35,-487.94,0,-22",
"floating-islands": "-22,-19,-22.8,0,-4",
}
results = []
def launch(name, argv, timeout=900):
logfile = OUT / f"{name}.log"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", *map(str, argv)]
started = time.monotonic()
peak_tree = 0
peak_process = 0
timed_out = False
with logfile.open("w", encoding="utf-8") as stream:
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
start_new_session=True)
while process.poll() is None:
pending, seen, total = [process.pid], set(), 0
while pending:
pid = pending.pop()
if pid in seen:
continue
seen.add(pid)
try:
pending.extend(int(x) for x in Path(f"/proc/{pid}/task/{pid}/children").read_text().split())
values = {}
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
if line.startswith(("VmRSS:", "VmHWM:")):
key, amount, *_ = line.split()
values[key] = int(amount)
total += values.get("VmRSS:", 0)
peak_process = max(peak_process, values.get("VmRSS:", 0), values.get("VmHWM:", 0))
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
pass
peak_tree = max(peak_tree, total)
if time.monotonic() - started > timeout:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
timed_out = True
break
time.sleep(0.1)
code = process.wait()
elapsed = time.monotonic() - started
rows = HASH_ROW.findall(logfile.read_text(encoding="utf-8", errors="replace"))
results.append((name, code, elapsed, peak_process, peak_tree, timed_out, len(rows)))
print(f"[L21Run] {name} exit={code} seconds={elapsed:.3f} peak_process_rss_kib={peak_process} "
f"peak_tree_rss_kib={peak_tree} hash_rows={len(rows)} timed_out={str(timed_out).lower()}", flush=True)
return logfile
def base_args(binary, recipe, workers, hash_output=False, crossing=False):
args = [binary, DATA, "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", f"--workers={workers}",
f"--camera={RECIPES[recipe]}", f"--recipe={recipe}"]
if hash_output:
args.append("--stream-hash")
if crossing:
args += ["--camera-step=24,0,0", "--region-crossing"]
return args
# Plan-time identities and canonical serialization at W1/W8/W12, both field recipes.
snapshots = {}
for recipe in RECIPES:
for workers, repeat in [(1, 1), (8, 1), (12, 1)]:
path = OUT / f"snapshot-{recipe}-w{workers}-r{repeat}.json"
args = [BIN["clang-off"], DATA, "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", f"--recipe={recipe}", f"--workers={workers}",
f"--resolved-layout-output={path}", "--resolved-layout-only"]
log = launch(f"snapshot-{recipe}-w{workers}-r{repeat}", args)
if results[-1][1] != 0:
raise SystemExit(f"snapshot plan failed: {log}")
snapshots[(recipe, workers)] = path.read_bytes()
if len({snapshots[(recipe, workers)] for workers in (1, 8, 12)}) != 1:
raise SystemExit(f"snapshot serialization changed with worker count: {recipe}")
print(f"[L21SnapshotIdentity] recipe={recipe} worker_counts=1,8,12 canonical_bytes=identical status=PASS", flush=True)
# Full matrix. Every row is compared against the L19 W12x5 reference.
for recipe in RECIPES:
for compiler, binary in BIN.items():
for workers, repeat in LODS:
name = f"matrix-{recipe}-{compiler}-w{workers}-r{repeat}"
log = launch(name, base_args(binary, recipe, workers, hash_output=True))
rows = HASH_ROW.findall(log.read_text(encoding="utf-8", errors="replace"))
reference_name = f"{recipe}-{compiler}-w12-r5.log"
reference = ROOT / "Saved/Linux/l19-matrix" / reference_name
expected = HASH_ROW.findall(reference.read_text(encoding="utf-8", errors="replace"))
if results[-1][1] != 0 or len(rows) != 12 or rows != expected:
print(f"[L21HashMismatch] {name} expected_rows={len(expected)} actual_rows={len(rows)}", flush=True)
raise SystemExit(1)
# The crossing matrix retains the 24 m two-view query proof on both compilers.
for compiler, binary in BIN.items():
for workers, repeat in LODS:
name = f"crossing-{compiler}-w{workers}-r{repeat}"
log = launch(name, base_args(binary, "surface-world", workers, hash_output=True, crossing=True))
rows = HASH_ROW.findall(log.read_text(encoding="utf-8", errors="replace"))
reference = ROOT / "Saved/Linux/l19-crossing" / f"{compiler}-w12-r5.log"
expected = HASH_ROW.findall(reference.read_text(encoding="utf-8", errors="replace"))
if results[-1][1] != 0 or len(rows) != 12 or rows != expected:
print(f"[L21CrossingHashMismatch] {name} expected_rows={len(expected)} actual_rows={len(rows)}", flush=True)
raise SystemExit(1)
# Five interleaved W12 pairs per recipe, hashing disabled. Alternate order by pair to reduce drift.
off_pairs = {recipe: {"old": [], "new": []} for recipe in RECIPES}
for recipe in RECIPES:
for pair in range(1, 6):
order = [("old", OLD_CLANG), ("new", BIN["clang-off"])] if pair % 2 else [("new", BIN["clang-off"]), ("old", OLD_CLANG)]
for label, binary in order:
name = f"offcost-{recipe}-pair{pair}-{label}"
log = launch(name, base_args(binary, recipe, 12, hash_output=False), timeout=900)
if results[-1][1] != 0:
raise SystemExit(f"hashing-off cost run failed: {log}")
off_pairs[recipe][label].append(results[-1][2])
print(f"[L21OffCostPair] recipe={recipe} pair={pair} old_seconds={off_pairs[recipe]['old'][-1]:.3f} "
f"new_seconds={off_pairs[recipe]['new'][-1]:.3f}", flush=True)
for recipe, pair in off_pairs.items():
old = statistics.median(pair["old"])
new = statistics.median(pair["new"])
print(f"[L21OffCostSummary] recipe={recipe} pairs=5 median_old_seconds={old:.3f} "
f"median_new_seconds={new:.3f} delta_percent={(new-old)/old*100.0:.2f}", flush=True)
# Query the real compiled Core field at every distinct recorded stratum/band endpoint.
for recipe in RECIPES:
name = f"boundary-probes-{recipe}"
args = base_args(BIN["clang-off"], recipe, 12, hash_output=False)
args += ["--audit-resolved-layout-boundaries"]
log = launch(name, args)
if results[-1][1] != 0:
raise SystemExit(f"Core boundary probes failed: {log}")
print(f"[L21EvidenceSummary] snapshots=6 matrix=28 crossing=14 off_cost_pairs=10 boundary_audits=2 status=PASS", flush=True)
for name, code, elapsed, rss, tree, timed_out, rows in results:
print(f"[L21RunDetail] {name} exit={code} seconds={elapsed:.3f} peak_process_rss_kib={rss} "
f"peak_tree_rss_kib={tree} hash_rows={rows} timed_out={str(timed_out).lower()}", flush=True)
@@ -1,173 +0,0 @@
# Linux22 — one strate-layout unit and the closed stack
The owner setting is now **8 m per layout chunk** for every generator. FloatingIslands spans **+8 to −120 m** (128 m) and SurfaceWorld spans **−120 to −632 m** (512 m). Their shared boundary is −120 m, so the old 60 m gap is gone. SurfaceWorld keeps the same physical interval and content field as L21.
## Stack and bands
| Strate | Full interval | Ceiling | Content | Bedrock |
|---|---:|---:|---:|---:|
| FloatingIslands | +8…−120 m | +7.5…+8 m (0.5 m) | +7.5…−119.5 m (127 m) | −119.5…−120 m (0.5 m) |
| SurfaceWorld | −120…−632 m | −120…−126.25 m (6.25 m) | −126.25…−625.75 m (499.5 m) | −625.75…−632 m (6.25 m) |
```text
z=+8.00 m FloatingIslands ceiling outer face
z=+7.50 m FloatingIslands content begins
Islands: bottoms −63.468…−60.912 m; tops −57.419…−52.357 m
z=−119.50 m FloatingIslands bedrock inner face
z=−120.00 m FI/SW boundary: FI bedrock meets SW ceiling; no gap
z=−126.25 m SurfaceWorld content begins; cap profiles rise above here
z=−625.75 m SurfaceWorld bedrock inner face
z=−632.00 m SurfaceWorld bedrock outer face
```
The alternate owner SurfaceWorld recipe also exercises TunnelNetwork above it: TunnelNetwork uses the same +8…−120 m interval, with a 1 m ceiling, 126 m content band, and 1 m bedrock band. Every tested strate type receives both solid bands in the shared composition path. Existing `BoundarySealThickness` supplies each band’s thickness: TunnelNetwork 8 owner voxels = 1 m, FloatingIslands 4 = 0.5 m, SurfaceWorld 50 = 6.25 m. `BaseDensity` supplies solid density. Bands are diggable and unprotected by default. `InterStrateGapChunks=0` controls spacing, not band thickness; `EdgeSealThickness` remains horizontal. No new thickness parameter was needed.
Visible ceilings use the shared L18/L19 boundary path, extended to floors. The authored transitions are Gradient (blend widths 2 chunks for the upper strate and 1 for SurfaceWorld), but Core still uses the explicit **Hard fallback with zero interpenetration**. The snapshot records this deviation; Gradient and Interleaved blending are not claimed as implemented.
## Unit resolution and validation
`StrateLayoutChunkSizeMeters` is one world setting with an 8.0 m default. The director/adapter and standalone loader resolve it to micrometres and Core fixed-point width. At 25 cm Core voxels the width is 8,192 subvoxels = 32 Core voxels; at the S1 20 cm check it is 10,240 subvoxels = 40 Core voxels. The internal 32³ Core tile remains a separate concept.
Changed conversion and consumer sites:
- `UVoxelStrateManager` resolves the world setting; `VoxelForgeCoreAdapter::AttachValidatedWorldAssembly` passes one fixed-point width into recipes and `ResolvedLayoutSnapshotV2`. `OwnerWorldLoader::Resolve` mirrors that resolution in the standalone path.
- `BuildSurfaceStratumPlan` now scales chunk bounds by the resolved width instead of `CHUNK_SIZE` (32 Core voxels). `BuildFloatingIslandStratumPlan` writes the same width into geometry version 4.
- `NativeSurfaceDensityAt` and native SurfaceWorld strata classification use snapshot width for `floor(CoreZ / width)` and bounds. FloatingIslands dispatch uses its validated snapshot recipe width. Legacy TunnelNetwork chunk lookup uses the same snapshot width.
- Shared ceiling and floor composition, far-shell clipping, cap bounds, endpoint probes, and strict layout validation use the resolved width. `FloatingIslandManifestV1::FromJson` no longer reconstructs legacy width from Core tile size or owner/Core voxel ratio; world assembly supplies and validates the resolved value.
Current owner snapshots:
- SurfaceWorld recipe: `resolved-layout-v2-fnv64:96d6a8b64f73c2d8`.
- FloatingIslands recipe: `resolved-layout-v2-fnv64:2b343bc155f181d5`.
- 20 cm S1 snapshot: `resolved-layout-v2-fnv64:0a56caef7dd44cac`.
Strict validation passed on the generated owner world for both recipes: zero issues, 7/7 negative controls rejected, and 27 boundary probes per recipe with **zero dispatch/snapshot disagreements**. Both stacks have zero unowned gap and zero undeclared band overlap. The 20 cm snapshot also passed strict validation.
## SurfaceWorld clearance and spawn
The clearance scan samples a 4,096 × 4,096 m area at 1 m spacing around the derived route spawn (16,785,409 samples). Clearance is ceiling inner face minus the higher of ground and cap. Negative values mean a cap pokes through the ceiling at −126.25 m.
| Biome slot / ID | Max ground | Max cap/surface | Minimum sampled clearance |
|---|---:|---:|---:|
| 0 / 4097 | −442.333 m | −44.496 m | **−81.754 m** |
| 1 / 4098 | −313.660 m | −30.881 m | **−95.369 m** |
| 2 / 4099 | −419.399 m | −0.425 m | **−125.825 m** |
| 3 / 4100 | −361.088 m | −109.922 m | **−16.328 m** |
Ground/mountain surfaces stay below −313.660 m in this scan. The collisions come from cap profiles: authored `CeilingUndulation` is 1000 or 900 Core voxels (250 m or 225 m). They were not clamped. The owner can raise the strate or reduce those cap profiles. Since SurfaceWorld’s interval and content are unchanged from L21, this clearance issue is unchanged too.
The SurfaceWorld plan produces 33 route points. The L1 benchmark and spawn camera now derive from `route.front`: **(9.250, 41.250, −502.848) m**, yaw 0°, pitch −22°. Route planning passed (648 candidates, 366 grade rejects, zero sweep rejects); walkability passed 5,642/5,642. The highest route point is −501.714 m, giving 375.464 m clearance to the ceiling face. FloatingIslands walkability passed 4,000/4,000.
| Island | Bottom…top | Bedrock margin | Ceiling margin |
|---:|---:|---:|---:|
| 0 | −62.700…−57.419 m | 56.800 m | 64.919 m |
| 1 | −61.410…−56.000 m | 58.090 m | 63.500 m |
| 2 | −63.468…−55.878 m | 56.032 m | 63.378 m |
| 3 | −60.912…−52.357 m | 58.588 m | 59.857 m |
## Captures
All six L22 captures are 1600×900 JPEGs under 50 KB (limit 300 KB). SurfaceWorld images compare with L19’s same-named views. The FI review angle is the owner-style low view at (−22, −19, −61) m; separate views look up at the ceiling and down at the new bedrock floor.
| View | L22 | L19 equivalent |
|---|---|---|
| SurfaceWorld, route-derived spawn | [L22](captures/surface-world-l1-spawn-after.jpg) | [L19](../Linux19/captures/surface-world-l1-spawn-after.jpg) |
| SurfaceWorld horizon | [L22](captures/surface-world-horizon-after.jpg) | [L19](../Linux19/captures/surface-world-horizon-after.jpg) |
| SurfaceWorld rings from above | [L22](captures/surface-world-rings-above-after.jpg) | [L19](../Linux19/captures/surface-world-rings-above-after.jpg) |
| FloatingIslands low owner angle | [L22](captures/floating-islands-owner-angle-after.jpg) | [L19](../Linux19/captures/floating-islands-owner-angle-after.jpg) |
| FloatingIslands ceiling | [L22](captures/floating-islands-ceiling-up-after.jpg) | [L19](../Linux19/captures/floating-islands-ceiling-up-after.jpg) |
| FloatingIslands bedrock floor | [L22](captures/floating-islands-bedrock-down-after.jpg) | — |
A whole-stack side render was not readable from the surface-only mesh export. The vertical stack diagram above gives the exact interfaces and bands.
## L19 ring tables rerun
RingProbe ran on L19 and L22 meshes at the same historical cameras: SurfaceWorld (0, −35, −487.94) m and FloatingIslands (−22, −19, −22.8) m. Values are median / p95 / maximum absolute edge-height difference in centimetres, followed by no-hit edge count. These are LOD mesh-edge gaps, not gaps in the validated strate stack.
### SurfaceWorld rings
| LOD | L19 edges/samples | L19 cm (gaps) | L22 edges/samples | L22 cm (gaps) |
|---|---:|---:|---:|---:|
| 0→1 | 46/288 | 0.178 / 0.594 / 1.171 (10) | 46/288 | 0.178 / 0.594 / 1.171 (10) |
| 1→2 | 32/288 | 1.419 / 4.990 / 10.438 (0) | 32/288 | 1.419 / 4.990 / 10.438 (0) |
| 2→3 | 34/256 | 2.179 / 12.648 / 35.355 (2) | 66/544 | 0 / 8.260 / 35.355 (2) |
| 3→4 | 40/265 | 5.099 / 35.999 / 1,190.974 (5) | 72/537 | 0 / 22.456 / 1,190.974 (6) |
| 4→5 | 58/270 | 0.235 / 3.694 / 491.570 (14) | 104/835 | 0 / 77.070 / 5,751.081 (6) |
| 5→6 | 65/266 | 12.234 / 127.224 / 536.573 (33) | 96/576 | 0 / 0 / 6,935.708 (32) |
| 6→7 | 41/269 | 28.238 / 291.861 / 945.477 (7) | 64/288 | 0 / 2,991.225 / 9,240.525 (32) |
| 7→8 | 32/275 | 83.843 / 1,225.416 / 3,755.407 (0) | 32/288 | 0 / 10,444.014 / 10,839.630 (0) |
The 0→1 and 1→2 rows are unchanged. Higher rings changed as the complete strata bands and far boundaries entered the view; the largest far-ring discrepancies increased. The full LOD-stable interface surface remains future work.
### FloatingIslands / SurfaceWorld interface rings
| LOD | L19 edges/samples | L19 cm (gaps) | L22 edges/samples | L22 cm (gaps) |
|---|---:|---:|---:|---:|
| 0→1 | 0/0 | — (0) | 63/185 | 1.328 / 6.795 / 10.623 (17) |
| 1→2 | 33/231 | 147.141 / 148.403 / 149.862 (0) | 0/0 | — (0) |
| 2→3 | 49/308 | 128.603 / 200.789 / 773.318 (8) | 32/288 | 0 / 0 / 0 (0) |
| 3→4 | 64/277 | 3.269 / 47.774 / 188.531 (27) | 32/288 | 0 / 0 / 0 (0) |
| 4→5 | 72/544 | 29.992 / 124.373 / 443.482 (6) | 72/547 | 0 / 74.140 / 552.186 (6) |
| 5→6 | 64/266 | 12.234 / 127.224 / 536.573 (32) | 64/288 | 0 / 0 / 0 (32) |
| 6→7 | 32/264 | 29.013 / 301.752 / 42,624.479 (0) | 32/288 | 0 / 0 / 0 (0) |
| 7→8 | 64/275 | 83.843 / 1,225.416 / 3,755.407 (32) | 64/288 | 0 / 0 / 0 (32) |
The FI table changed substantially. The new bedrock floor is visible at −119.5 m and supplies flat matched samples in several coarser rings; the old L1→2 interface edge is absent in this camera’s L22 mesh. This does not implement the later full LOD-stable interface design.
## Hashes, determinism, invariants, and cost
Versions: evaluator 7, recipe schema 2, layout snapshot schema 2, FloatingIslands geometry 4. New W12 baseline rows are density / mesh / payload / topology:
| Recipe/view | Full detail | Whole view |
|---|---|---|
| SurfaceWorld, derived spawn | `e7e0b0668a491c46 / ca35fe7a91099574 / 19f29f621cfdbbc7 / e37b3191a7200c67` | `4932f16f7d8dd5ab / 98e68419a13b76db / 56124d542206790c / 75ef1148afa20ca8` |
| FloatingIslands, auto island camera | `647bcb4038627e16 / 87324ad688a49bfa / a0c5b9a73288e393 / f9c316a79dd42152` | `2b5b06283399ad9d / 5c66f389b526f6bb / f2e7ff68d9dc2262 / 121abcf0607f0cf1` |
For the same historical SurfaceWorld camera, L21 and L22 exact-query density/unclamped hashes match at `e4d4a8eb95b35933`; detail is `efcba20c0c70c415`, collision is `e604823a249029bf`. Full-detail hashes also match exactly: `0ee0579bd15cbbe4 / 32e5d4964d610fee / 1932b96ee4b1d910 / 69743c6bad3447dc`. Whole-view hashes differ because shared bands and complete outer strata now contribute to the mesh.
Determinism passed 28/28 runs (both recipes, Clang 19 with FP contraction off and GCC 14 default; W1, W8, W12×5). The 24 m SurfaceWorld crossing passed 14/14. Resolved snapshots were byte-identical across W1/W8/W12. Final W12 smoke hashes on both post-guard binaries still match their baselines. SurfaceWorld route parity passed with 782,026 exact-query samples and zero mismatches; FloatingIslands also passed with 782,026 samples and zero mismatches. SurfaceWorld’s proof audit checked 25,927 blocks / 34,508,837 samples with zero violations. FI had zero checked blocks and 24,576 conservative unknown/fallback blocks, zero violations; it did not claim positive proof coverage. Shape readiness, base/page equivalence, and walkability audits passed.
The W12 hashing-off five-pair median cost against the L21 binary was 1.613 s → 1.411 s for SurfaceWorld (−12.52%) and 11.087 s → 18.840 s for FloatingIslands (+69.93%), reflecting FI’s added 64 m of volume. Largest measured process RSS was 897,840 KiB; largest process-tree RSS was 4,603,848 KiB. Builds and runs stayed under the 6 GiB address-space limit, CPUs 0–11, at most 12 workers, and the 15/30 minute limits. The governor was unchanged.
The L3 FP-contract preamble/self-test and L4 `FLazy`, L7 shared cache, L9/L12 proofs, L11 readiness/crash handling, L13 route binding, L18/L19 shared ceiling, L21 snapshot/validator, `ARCHITECTURE.md` §8.10, and CLAUDE.md async rules remain in place. The Linux22 architecture note is §8.40.
## Unreal edits and Windows checklist
Unreal edits are limited to:
- `Source/VoxelForge/Public/VoxelSettings.h` — world setting `StrateLayoutChunkSizeMeters`, default 8.0.
- `Source/VoxelForge/Public/VoxelStrateManager.h` and `Private/VoxelStrateManager.cpp` — resolve and use one layout width for manager strate bounds/lookups.
- `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` — pass that width to plans and the V2 snapshot; compose the same parameter-driven ceiling and bedrock bands for each generator.
The Unreal module cannot be compiled on this Linux machine. Windows checklist:
1. Rebuild the VoxelForge module and confirm the setting defaults to 8.0 m.
2. Load the owner world with FloatingIslands and SurfaceWorld; verify +8…−120 m and −120…−632 m, with no gap at −120 m.
3. Verify ceiling and bedrock are visible and breakable, then check the route-derived SurfaceWorld spawn and cap/ceiling collisions against the captures.
4. Run the boundary validator and compare the generated snapshot with the two Linux22 identities above.
## Failed and retried runs
- Three early Clang builds failed while implementation files were being synchronized: the standalone loader referenced settings/API not yet copied; the next compile found three undeclared copied-manager width references; a later attempt found a missing closing brace in the clearance audit. These were corrected; the final Clang and GCC builds passed.
- One final smoke attempt used a nonexistent `linux22-clang-off` binary suffix and exited 127 before running a workload. It was rerun with the correct `linux22-clang` and `linux22-gcc` paths; all four W12 hash checks passed.
- A zero-byte `build-clang-off-floor.log` has no recorded compiler output and was superseded by the successful measured final builds.
- The clearance scan completed successfully and reported the cap collisions above; this is a world-fit finding, not a failed run. No build or run timed out or exceeded its limit.
Nothing was committed, pushed, or stashed.
## Orchestrator review (Claude)
- Merged. The strict validator passes on the real owner world; SW full-detail and exact-query hashes are
unchanged (content did not move); FI is +8…−120 m with a bedrock floor.
- Captures checked by me: the FI owner-angle view shows islands above a clean, flat bedrock floor with a
closed ceiling, and the ledges are gone. The SW rings-from-above view shows continuous terrain with no
visible ring steps, so the large far-ring values in the RingProbe table (up to ~108 m) are most likely the
probe pairing different surfaces now that bands are in view, not real breaks. Treat that table with care
until the probe filters by surface owner. The SW horizon is from the new route-derived spawn, so it is not
the same angle as L19.
- Owner decision needed: SW cap profiles (`CeilingUndulation` 225–250 m) exceed the new ceiling band by up
to ~126 m; reported, not clamped.
- Cost: FI whole view +70% (its strate doubled in volume); SW −12.5%.
- Unreal edits (VoxelSettings `StrateLayoutChunkSizeMeters` = 8.0, StrateManager, CoreAdapter) are
UNCOMPILED here; Windows checklist above. CLAUDE.md's scale rule was updated to match.
- Transitions are still Hard (recorded deviation).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

@@ -1,158 +0,0 @@
# L23 — inter-strate passage
## Owner answer
**After L22, Core did not have a resolved-layout passage through the new bands.** L22 exported
`FVoxelPassage` points as `milli owner voxels` metadata, but the native FloatingIslands and
SurfaceWorld Core paths did not use that metadata to carve the resolved bands. The legacy manager
still has `UVoxelStrateManager::GeneratePassages`; its coordinates are in owner voxels, not metres.
The owner voxel is 12.5 cm (`OwnerVoxelSizeMicrons=125000`); `voxel_size_cm=25` is the Core voxel.
The legacy 0→1 passage therefore had upper `(-19.594, 3.137, -14.255) m` and lower
`(-19.594, 3.137, -102.959) m`, with radius 1 m. Its lower point was still in FloatingIslands
content (`-119.5…+7.5 m`), 23.29 m above SurfaceWorld content (`-625.75…-126.25 m`). It did not
open the FI bedrock (`-120…-119.5 m`) and SW ceiling (`-126.25…-120 m`). The JSON also contains a
legacy 0→0 passage; L23 plans only adjacent resolved strata.
L23 now derives the passage from the director-resolved bands and route anchors. For the FI recipe,
mouths are `(9.800, 4.571, -54.831) m` and `(9.250, 9.250, -502.608) m`. For the SurfaceWorld
recipe the unanchored upper mouth is `(10.732, -5.775, -107.766) m`; the lower mouth is the same.
Both locations are inside the actual content and within 24 m of `(0,0)`.
## Planner, radius, and routes
`UVoxelStrateManager::GeneratePassages` remains the legacy Unreal planner. L22's season manifest
carried recipes/routes, while the adapter copied manager passage points into V2 anchors with the
legacy units. L23's adapter and headless director convert each upper strate's `PassageConfig` to
Core fixed units, build a V3 layout with route anchors, then call `PlanResolvedLayoutPassagesV1`.
The planner hashes seed, upper/lower strate, connection, and salt, so peers with the same resolved
input choose the same path. Strate moves and route changes trigger a new plan; exported root
`passages` are not imported.
The placement disk comes from `PassageConfig.DistanceMax=192` owner voxels = 24 m; when no route
anchor is available, `DistanceMin=64` owner voxels = 8 m supplies the inner annulus. `OriginSpineRadius`
and spawn radius are separate settings. `MouthRadius`/`MidRadius` set tunnel cross-section; Core
enforces a 1.25 m minimum radius for the player capsule. Neither ceiling nor cap parameters changed.
In the FI snapshot, route point 28 (last of 29) equals the FI departure mouth; SW route point 0
(first of 33) equals its arrival mouth. Both route audits pass. This owner layout has exactly two
strata, so SW has no next interface/departure mouth; its free route endpoint is
`(54.750, 54.750, -502.308) m`.
## Passage audit
Capsule is 0.34 m × 1.76 m. A passage fails on any blocked capsule sample, unsupported floor probe,
grade failure, mouth outside content/radius, route-mouth mismatch, or if the no-passage control
does not detect blockage.
| Recipe | Interfaces | Center samples | Capsule samples | Blocked | Support probes / unsupported | Grade failures | Mouths in content / radius |
|---|---:|---:|---:|---:|---:|---:|---:|
| FloatingIslands → SurfaceWorld | 1/1 | 5,174 | 325,962 | 0 | 40,412 / 0 | 0 | 2/2 · 2/2 |
| TunnelNetwork → SurfaceWorld | 1/1 | 4,513 | 284,319 | 0 | 35,254 / 0 | 0 | 2/2 · 2/2 |
The deliberate no-passage controls found 17,218 blocked capsule samples (FI recipe) and 23,876
(SurfaceWorld recipe); both were detected. FI’s 29-point route and SW’s 33-point route pass with
0 blocked samples, 0 unsupported probes, 0 grade failures, and mouth binding 2/2. The
SurfaceWorld-only route passes 33 points with the same zero failures and binding 1/1.
The shared composition layer applies each generator field, then bedrock/ceiling bands, then the
bounded passage carve and support shelf. Only the tunnel path opens a band. The full 0→1 capsule
audit crosses the FI bedrock and SW ceiling; support stays below the capsule.
The audit samples the resolved Core field by interface owner and does not reuse the old ring-table
probe, so it does not pair band and content samples from different surface owners.
Runtime passage checks covered all eight generator kinds: TunnelNetwork, FlatPlain, CrystalChamber,
Maze, VerticalShafts, and Underwater as upper-strate fixtures; FloatingIslands in the FI recipe;
SurfaceWorld as the real lower strate in both owner-world recipes. The SurfaceWorld-as-upper fixture
could not produce its separate route: all 648 route candidates failed the grade filter. That fixture
is recorded as a failed route setup, not counted as a passage pass. The real SurfaceWorld lower
strate audits pass.
## Determinism, proofs, and content hashes
Clang 19 (`--fp-contract=off`) and GCC 14 (`--fp-contract=default`) builds passed. Both executables
ran the mandatory Core FP-contraction self-test without rejection. For both recipes, W1/W8/W12×5
across both compilers produced one distinct result per hash class. The same 28-run matrix passed the
24 m region crossing: 314 moving tiles, stable within each recipe. Route parity passed on both
compilers: 8/8 page pairs, 14 higher-LOD exact pairs, 782,026 exact-query samples, and one edit,
all with zero mismatches. V3 strict validation accepted both layouts with 0 issues; all 9 malformed
layout controls were rejected.
Passage-centered uniform-proof boxes are conservatively unknown/fallback (FI: 21,504 fallback and
unknown, 0 violations); no false certificate is emitted around the new carve. Away from the passage,
SurfaceWorld at `(512,512,-500) m` matches the L22 baseline bit-for-bit: 27,368 certified blocks,
36,426,808 samples, 1,304 fallbacks, 0 unknowns/violations. Full-detail hash is
`66ac781666579e3e / 914873894a0e38c4 / ec8b03ade039cd56 / a2b621b0321d43dc` (density/mesh/payload/
topology); whole-view hash is `ddc6d1942db48f52 / bce985905e70dac4 / 266f5a008bcf4491 /
e6fa520c5ad2817b`. LOD0–3 and 9 exact-query hashes also match. The earlier unbounded comparison
included the far horizon and was discarded; this comparison used render-distance 0 and max LOD 3.
Changes are limited to resolved passage planning, band openings along those paths, and supported
landings. SurfaceWorld content away from the passage is unchanged by the hashes above. L3 FP
contract setup/self-test, L4 `FLazy`, L7 shared cache, L9/L12 proof behavior, L11 readiness/crash
path, L13 route binding, L18/L19 shared ceiling, L21/L22 layout bands, ARCHITECTURE §8.10, and
`CLAUDE.md` async/Epoch rules remain intact. The route and passage audits exercise L13 binding; the
async/cache files were not edited.
## Captures
Small 1280×720 JPEGs (all render inputs/PNG intermediates stay in `Saved/Linux/linux23/`):
- [FI passage mouth](captures/fi-mouth.jpg)
- [Tunnel at the FI bedrock / SW ceiling interface](captures/tunnel-interface.jpg)
- [Arrival in SurfaceWorld](captures/sw-arrival.jpg)
## Changes and Windows checklist
Version bumps: resolved-layout snapshot V3, evaluator 9, FloatingIslands geometry 4. The only
Unreal-side source edit is `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`: it binds V3
routes/configs and invokes the resolved planner. Core and standalone sources build here; the Unreal
adapter cannot be compiled on this Linux host.
On Windows:
1. Build the Editor/plugin with the updated Core header and adapter; check UHT and link.
2. Load the owner world at seed 0 and verify strict V3 validation and stable mouth coordinates.
3. Walk the FI route, passage, and SW route with the 0.34 m × 1.76 m capsule; inspect all three
capture viewpoints in-engine.
4. Move a strate or route anchor, reload, and verify the passage is re-derived and peer-identical.
5. Confirm SW away from the passage and its ceiling/cap parameters remain unchanged.
## Failed and retried runs
Failed development audit logs (all retained under `Saved/Linux/linux23/`): FI
`audit-floating-islands.log`, `r2–r5`, `r17–r19`, `r21–r36`, `r38–r41`; SurfaceWorld
`audit-surface-world.log`, `audit-surface-world-diagnostic.log`, `audit-surface-world-diagnostic-r2/r3`,
and `audit-surface-world-r2–r7`, `r19`, `r21–r22`, `r24–r26`, `r28–r31`, `r33`, `r35`. The early
failures were blocked capsule samples or unsupported landing probes. r35 still had 10 unsupported
passage support probes; r36’s correction left 1,977 blocked capsule samples. r37 build failed on
undeclared `begin/end`; r38 regressed to 10 unsupported probes. r39–r41 diagnostics traced support
search across a non-monotone lower route tail; r42 build failed on `continue` inside a lambda. The
lower-mouth split/index fix passed r43; final Clang/GCC builds and audits pass.
Other retries: `plan-surface-world-r2.log` used the wrong data path; two layout-only calls omitted
the required benchmark/pipeline flags and exited 2. The first upper-SurfaceWorld fixture failed its
648-candidate route search as noted above. Initial offscreen renders hit the read-only Mesa cache
location and were rerun with cache disabled and the CPU/address-space limits. Initial captures were
also rerendered under affinity/memory limits. Camera trials were replaced by the three listed JPEGs.
The first SurfaceWorld away hash comparison included the far horizon and was replaced with the
bounded L22/L23 comparison. The hash parser initially reported missing rows because its regex was
not multiline; corrected parsing found 28/28 runs and zero mismatches.
Runs were pinned to CPUs 0–11 and capped at 6 GiB address space; no task exceeded 30 minutes or
stalled for 15. This shell inherited nice level 5; an attempt to restore normal priority was denied,
so I made no priority or governor change. No commit, push, or stash was made.
## Orchestrator review (Claude)
- Merged as a correctness fix: the strates were not connected after L22 (and Core never carved the
passage at all); now a planned, deterministic passage crosses FI bedrock and the SW ceiling, with a
capsule audit of 0 blocked / 0 unsupported and a detected negative control.
- **The look is not yet "natural" (my capture review):** `fi-mouth.jpg` shows a stepped tube hanging in
FI's open void (there is no rock to carve between the island-level mouth at −54.8 m and the bedrock at
−119.5 m); `sw-arrival.jpg` shows flat stacked terraces; `tunnel-interface.jpg` is too close to read.
Owner input needed on where an FI passage should start (island vs bedrock floor) and how it should
look.
- Open: the SurfaceWorld-as-upper-strate fixture can't plan its route (648/648 grade-rejected), a real
limit for future layouts.
- Unreal adapter edit uncompiled here; Windows checklist above.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

@@ -1,113 +0,0 @@
# VoxelForge L24 — passages and anchor island
## Owner summary
Inter-strate passages are now planned in Core from the upper strate's `FStratePassageConfig`, for every generator. The public default is **Worm**: a gently graded, deterministic meander with variable radius and rough walls. Straight, Spiral and Cascading remain selectable per strate. FloatingIslands sets its upper passage to **Spiral** and gets a unique anchor island with a helical ramp down inside a rock root to the bedrock floor; the passage then continues through the SurfaceWorld ceiling. The other islands remain floating.
The seed-derived anchor is stable island ordinal 2 at about `(9.800, 4.571) m`, within the 24 m placement disk; its mouth phase also hashes the seed and stable island ID. The root radius is 6 m, the helix radius is 3 m and the bore radius is at least 1.5 m, leaving 1.5 m nominal rock between the bore and root edge before bounded relief. The authored two-turn minimum extends to 19 turns to keep the 64 m descent under the 10.8-degree target. The root reaches the bedrock floor at about `-119.5 m`. The FI route departure is bound to the island mouth. Island placement hashes and ordinals for the other islands are unchanged.
The SurfaceWorld arrival uses its native hillside/terrain plus the passage tread; the shared carver no longer stamps a flat lower route-anchor shelf over the cave opening. The final mouth capture shows the opening from above and a few metres away.
## Captures
All are JPEGs under 300 KB. The FI exterior capture pairs a full view with a world-space half-plane cutaway; the second panel exposes the helical bore in the root above the bedrock slab. The interior image is from the audited centerline, aimed down the ramp with a 150-degree camera and camera headlamp. The SurfaceWorld arrival is an overhead view, about 7 m laterally from the mouth. Straight and Worm are left/right from matching final-source views.
- [FI anchor island, root and helix cutaway](captures/fi-anchor-cutaway.jpg) — 35,221 bytes
- [Inside the helix, looking along the descent](captures/fi-helix-inside.jpg) — 8,754 bytes
- [SurfaceWorld arrival mouth](captures/sw-arrival-mouth.jpg) — 14,720 bytes
- [Straight (left) beside default Worm (right)](captures/straight-vs-worm.jpg) — 60,020 bytes
## Passage and naturalness audits
The capsule is 0.34 m wide by 1.76 m high. The grade contract is 15 degrees; the planner targets at most 10.8 degrees to leave margin. Wall and ceiling relief is up to 0.24 m, gravity-facing relief is attenuated by 20%, and the floor relief stays gentler with support under the capsule. The naturalness check rejects horizontal runs longer than 8 m (segments flatter than 0.5 degrees) and requires at least 0.20 m peak-to-peak wall relief from at least 8 valid wall samples. The L23 smooth, unrelieved wall control measures 0 m relief and is detected as a failure.
Final-source Clang r55 FI audit: 801 control points, 5,317 centers, 334,971 capsule samples; **0 blocked, 0 unsupported, 0 grade failures**; support samples 29,278; both mouths are in content and within radius; two route bindings pass; blocked-passage negative control detected. Naturalness: flat run 0 m, wall relief 0.793 m from 72 samples; pass. Strict V3 validator accepted the layout and rejected 9/9 malformed controls.
Final-source Clang r55 SurfaceWorld style sweep:
| Style | Capsule samples | Blocked / unsupported / grade failures | Wall relief | Naturalness |
|---|---:|---:|---:|---|
| Straight | 284,256 | 0 / 0 / 0 | 0.441 m (10 samples) | Pass |
| Worm | 284,949 | 0 / 0 / 0 | 0.298 m (12 samples) | Pass |
| Spiral | 337,995 | 0 / 0 / 0 | 0.753 m (14 samples) | Pass |
| Cascading | 287,910 | 0 / 0 / 0 | 0.435 m (14 samples) | Pass |
All styles had 0 m flat runs against the 8 m limit; route bindings, blocked controls and strict validator controls also passed in each run. The earlier L23 geometry has 0 m wall relief and fails the new naturalness threshold.
Audit logs are in `Saved/Linux`: `linux24-audit-fi-clang19-r55-lower-mouth.log` and `linux24-style-{straight,worm,spiral,cascading}-surface-final-r55.log`.
## Determinism and compatibility evidence
The final-source r55 worker matrix passed Clang and GCC at W1, W8, W12, and W12 repeated five times. It covers all 120 full-detail tiles and the 176-tile whole view through LOD2; every worker/repeat/compiler produced identical hashes:
| Final-source view | Density | Mesh | Payload | Topology |
|---|---|---|---|---|
| Full detail, 120 tiles | `657388bd79ce59a1` | `7ab35e5ea2bd80a5` | `fe853e905a362755` | `963af083379cfd25` |
| Whole view, 176 tiles through LOD2 | `90d1f05a8c8c2406` | `c330e8944d533dd6` | `47102acb793fb854` | `354faeb5134a0aea` |
| LOD0, 64 tiles | `25d79b76f9d0cd01` | `33a2c2fdee222be5` | `32b8a3331d984725` | `7033aba8bb626ea5` |
| LOD1, 56 tiles | `24df5021a729fdd5` | `65cf44f4e7cf20e5` | `0fda084ee0cc0355` | `35ffa189a5b54da5` |
| LOD2, 56 tiles | `d5cf2f60a27cfee2` | `dbb8283a93e79956` | `05f9170ad820dac4` | `6d79c252b66d30ea` |
| Exact probes, 9 | `7b7c50ac24ccadcc` | detail `efcba20c0c70c415` | collision `e604823a249029bf` | — |
Per-run logs are in `Saved/Linux` as `linux24-r55-worker-hash-fi-{clang,gcc}-l2-w1.log`, `...-w8.log`, `...-w12.log`, and `...-w12-r1.log` through `...-r5.log`. Crossing, away-field, proof, boundary and L13 logs use the same `linux24-r55-` prefix.
The unrestricted r55 hash runs including LOD3 stopped after shape compilation without producing pipeline hashes; they were not counted as passes. The r54 full matrix passed Clang and GCC at W1, W8, W12, W12×5 and LOD0–8. Representative r54 hashes (before lower-mouth shelf removal) were:
| View | Density | Mesh | Payload | Topology |
|---|---|---|---|---|
| Full detail, 120 tiles | `e248e7f04a0eb5dc` | `09c8289f260df275` | `7501d467bebdc9b2` | `43c78d5cab335dbf` |
| Whole view, 195 tiles | `d4833386ea55cc28` | `26863a50c8138aa6` | `46601fd8395b44ea` | `9680964dfaa5f2d1` |
| Exact probes, 9 | `dea346fbf2e2d259` | detail `efcba20c0c70c415` | collision `97df60072c993bff` | — |
The r55 24 m crossing passed for FI and SurfaceWorld on both compilers: 314 entering tiles each in the full crossing run. The final-source hashed LOD0–1 crossing covered 288 entering tiles and matched across compilers. FI hashes (density/mesh/payload/topology): `9fadf809b3b081b2 / 307b09c297623cf2 / 2fe406d2bd1229d2 / 7728405d1bf07a61`. SurfaceWorld: `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae`. Exact probe hashes matched the worker matrix. The r54 full-range hashed crossing also passed for FI and SurfaceWorld on both compilers (96 voxels, 314 tiles): FI `f9e89a48116c45ff / a6c8f20efcfb460f / f4df697779b2bf00 / e378f82fedc1a822`; SurfaceWorld `3561ea23485e359f / e8331620fb0fb908 / 765f7dad67c852aa / 3ccc09ac81c05d71`.
SurfaceWorld away from the passage remained bit-identical to L23 on both compilers in the final-source LOD0–1 bounded comparison: 960 full-detail tiles, hashes `66ac781666579e3e / 914873894a0e38c4 / ec8b03ade039cd56 / a2b621b0321d43dc`, matching the r54/L23 baseline exactly. The r54 full whole-view hashes were `ddc6d1942db48f52 / bce985905e70dac4 / 266f5a008bcf4491 / e6fa520c5ad2817b`. Final-source r55 SurfaceWorld L9 proofs on both compilers checked 25,720 blocks / 34,233,320 samples (1,304 expected fallbacks, 0 unknown, 0 violations). Final-source r55 FI proof runs on both compilers had 2,560 expected fallbacks/unknowns, 0 checked samples and 0 violations; the boundary audit checked 27 endpoints with 0 disagreements. Final-source L13 route parity passed on both compilers: 8 page pairs, 14 higher-LOD exact pairs, 782,026 exact-query samples, 0 mismatches, one edit pair, 64 expected unknown proof blocks, 0 violations.
**Final-source r55 cross-compiler reruns:** Clang build, FI passage audit, all four style audits, 120-tile worker hash matrix, 24 m crossings, L13 parity, L9 FI proof and boundary checks pass. GCC build passes; its 120-tile worker matrix, 24 m crossings, L9 FI proof and boundary checks pass. The r55 GCC FI capsule audit did not finish; the prior r54 GCC audit, style sweep, crossings, parity, proofs and full LOD0–8 hash matrix passed.
## Changes and versions
- Shared passage planning honours all four styles and their per-strate parameters; Worm is the public default. It adds a deterministic anchor-root record, FI helix path, bounded wall relief and the naturalness audit.
- The owner FI upper-strate config selects Spiral. Its stable anchor/root is limited to ordinal 2; other island placements are unchanged.
- Lower SW arrival support overlays were removed so the native terrain meets the cave mouth without stacked shelves.
- The Linux offscreen capture helper gained a camera headlamp and optional world-space clip plane for cutaway views.
- `WorldAssemblyEvaluatorVersion=10`; FI recipe geometry version `5`; resolved-layout snapshot remains V3.
Unreal-facing edits are limited to `Source/VoxelForge/Public/VoxelStrateTypes.h` (per-strate passage fields/default) and `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` (forward those fields to Core). The remaining changes are shared standalone Core, audit/capture support, owner JSON and docs: `VoxelForgeCore.h`, `FloatingIslandsInternal.h`, `ResolvedLayoutInternal.h`, `ShapeWorldInternal.h`, `StreamBenchmarkPipeline.h`, `WorldAssemblyPrototype.h`, `LinuxOffscreenCapture.cpp`, and `Standalone/data/owner_world.json`. Documentation is in `ARCHITECTURE.md` §8.42 and `CODEMAP.md` §3.25. Unreal changes are source-only here and were not compiled.
## Windows checklist
1. Regenerate project files and build `VoxelForgeEditor Win64 Development` with the project toolchain.
2. Confirm the FI upper-strate passage style is Spiral and the other unmodified strata use Worm; regenerate the resolved layout after changing strate passage settings.
3. In PIE, walk from the anchor island down the root and through the SurfaceWorld arrival; check capsule clearance, support, collision and mouth transitions.
4. Confirm the other FI islands still float, then repeat with the same seed on a second client/editor process and compare placement and passage shape.
5. Change one strate to Straight, Worm, Spiral and Cascading in turn; regenerate and verify only that strate's passage style changes.
## Failed and retried runs
- Early route calibration r32–r34 found 3–4 blocked capsule samples; r35–r42 radius/bore adjustments alternated blocked and unsupported samples. r39 also failed strict validation because a mouth exceeded the placement radius.
- r43 added diagnostics. r44 corrected XY projection. r45's 0.25 m lift produced 1,124 blocked samples and 4 unsupported; r46's 0.20 m lift left 4 unsupported; r47's 0.25 m lift produced 1,042 blocked; r48's relaxed support rule produced 5,660 blocked.
- r49 build failed on an undeclared `DeterministicSqrt`; r50 corrected the name but still had 5,609 blocked; r51 narrowed support and reached zero blocked/unsupported. r52 removed the fixture's FI style override and restored the L23 geometry comparison.
- r53's 0.16 m relief failed the valid-wall-sample threshold for some styles. r54 increased relief to 0.24 m and set the minimum to 8 samples; all four styles passed. r55 removed lower-mouth support shelves; Clang FI and style audits still pass.
- GCC r55 style sweep was interrupted after its Straight case emitted capsule and naturalness PASS results, before a complete style summary. A separate GCC r55 FI capsule-audit retry remained silent and was stopped after roughly 8 minutes. GCC r54 audits passed.
- Three unrestricted r55 stream-hash attempts (FI W1, FI W12, and SurfaceWorld W12) stopped after shape compilation with no pipeline summary. Limiting the final-source matrix to LOD0–2 completed 120 full-detail plus 56 LOD2 tiles and passed across both compilers and worker counts. A `max-clip-level=1` trial was rejected by Core's bounded 4,096-node shape compiler envelope; retrying with the supported clip limit and `max-level=1` passed.
- An LOD0–3 FI proof invocation remained silent after shape compilation; the LOD0–1 FI proof retry and the 960-tile SurfaceWorld proof completed on both compilers with zero violations. Camera trials replaced a horizontal SW view that showed flat bands with the downward arrival view; a closer oblique trial also exposed stair-like bands and was discarded. Low-FOV interior trials showed only a close wall, so the final interior view uses a 150-degree camera and headlamp.
- Two initial r55 capture exports at `max-level=1` wrote empty 4,380-byte mesh scenes; the renderer rejected them for containing no triangles. An unrestricted `max-level=3` capture export reached 593,068 desired tiles, grew to an incomplete 1.7 GB mesh and was stopped. Bounded exports without an explicit camera used the profile viewer start away from the anchor; repeating them at `(9.8, 4.6, -85) m` produced the intended 195-tile views. A three-value `--camera` argument was rejected (exit 2) and corrected to the required five-value form. An early bounded export with the wrong camera and coarse mesh was discarded. Initial Mesa cache writes targeted the read-only home cache and were disabled; subsequent renders used `Saved/Linux/mesa-shader-cache`. No system changes were made.
- One initial headless invocation omitted required flags (exit 2), and one GCC binary name typo returned 127; both were corrected. The offscreen capture helper was rebuilt after adding clip-plane cutaways and brighter headlamp shading.
No commit, push or stash was made. No Unreal build was available on this machine.
## Orchestrator review (Claude)
- **Re-run by me, full view (LOD0–8, 2,370 tiles), Clang W1/W8/W12 + GCC W12:** identical whole-view hashes
(SW `c817c4c84eb82e11/53e2e3b2358d2cb1/2c4f1724df03c3da/61a51ac3091f9e8f`, FI
`ea50fd77bea1fc9f/b4344b2b80a63eb3/8839f2e0fd20290b/9c98918b7ca4e2e8`); SW 1.44 s, FI 15.3 s whole view.
The "unrestricted r55 runs stopped after shape compilation" were command-line issues, not a code hang.
- Merged for: per-strate passage styles honoured in Core, Worm as the natural default, the naturalness
check with an L23 negative control, and the removed SW lower shelves.
- **Not as the owner asked (my capture review):** in `straight-vs-worm.jpg` the FI helix appears as a
jagged spiral **ribbon hanging in open air around the pillar** (wider than the pillar, and continuing
below the floor), not a tunnel enclosed inside the rock root. `sw-arrival-mouth.jpg` and
`fi-helix-inside.jpg` are too close or blurry to judge. Next round: the helix enclosed in rock, no
tread or support geometry in open air, readable wide captures.
- Unreal edits (`VoxelStrateTypes.h` passage fields/default, CoreAdapter) uncompiled here.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Loaded 100 of 444 files, more files were not shown because too many files have changed in this diff. Show more