Luna, M1 round 1 of 4 (plan in Standalone/ASSEMBLER_M1_REPORT.md): - Versioned fixed-scale world manifest (the shipped plan format) and the Core block contract: point evaluation, interval-or-unknown, influence bound, detail envelope, channels. - Interval audit: 256 boxes / 6,912 samples, 0 missed, 0 unknown; a deliberately false bound is detected (negative control). - Native Heightfield2D with biome input: 4,096-point hash fae2721c052aec5c identical at W1/W8/W16; point queries 2.059 ms vs 62.115 ms legacy -- but NOT a match yet (mean density error 274 voxels, 2,261 sign mismatches vs the legacy surface). 45 mesh-free Core queries match direct evaluation exactly. Existing cave world unchanged (walkability, 64/64 tiles). - Open: collision readiness, a valid full-view timing (viewer captures were occluded/empty; top-down height maps are the only usable pair). Also carries the design doc's owner decisions (fixed water; player edits unrestricted, route law for automatic changes only) and three plugin files + BuildHost scripts that earlier round commits had missed (also fixed on unreal/core-integration as 2574b56). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
9.9 KiB
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
Vec3dquery positions andVec3ivoxel coordinates are in voxels.TileKey.coordinateis a tile coordinate; one coordinate step spans32 << lodvoxels.- Mesh vertex positions are centimetres. UVs, RGBA8 colors, normals and 32-bit indices are
returned per vertex / triangle in owned
std::vectors. MeshGroups::groundTriangleCountandceilingTriangleCountdescribe the contiguous index runs emitted by the mesher. Ceiling is the final run.skirtTriangleCountis a subset of the ground and ceiling runs.surfaceVertexCountends before skirt vertices.- A
Worldowns 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 sameWorld.
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 (prototype)
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 reviewed sample payload is
round1-manifest.json. Round 4 still needs to
connect this payload to the season asset/hash path.
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. Schema and evaluator versions are
both 1. 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 and route points. Bounds,
feature count and route count are validated before a manifest is accepted.
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. Negative density is solid. Its block
contract reads biome and writes height plus rock; water, waterSurface, route and
surfaceTags are declared channels for later blocks. The detail envelope reports maximum absolute
vertical displacement and minimum feature size. Influence is global in XY and limited to the
finite stratum's Z extent.
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 the maximum recipe-profile amplitude and cap undulation over the whole biome set. The public
AuditHeightfieldBounds samples points inside randomized 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.
Queries and tiles
densityAt(position) returns signed density; negative is solid and nonnegative is air.
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.