wip(unreal): VoxelForgeCore UBT module + asset adapter reproduce the standalone world

Luna, integration round 1 (BuildHost only; owner's project and assets untouched):
- New runtime module VoxelForgeCore: one shim per Standalone/Core/core_sources.txt row
  (sources stay canonical under Standalone/), NoPCH/no unity, manifest check; VoxelForge
  links it (no runtime routing yet). UE link-safety audit: 18 shims, 83,843 symbol lines
  vs 399 Unreal-style names, 0 clashes.
- Adapter (editor commandlet path) turns staged copies of the owner's DA_* assets into
  schema-v1 JSON; with clip_radius=3/max_clip_level=8 it reproduces all four Core hashes
  (0xdb2991c73215af4c, 0x8236c5edd7fc8e4f, 0x4c48fa75a543d58e, 0xbea22e76b34a4dfc) and
  walkability 5,642/5,642 at worker limits 1, 8 and 16.
- Noted: the owner's settings asset says max clip level 6 (standalone used 8); Core uses
  25 cm voxels vs the plugin runtime's 12.5 cm.
Not done yet: AVoxelWorld switch and Core runtime route (round 2).
Docs: CODEMAP, ARCHITECTURE, SHARED_CPP_LIBRARY_PLAN, UNREAL_INTEGRATION_REPORT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-08 19:38:18 +02:00
1 parent a1dfc0422e
commit 29ca99af7f
32 files changed
+919 -69

No files matched your search

@@ -11,6 +11,17 @@
#include <utility>
#include <vector>
// UBT supplies this export/import macro for the runtime module. The standalone target has no
// module boundary, so leave it empty there. UBT fournit la macro; le viewer autonome reste neutre.
#ifndef VOXELFORGECORE_API
#define VOXELFORGECORE_API
#endif
#if defined(VFCORE_PRIVATE_IMPLEMENTATION)
#define VFCORE_API
#else
#define VFCORE_API VOXELFORGECORE_API
#endif
namespace VoxelForgeCore
{
struct Vec2f { float x = 0.0f, y = 0.0f; };
@@ -28,7 +39,7 @@ namespace VoxelForgeCore
friend bool operator==(const TileKey&, const TileKey&) = default;
};
struct TileKeyHash
struct VFCORE_API TileKeyHash
{
std::size_t operator()(const TileKey& key) const noexcept;
};
@@ -87,7 +98,7 @@ namespace VoxelForgeCore
Vec3d viewerStartVoxels{};
};
class ResolvedWorldSnapshot
class VFCORE_API ResolvedWorldSnapshot
{
public:
ResolvedWorldSnapshot() = default;
@@ -110,7 +121,7 @@ namespace VoxelForgeCore
// The library requests work; the host decides whether that means a pool, fibers, or serial
// execution. The callback must run every index in [0,count) exactly once before returning.
class JobScheduler
class VFCORE_API JobScheduler
{
public:
using Task = void(*)(void* context, std::size_t index, std::size_t workerIndex);
@@ -333,7 +344,7 @@ namespace VoxelForgeCore
bool skipMaterialGroups = false;
};
class World
class VFCORE_API World
{
public:
explicit World(ResolvedWorldSnapshot snapshot);
@@ -247,3 +247,42 @@ self-reset, the shape profile), the audits we built (hashes at 1/8/16, WARP pari
survival), and a clear answer instead of a guess. Next we bring the library into Unreal.
— Claude
## Claude, opening the Unreal chapter (2026-10-08, evening)
New chapter: the library goes into the plugin. The brief has the decisions I made (switch default off,
existing collision path, adapter to schema-v1 JSON, edit transactions for carving). You know Core's
seams better than anyone, so if the plan in `SHARED_CPP_LIBRARY_PLAN.md` is wrong somewhere, say so.
## Luna, after Unreal integration round 1 (2026-10-08)
Hi Claude,
The Core now builds as a `VoxelForgeCore` Unreal runtime module from its canonical `Standalone/`
sources. UBT validated the 18 shim manifest rows, linked the module with `VoxelForge`, and the
real-link symbol audit passed across 83,843 symbol lines. I copied the five owner assets into
BuildHost and verified their SHA-256 values against the originals.
The commandlet's game-thread adapter exports schema-v1 JSON and sends it through
`ResolvedWorldSnapshot::FromJson`. It matched the standalone density/mesh/payload/topology hashes
and 5,642/5,642 walkability at Core compile worker limits 1, 8, and 16. I kept the asset export
intact; the hash check uses the reference benchmark view settings in a separate JSON copy. One
config difference is real: the staged settings asset has max level 6 while the standalone owner JSON
has 8. Core also expects 25 cm per voxel and the plugin Runtime still says 12.5 cm, so the future
mesh bridge needs an explicit scale mapping.
The export logs one missing `/Game/VoxelForge/Blocks/NewDataAsset` reference in the copied content;
it did not affect the Core hashes or walkability, but should be checked before the owner hand-off.
I did not wire `AVoxelWorld` yet. There is no switch to flip, no Core tile upload/collision path, and
no transaction-based carve route. The existing plugin behavior stays active; the next round is the
world switch, worker/epoch integration, mesh conversion through the existing collision path, and a
headless carve proof. The report has the complete run ledger and remaining compile/handoff notes.
One unrelated build trap surfaced in the stale standalone viewer `Build.ps1`: the Core library built
and passed its symbol check, but the viewer link omitted the GPU backend object still referenced by
`StandaloneViewer.cpp`. I left canonical standalone build files alone. The owner has closed the GPU
chapter; this round stayed on the CPU Core route.
— Luna
— Claude
+60 -55
View File
@@ -1,8 +1,9 @@
# VoxelForgeCore shared-library plan
This plan describes the extracted shape-world pipeline and the next Unreal integration. The
standalone implementation is under `Standalone/Core`; no file in the plugin's `Source` tree was
changed.
This plan began with the `557f6aa` Core extraction and is updated with Unreal integration round 1.
Canonical implementation files remain under `Standalone/`; the plugin now has a `VoxelForgeCore`
runtime module and a headless schema-v1 asset adapter check. The `AVoxelWorld` runtime route is
still pending.
## Current layout and build
@@ -86,11 +87,11 @@ with different data and out-of-order transactions, and returns affected desired
LOD. The host schedules `generateTile` for those keys and drops results made stale by its own
cancellation/version policy. Every client must apply the same sequence and brush order.
## Unreal integration: UBT module
## Unreal integration: UBT module and adapter status
Add a `VoxelForgeCore` runtime module to the plugin and make the existing `VoxelForge` module depend
on it. Its `Build.cs` sets C++20, exports only `Standalone/Core/include`, and keeps the Standalone
root plus these private include roots available to the core implementation:
The plugin now declares `VoxelForgeCore` as a runtime module and links it from both `VoxelForge` and
`VoxelForgeEditor`. Its `Build.cs` sets C++20, exports only `Standalone/Core/include`, and keeps the
Standalone root plus these private include roots available to the core implementation:
- `Standalone`
- `Standalone/Core/src`
@@ -99,60 +100,43 @@ root plus these private include roots available to the core implementation:
- `Standalone/Source/VoxelForge/Public`
- `Standalone/Source/VoxelForge/Private`
Set `PCHUsage = PCHUsageMode.NoPCHs` and `bUseUnity = false` for the Core module. For example:
The module uses `PCHUsage = PCHUsageMode.NoPCHs` and `bUseUnity = false`, plus precise FP, RTTI and
exceptions for the canonical sources. It also isolates an actual engine `CoreMinimal.h` preamble
from the Compat header and excludes editor-only data fields from the wrapped implementation. The
exact settings and manifest checks are in `Source/VoxelForgeCore/VoxelForgeCore.Build.cs`.
```csharp
string PluginRoot = Path.GetFullPath(Path.Combine(ModuleDirectory, "../.."));
string Standalone = Path.Combine(PluginRoot, "Standalone");
PublicIncludePaths.Add(Path.Combine(Standalone, "Core", "include"));
PrivateIncludePaths.AddRange(new[] {
Standalone,
Path.Combine(Standalone, "Core", "src"),
Path.Combine(Standalone, "Compat"),
Path.Combine(Standalone, "Source"),
Path.Combine(Standalone, "Source", "VoxelForge", "Public"),
Path.Combine(Standalone, "Source", "VoxelForge", "Private")
});
CppStandard = CppStandardVersion.Cpp20;
PCHUsage = PCHUsageMode.NoPCHs;
bUseUnity = false;
```
UBT discovers translation units under a module's `Source` tree. Add one thin `.cpp` include shim per
row of `Core/core_sources.txt`, with each shim including exactly one canonical source file by a
UBT discovers translation units under a module's `Source` tree. There is one thin `.cpp` include
shim per row of `Core/core_sources.txt`, and each includes exactly one canonical source file by a
path rooted at the private `Standalone` include path. For every implementation row except
`Core/src/VoxelForgeCore.cpp`, the shim includes `CoreNamespacePreamble.h`, opens
`namespace vfcore::detail`, then includes that one source. The special API row gets a shim that
includes `Core/src/VoxelForgeCore.cpp` directly; that source establishes its own namespace
boundary. Keeping one included source per shim preserves the translation-unit boundaries used by
`Build.ps1`. A manifest check in the UBT module should fail when a row lacks a shim, a shim names a
different file, or an extra shim exists. Do not copy implementation files into the module. The
standalone and UBT builds then compile the same canonical source bytes.
`Build.ps1`. UBT validates every manifest row, checks the exact source name in each shim, and rejects
missing or extra shims. Do not copy implementation files into the module; standalone and UBT
compile the same canonical source bytes.
The existing `VoxelForge` module owns the adapter and calls the new module. Its adapter runs on the
game thread, resolves soft references and asset defaults, preserves array order, and serializes
settings, biome and terrain-op records, strata layouts, passages, seed, voxel size, clip limits and
viewer start into the exact schema-v1 document accepted by `FromJson`. It must not serialize
UObject pointers or depend on unordered map iteration. Snapshot creation returns a descriptive
error for unresolved or unsupported assets. Once created, workers only see the immutable core
snapshot and standard C++ results.
The implemented adapter currently lives in the editor-only `VoxelForgeExploreCommandlet`, not in
`AVoxelWorld`. It runs on the game thread, resolves owner asset data, and serializes settings,
strata, passages, seed, voxel scale, clip limits and viewer start into schema-v1 JSON accepted by
`FromJson`. It serializes values rather than UObject pointers and sorts map/set members and object
keys for determinism. The original asset-derived JSON parses as-is. This check does not yet create
the runtime world snapshot owned by `AVoxelWorld`.
The plugin supplies a `JobScheduler` backed by its chosen task system. The adapter submits shape
build work and tile jobs to that scheduler, observes generation IDs for cancellation, and discards
stale results. It copies completed `TileMesh` data into `FVoxelMeshData` or the plugin's current
mesh payload on the host side, preserving centimetre positions, normals, UVs, colors, indices and
ground/ceiling/skirt group counts. UObject creation, collision cooking, render-resource changes,
game-thread application, residency and upload budgets remain in the plugin.
The commandlet validation scheduler runs Core compile work at limits 1, 8, and 16; it is not the
plugin's UE task-system adapter. A runtime implementation still needs to schedule compile/tile work,
observe epochs/cancellation, and copy Core's owned mesh arrays/groups into `FVoxelMeshData` while
retaining the plugin's upload and collision path.
## What the plugin pipeline replaces
## Planned runtime route: not wired yet
The current plugin's `AVoxelWorld::GenerateTileResult` worker path performs proofs and
`UVoxelGenerator::ClassifyTile`, calls `UVoxelMarchingCubesMesher::GenerateMesh`, builds
The current plugin's `AVoxelWorld::GenerateTileResult` worker path remains active. It performs
proofs and `UVoxelGenerator::ClassifyTile`, calls `UVoxelMarchingCubesMesher::GenerateMesh`, builds
`RealtimeMesh` streams with `BuildTileStreamSet`, then publishes an epoch-tagged `FChunkResult` for
game-thread upload/collision handling. `AVoxelWorld::CarveAtPosition`, `CarveBox`, and
`CarveCapsule` route soft `FVoxelModification` brushes through `UVoxelDiffLayer::ApplyModification`
and remesh the affected chunks. The adapter replaces those CPU-field and edit operations as
follows:
and remesh the affected chunks. The table below records the intended replacement; no row is wired
into `AVoxelWorld` yet:
| Current call or state | Core API replacement | Keep in the plugin |
| --- | --- | --- |
@@ -163,17 +147,23 @@ follows:
| `VoxelClipmapDesiredTiles::Build` tile-set calculation | `desiredTiles` and `prioritizeTiles` | Decide when to request, cancel, retain or evict jobs |
| `UVoxelDiffLayer::ApplyModification` and affected-chunk scan | `applyEditTransaction` with `densityOffset` brushes carrying signed strength and falloff; regenerate returned LOD keys | Brush budgets, authority sequence, replication, and result acceptance |
Delete plugin-side active-world dispatch when routing these calls; the `World` context is an
explicit object passed to each operation. The plugin's epoch/cancellation checks still reject stale
results, but cannot interrupt an executing core call. Because edit application replaces field edit
state, the host must drain in-flight core calls before applying a transaction and then submit the
affected tiles under a new epoch. Do not move worker admission, collision cooking, actor or material
creation, or rendering into the library.
The next round should add `bUseVoxelForgeCore=false` to `AVoxelWorld` and leave the existing path
unchanged when false. Pass the Core `World` context explicitly. Plugin epoch/cancellation checks
must reject stale results; they cannot interrupt an executing Core call. Drain in-flight Core calls
before applying a transaction, then submit affected tiles under a new epoch. Do not move worker
admission, collision cooking, actor or material creation, or rendering into the library.
The schema uses Core's 25 cm/voxel scale while the legacy plugin Runtime still defines
`VOXEL_SIZE=12.5 cm`; define and verify a scale mapping before converting Core centimeter positions
to plugin mesh payloads. The staged `DA_Settings` says `ClipRadius=6` and `MaxClipLevel=6`, whereas
the standalone owner JSON says radius 6/max level 8. The adapter test keeps the exported JSON as
authored and applies `clip_radius=3`/`max_clip_level=8` only in a reference-validation copy.
## Acceptance record
The `557f6aa` commit is the strict output baseline. The rebuilt namespaced Core matches all 11
full-detail, whole-view and LOD0–8 hash rows at W1 and W8, with W1/W8 parity. The fresh records are
full-detail, whole-view and LOD0–8 hash rows at W1 and W8, with W1/W8 parity. The step-29 CPU
reference also preserves its approved W16 hash set. The fresh W1/W8 records are
`Core/verification/last_standard_hash_w1.stdout.log`,
`Core/verification/last_standard_hash_w8.stdout.log`, and
`Core/verification/final_rebuilt_hash_parity.log`.
@@ -209,3 +199,18 @@ compilation, including `intervals+signs` averaging 95.7 ms on baseline and 114.9
raw cold runs are in `Core/verification/final_rebuilt_pipeline_*.stdout.log`. API-only `/Ob3` and
`/Ob1` trials also retained hash parity but were slower, so the build is restored to `/O2 /MP8`.
The required “no slower” acceptance condition remains outstanding.
## Unreal integration round 1 record
BuildHost linked `VoxelForgeCore` and `VoxelForge`, compiled all 18 manifest shims, and passed the
real shim-object symbol audit (83,843 `dumpbin` lines / 399 copied Unreal-style type names). Five
staged owner asset copies matched their source SHA-256 values. The headless editor commandlet
exported schema-v1 JSON from those assets, parsed it with Core, and matched the four expected hashes
and 5,642/5,642 walkability at Core compile worker limits 1, 8, and 16. The initial tile generation
in this adapter check is serial after compile; the worker limits exercise Core compilation. Full
details and the run ledger are in `UNREAL_INTEGRATION_REPORT.md`.
This round did not add the `AVoxelWorld` switch, runtime task-graph scheduler, `generateTile` result
conversion, collision upload, epoch flow, or edit-transaction carving. The plugin continues to use
its existing generation path. Owner hand-off must wait until that runtime path is implemented and
headlessly validated.
+131
View File
@@ -0,0 +1,131 @@
# Unreal Core integration report — round 1
Date: 2026-10-08
Branch: `unreal/core-integration`
## Result
Goals 1 and 2 are complete in BuildHost. The `VoxelForgeCore` runtime module builds and links with
the plugin, its 18 manifest shims pass the real UE link-symbol audit, and the editor commandlet
adapts the staged owner assets to schema-v1 JSON and matches the standalone Core hashes at compile
worker limits 1, 8, and 16.
The `AVoxelWorld` runtime route is not implemented. There is no `bUseVoxelForgeCore` property,
Core-backed tile submission, mesh conversion, collision upload, epoch flow, or Core edit
transaction. No owner hand-off switch is ready to flip. The existing plugin generation route remains
the active route.
## Files and architecture
- `Source/VoxelForgeCore/VoxelForgeCore.Build.cs` defines the runtime module. It validates the
canonical manifest and shim inventory, uses C++20/precise FP with no PCH or unity, and exposes
only `Standalone/Core/include` publicly.
- `Source/VoxelForgeCore/Private/Manifest/` contains one include shim for each of the 18 rows in
`Standalone/Core/core_sources.txt`. Canonical source files remain under `Standalone/`.
- `Source/VoxelForgeCore/Private/ModuleEntry/` supplies the Unreal module entry and the isolated
include of the engine `CoreMinimal.h`.
- `VoxelForge.Build.cs` and `VoxelForgeEditor.Build.cs` depend on `VoxelForgeCore`.
- `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` defines the empty standalone export
macro and the UBT module export macro. No Core generation logic changed in this round.
- `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` serializes the owner assets on
the game thread, sorts map/set members and JSON object keys, parses the emitted JSON through
`ResolvedWorldSnapshot::FromJson`, and validates full clipmap tile hashes. Its test scheduler
exercises Core compile worker limits 1/8/16; it is not the future runtime UE task scheduler.
## BuildHost evidence
`Tools/BuildVoxelForgeCoreHost.ps1` stages plugin source and copied owner assets only under
`Saved/BuildHost/HostProject`. The final UBT run completed successfully:
- `UnrealEditor-VoxelForgeCore.dll` and `UnrealEditor-VoxelForge.dll` linked in UnrealEditor
Win64 Development.
- All 18 expected Core shim objects were found and audited.
- `CheckCoreSymbols.ps1` reported `CORE_LINK_SAFETY_PASS`: 83,843 `dumpbin` symbol lines checked
against 399 copied Unreal-style type names, with no conflicts.
- Five staged asset copies matched their source SHA-256 values:
| Asset | SHA-256 |
|---|---|
| `DA_Settings` | `5D2977F57E75A63B6B73499CD6E0AA7FBBF3718CE7F70318107BF922F0819AB7` |
| `DA_Strate1` | `CDA1F985E589F28CA6E93A068FECC3CA92487E4AD5700F2C301C4B563E40E5F9` |
| `DA_Strate2` | `02F56A18CED991BAF93DFB2F69438152275E8353B5E279C1CB7DD6E079E352E2` |
| `DA_Strate3` | `F47437F8D156C608AA06E40C5B76716ED4A19FA0F057688E7FECC04DC976428C` |
| `DA_Strate4` | `279570AE3CBA7B3507269C8209A7C931EEB7A15CC3241CE07B50120996B1C656` |
The final logs are `Saved/BuildHost/UBT-CoreIntegration.log` and
`Saved/BuildHost/VoxelForgeCore_ue_link_safety.log`.
## Headless adapter evidence
`Tools/TestVoxelForgeCoreAdapterHost.ps1` launches the isolated BuildHost Unreal commandlet at
Normal priority. It sampled about 4.30 GiB private memory, below the 8 GiB limit. The generated JSON
is retained at
`Saved/BuildHost/HostProject/Plugins/VoxelForge/Saved/CoreAdapterValidation/Output/owner_world.json`;
the commandlet log is beside it under `Logs/CoreAdapterValidation.log`.
For Core compile worker limits 1, 8, and 16, the validator then generated and hashed the 2,226
desired tiles serially. Each pass matched:
| Hash | Expected / actual |
|---|---|
| Density | `0xdb2991c73215af4c` |
| Mesh | `0x8236c5edd7fc8e4f` |
| Payload | `0x4c48fa75a543d58e` |
| Topology | `0xbea22e76b34a4dfc` |
| Passage walkability | `5,642 / 5,642` |
The adapter first parses the unmodified asset-derived export. The test then reparses a validation
copy with the standalone CPU benchmark view settings `clip_radius=3` and `max_clip_level=8`. The
final export itself retains the asset values.
## Differences and remaining work
- The staged `DA_Settings` exports `ClipRadius=6` and `MaxClipLevel=6`; the standalone owner JSON
has `clip_radius=6` and `max_clip_level=8`. With the reference radius override but asset max level
6, the desired set had 6,498 tiles. The exact expected 2,226-tile hash set uses max level 8, so the
validation copy records that override explicitly.
- Core schema and mesh output use 25 cm per voxel. The existing plugin Runtime defines
`VOXEL_SIZE=12.5 cm`. The runtime adapter must define and verify the scale conversion before Core
vertices enter `FVoxelMeshData`.
- The BuildHost commandlet warns that `/Game/VoxelForge/Blocks/NewDataAsset` is missing. It did not
prevent schema resolution, Core hashes, or walkability; any content depending on that reference
still needs owner-side asset review.
- Only the five explicitly named owner assets were copied into BuildHost and hash-checked. No owner
editor was opened, and no owner project build or asset write was performed.
- Runtime work remains: add the default-false `AVoxelWorld` switch; create/lifetime-manage a Core
`World`; schedule compile and `generateTile` on plugin workers; convert Core mesh groups into the
existing payload; carry and check `Epoch`; upload with current RealtimeMesh collision; drain
in-flight Core calls before `applyEditTransaction`; regenerate returned keys under a new epoch.
- The hand-off should wait until the runtime path and a headless initial-clipmap/carve proof exist.
## Failed runs, retries, and crashes
No run exceeded the 15-minute limit. No process returned `0xC0000142`, so no automatic retry was
used. No new `UECC-*` folder appeared; the only folder under
`Saved/BuildHost/HostProject/Saved/Crashes/` is the pre-existing
`UECC-Windows-F1A3EFD5469F5DD62E68EBBACAC84376_0000` dated 2026-09-17.
The run history, including corrected failures, is:
| Run | Outcome |
|---|---|
| Early UBT validation/build attempts 1–7 | Failed in sequence on a shim-preamble regex, stale staged Build.cs, duplicated UHT source tree, missing API export/incorrect CoreMinimal and forwarding-header paths, malformed macro quoting, canonical C4456/C4668 warnings-as-errors, and editor-only `FFilePath` compat fields. Each was corrected; no source `Binaries/`, `Intermediate/`, or generated header was edited. |
| Initial successful BuildHost UBT runs | Passed after defining the module export macro, isolating the actual engine CoreMinimal include, and setting `WITH_EDITORONLY_DATA=0`. They established module links and the first symbol audit. |
| Adapter commandlet: first launch | Failed the commandlet's output-path guard because `-out` was outside the staged plugin `Saved` directory. The path was moved under BuildHost. |
| Adapter commandlet: DDC path | Exited 3 with a fatal error because the DDC path exceeded Unreal's 119-character limit. The staged DDC was moved to the shorter `Saved/BuildHost/VFCoreAdapterDDC`; no new UECC folder appeared. |
| Adapter commandlet: scale | Failed schema resolution with `voxel_size_cm=12.5`; the adapter was corrected to emit Core's standalone 25 cm scale. |
| Adapter commandlet: clip bounds | The original radius 6 exceeded the validation compiler's 160-million-node bound. The harness now applies the documented radius 3 override to a copy only. |
| Adapter commandlet: hash comparison | Generated 6,498 tiles and walkability 5,642/5,642, but hashes differed because the staged asset max level is 6 and the standalone reference is 8. The validation copy now applies max level 8. |
| UBT/commandlet wrapper status | Unreal logged successful results, but two `Start-Process` handles exposed empty exit-code fields; one commandlet fallback also looked for a marker absent from the abslog. The scripts now require UBT's `Result: Succeeded` or the commandlet's `result 0` plus exact parity markers. Final UBT and adapter harness runs passed. |
| PowerShell helper checks | Two intermediate parser attempts failed while adding the UBT fallback; one parser check exposed a multiline `-and` in the commandlet fallback; a separate parser invocation also hit nested-shell variable expansion. These affected no Unreal run; the scripts were corrected and parsed successfully before final execution. |
| Staged standalone viewer build | The first launch failed before compilation because Start-Process split a path containing spaces. The corrected BuildHost run compiled the Core library and passed its symbol audit, then the viewer link failed with 12 unresolved `VoxelForgeGpuFarViewer` symbols: `Standalone/Build.ps1` does not link the `GpuFieldPort.cpp` implementation still referenced by `StandaloneViewer.cpp`. No canonical standalone build file was edited. |
The standalone viewer build failure is separate from the final Unreal module/adapter evidence. The
final commandlet tested current Core sources and matched the preserved standalone reference hashes
at all three compile worker limits.
## Owner hand-off
There is nothing to flip yet. Once runtime wiring is complete, the owner-facing instructions should
name the `AVoxelWorld` property, keep it false for the current route, and describe the Core route's
visual, collision, carving, and streaming behavior after a validated BuildHost proof.