Files
Fr0zkaandClaude Opus 5.5 dcf0763ec5 wip(assembler): round L18 -- FloatingIslands false floors removed; shared strate ceiling
FI geometry v3 drops the all-XY lower boundary seal that created false floors (3.5/8 m); the
ceiling comes from the shared VF_ApplyUpperBoundarySeal and AppendStratumBoundaryCeilings path
for every generator, at each strate's own configured upper boundary (owner: every strate has a
ceiling). Far-LOD ceiling continuity: 0 gaps at LOD4-8. SurfaceWorld bit-identical to L14
(14 configs + 24 m crossing); FI deterministic (28-row matrix); route parity and proofs pass.
Unreal: FI geometryVersion 2->3 and the shared upper-seal helper extracted (UBT compile needed on
the owner PC). Open: small ceiling holes; lower-stratum LOD ring steps seen through the FI void.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-20 07:55:53 +02:00

15 KiB
Raw Permalink Blame History

VoxelForgeCore API reference

Public header: Core/include/VoxelForgeCore/VoxelForgeCore.h (C++20, standard library only). Build the static library and viewer with Build.ps1 -Target All, or only the library with Build.ps1 -Target Core.

The public header uses only standard C++ types and can be included after Unreal's CoreMinimal.h. Core/verification/UnrealHeaderCoexistence.cpp is the compile smoke check against the installed Unreal 5.7 headers. Copied generator and compatibility declarations stay in the private vfcore::detail implementation and do not appear in this API.

Units and ownership

  • Vec3d query positions and Vec3i voxel coordinates are in voxels.
  • TileKey.coordinate is a tile coordinate; one coordinate step spans 32 << lod voxels.
  • Mesh vertex positions are centimetres. UVs, RGBA8 colors, normals and 32-bit indices are returned per vertex / triangle in owned std::vectors.
  • MeshGroups::groundTriangleCount and ceilingTriangleCount describe the contiguous index runs emitted by the mesher. Ceiling is the final run. skirtTriangleCount is a subset of the ground and ceiling runs. surfaceVertexCount ends before skirt vertices.
  • A World owns its compiled field and edit log. The snapshot can be shared as immutable input. Serialize compile/edit changes against query and tile-generation calls on that same World.

Load and compile

ResolvedWorldSnapshot::FromJson accepts the normalized UTF-8 schema-v1 owner export used by the standalone loader. It returns std::nullopt and fills error when parsing or asset resolution fails. Read configuration() for the resolved scalar settings.

Implement JobScheduler::parallel_for in the host. It must run each index from zero to count - 1 exactly once and return only after all submitted work is complete. The library creates no threads. ShapeBuildRequest supplies the detail and clip bounds in voxels, worker limit, detail variant, far-world inclusion, and optional tunnel/detail profiles.

std::string error;
auto snapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson(json, error);
if (!snapshot) { /* report error */ }

VoxelForgeCore::World world(std::move(*snapshot));
VoxelForgeCore::ShapeBuildRequest build;
build.minVoxel = {-256, -768, -384};
build.maxVoxel = { 256, -256,  128};
build.clipMinVoxel = {-57344, -57344, -8192};
build.clipMaxVoxel = { 49152,  49152,  8192};
build.workerLimit = 8;
build.includeFarWorld = true;
if (!world.compile(build, hostScheduler, error)) { /* report error */ }

For a streamed shape world, compile the initial camera region once, then add deterministic detail pages before generating any newly desired fine tile outside that region:

const auto entering = world.desiredTiles(newCameraVoxels);
VoxelForgeCore::TileGenerationOptions options;
std::size_t pageCoveredTiles = 0;
if (!world.compileTileRegion(entering, 8, hostScheduler, error, &pageCoveredTiles)) {
    /* report error */
}
for (const auto& key : entering) {
    if (auto mesh = world.generateTile(key, options, error)) { /* upload and retain */ }
}
world.retainTileRegions(entering);

By default, the Core groups missing keys by LOD and builds one analytic primitive/bin region for each entering LOD slab. The region bounds come from the requested absolute tile coordinates, with an 8-voxel sampling apron; no dense lattice is allocated for unused parts of the slab. A tile's samples therefore depend on its world coordinates and the world snapshot, not the camera path or the order in which tiles arrive. compileTileRegion skips LODs at or above fullResolutionLodCount, keys covered by the initial ShapeBuildRequest, and keys already resident. Its optional output counts requested keys that received a new region. After the regions needed by the next desired set compile, call retainTileRegions with that set to release regions that are no longer needed. The Core owns region lifetime and mesh data; the host keeps still-visible meshes resident until replacement tiles are generated and uploaded.

Pass analyticOnly = false to retain the reference packed-page compiler. Packed pages use absolute parent-cell coordinates and an 8-voxel apron; each covers a 128-voxel cube or one larger coarse tile. This path remains available for parity checks and workloads that need the packed lattice.

World assembler manifest v1 (evaluator v1–v4)

ResolvedWorldSnapshot::FromJson accepts an optional world_assembly object alongside the owner snapshot. The object is the shipped-plan payload shape: a fixed-scale recipe and finite-strate plan with hashes, no generated mesh or voxel samples. The current review payload is written to round4/world_assembly.json. Evaluator 1–3 payloads remain readable with their original hashes and field semantics. Evaluator 4 adds a minimum two-voxel shelf thickness and a versioned route support contract; it requires at least two route waypoints.

Every coordinate and scalar uses signed integer units of 1/256 voxel (units_per_voxel: 256), independent of the plugin's 25 cm Core voxel and of tile size. The schema version is 1; evaluator versions 1 and 2 are accepted, with evaluator 2 used for new recipes. Evaluator 1 retains its original evaluation and hash semantics. JSON integer fields are canonicalized in declared field order and hashed as 4- or 8-byte little-endian words with FNV-1a 64. Hashes are emitted as 16 lowercase hexadecimal characters; JSON key order and whitespace do not affect them. The root, recipe, plan and each stratum carry a content hash. The recipe stores its seed, warped biome-map settings and one to eight biome profiles; the plan stores finite-strate extents, cap settings, stable-ID features, fixed-season water, route points and (for evaluator 4) player-capsule, grade, support and sweep-spacing constraints. Route points are fixed-scale capsule-center locations with stable IDs and a protected automatic-change corridor radius. Bounds, feature count, route count, route IDs, corridor widths and finite-strate capsule extents are validated before a manifest is accepted.

Evaluator 2 adds climate-based biome assignment, output-space blending of both ground and cap profiles, per-biome relief and height warp, beach width, cliff limits, and optional per-biome cap profiles. Feature types 1, 2 and 3 represent a cliff plane, a bounded 3D overhang box and an authored cap feature. An overhang feature has a stable ID and a finite AABB clipped to its stratum; intervals outside the box can use its certified influence bound, while intersecting intervals remain on the evaluation path. Water surface and fill are separate sample outputs. The season water plane is fixed; this block does not generate a water mesh.

std::string error;
auto manifest = VoxelForgeCore::WorldAssemblyManifest::FromJson(payload, error);
if (!manifest || !manifest->validate(error)) { /* reject the payload */ }
const auto* loaded = snapshot.worldAssembly(); // null when the owner snapshot has no plan

Heightfield2D is the first field block. point(xQ, yQ, zQ) evaluates the biome map and blended height profiles at fixed-scale coordinates, returning the ground and cap heights, primary and neighbor biome IDs, Q16 blend weight and signed density. Evaluator 2 also returns the fixed water surface and water-fill sample values. Negative density is solid. Its contract reads biome and writes height, rock, water and waterSurface; route and surfaceTags remain declared channels for later blocks. The detail envelope reports maximum absolute displacement and minimum feature size. Evaluator 4 defines the shelf's minimum solid thickness as two Core voxels (0.5 m) and applies its feather to the horizontal footprint so it cannot thin the roof-to-floor section. AuditHeightfieldShelfThickness samples shelf sections against the pre-feature field and includes the previous sub-voxel taper as a negative control. Influence is global in XY and limited to the finite stratum's Z extent, except for the explicitly bounded feature boxes.

interval(minXQ, minYQ, minZQ, maxXQ, maxYQ, maxZQ) returns a conservative density range or known == false; callers must keep evaluating when it is unknown. The current heightfield range uses conservative profile, beach, cliff, cap and bounded-feature envelopes. The public AuditHeightfieldBounds samples points in randomized boxes and targeted biome-border, cliff, cap, negative-coordinate and overhang boxes, and includes a deliberately false one-sided range as a negative control. A pass means no sampled point escaped and the negative control was rejected; it is an audit of this block implementation, not a proof for future blocks.

AuditHeightfieldRoute sweeps the recorded route through the final composed field using the versioned player capsule (0.34 m radius, 1.76 m height), checks a 15-degree maximum grade, samples capsule clearance at no more than half-voxel intervals, and probes support below five foot locations at each swept center. Automatic changes must be composed first and pass this audit before the plan is accepted. Player carve/fill edits remain unrestricted. This validates analytic field collision and support; it does not cook an Unreal physics mesh.

Floating-island feature family v1 (standalone Core)

FloatingIslandRecipeV1 and FloatingIslandManifestV1 define a separate, reusable finite 3D feature family. Coordinates, dimensions, and density values use signed 1/256-voxel units; negative density is solid. The recipe carries its seed, fixed vertical layout, site density, island count and spacing, radius range, top flattening, taper, bounded contour warp, roughness, blend, connector dimensions and 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.

FloatingIslandFieldV1::densitySubvoxels evaluates the canonical island and connector field. interval returns unknown for boxes overlapping any influence AABB and a certified all-air range outside them. Geometry version 2 builds a flat walkable crust plus deterministic lobed rock roots, keeps bounded oblique surface detail inside its certified envelope, and arches the connector undersides. AABBs include root depth, contour displacement, detail, arch sag and smoothing reach. AuditFloatingIslandBounds checks sampled boxes and void beside connectors and includes a deliberately shrunken island-bound negative control. AuditFloatingIslandThickness reports each island's (topZ - bottomZ) / (2 * radius) and checks a caller-supplied minimum; AuditFloatingIslandRoute samples the player capsule and support along the serialized route. M3 round 2 passed at a minimum depth-to-diameter ratio of 0.75 against a 0.70 threshold, while a 0.30 thin-pad control failed.

The family is still separate from WorldAssemblyManifest and the Unreal adapter. M3 rounds 1–2 prove the standalone Core representation, bounded geometry, route, determinism and matched legacy timing. The frozen owner evaluator is a visual and timing oracle, not a hash target. Unreal import and cooked collision are planned for M3 round 3.

Queries and tiles

densityAt(position) returns signed density; negative is solid and nonnegative is air. Server and authority code can compile/query a World and run AuditHeightfieldRoute without calling generateTile; generated render meshes are not part of this query path. Unreal physics collision cooking remains the adapter's separate readiness step. isSolidAt wraps that sign test. isSolidAlongSegment(start, end, spacing) samples evenly along the segment with no interval wider than spacing voxels; it returns true if any sample is solid. classifyBox(origin, step, cells) returns allSolid, allAir or mixed for mesher tile classification.

desiredTiles(cameraVoxels) returns the snapshot's clipmap set. Pass it to prioritizeTiles(tiles, centerChunk) when submitting work. Generate independent keys concurrently on host workers:

VoxelForgeCore::TileGenerationOptions options;
auto mesh = world.generateTile(key, options, error);
if (!mesh) { /* report error */ }
// mesh->vertices, mesh->indices and mesh->groups are owned by this result.

The optional hashDensity flag fills deterministic density, viewer-mesh, payload and topology hashes. captureDensitySamples is a diagnostic option and retains the sampled lattice values.

Deterministic edits

Build an EditTransaction with a monotonically increasing authority sequence and an ordered brush array (sphere, capsule, roundedBox; carve, addRock, densityOffset). carve and addRock apply hard signed-distance CSG. densityOffset applies strength inside a sphere or inside/around a capsule or box with its falloff band; negative strength carves and positive strength adds rock. This operation maps the plugin's soft FVoxelModification brushes into the shared field. Apply the transaction against the currently desired keys:

VoxelForgeCore::EditTransaction edit;
edit.sequence = 12;
VoxelForgeCore::EditBrush brush;
brush.shape = VoxelForgeCore::EditBrush::Shape::capsule;
brush.operation = VoxelForgeCore::EditBrush::Operation::densityOffset;
brush.a = {0.0, 0.0, 0.0};
brush.b = {12.0, 0.0, 0.0};
brush.radius = 3.0;
brush.strength = -10.0f;
brush.falloff = 2.0f;
edit.brushes.push_back(brush);
const auto affected = world.applyEditTransaction(edit, currentTiles, error);
if (affected) {
    for (const auto& lodTiles : affected->affectedByLod)
        for (const auto& key : lodTiles)
            submitHostJob([&, key] {
                std::string tileError;
                return world.generateTile(key, options, tileError);
            });
}

The transaction digest covers the sequence and each brush field in order. Replaying the same sequence and values returns the original result; reusing a sequence with different values or applying an older sequence is rejected. Each client must deserialize identical brush values and apply transactions in the same sequence. affectedByLod is limited to the desired keys passed to the call.

Other outputs

farConnectorSegments() returns owned connector geometry, while farConnectorCandidates(min,max) returns candidate indices overlapping a voxel-space box. The audit methods expose shape detail, surface-family and camera-look checks for host diagnostics. All returned values are standard C++; the host owns conversion to engine mesh, collision or renderer objects.