standalone: VoxelForgeCore link-safe next to Unreal -- internals in vfcore::detail, symbol check

- The copied generator, mesher and Compat stand-ins compile inside vfcore::detail.
- CheckCoreSymbols.ps1 (run by Build.ps1) fails on any unqualified Unreal-style symbol in the
  library: 16,255 on 557f6aa, 0 now. A UE 5.7 CoreMinimal.h + VoxelForgeCore.h smoke compile passes.
- Output identical: 11 hash rows at W1/W8, routes 5642/5642, look/passage/far audits pass,
  bad-shell control fails, six captures byte-identical.
Cost, measured by me interleaved (8 workers, cold, 4 runs each, first run excluded): 0.963 s vs
0.937 s for 557f6aa, ~27 ms in shape compilation; same math, so codegen/inlining. To be found.

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-05 15:20:29 +02:00
1 parent 557f6aac30
commit aeeedaa73d
11 files changed
+461 -87

No files matched your search

+5
View File
@@ -4,6 +4,11 @@ Public header: `Core/include/VoxelForgeCore/VoxelForgeCore.h` (C++20, standard l
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**.
+82
View File
@@ -0,0 +1,82 @@
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$LibraryPath,
[string]$ResultPath
)
$ErrorActionPreference = 'Stop'
$script:Root = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$LibraryPath = [IO.Path]::GetFullPath($LibraryPath)
if (-not (Test-Path -LiteralPath $LibraryPath -PathType Leaf)) {
throw "Core library not found: $LibraryPath"
}
$VsBinRoots = @(Get-ChildItem -LiteralPath 'E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC' -Directory -ErrorAction Stop |
ForEach-Object { Join-Path $_.FullName 'bin\Hostx64\x64' } |
Where-Object { Test-Path -LiteralPath (Join-Path $_ 'dumpbin.exe') })
if ($VsBinRoots.Count -eq 0) { throw 'dumpbin.exe was not found in the Visual Studio toolchain.' }
$Dumpbin = Join-Path $VsBinRoots[-1] 'dumpbin.exe'
# Build the forbidden set from the compatibility and copied Unreal-facing declarations so the
# check covers the complete private type surface instead of a short hand-maintained example list.
$ScanRoots = @(
(Join-Path $script:Root 'Compat'),
(Join-Path $script:Root 'Source'),
(Join-Path $script:Root 'Core\src')
)
$TypeNames = [Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal)
foreach ($ScanRoot in $ScanRoots) {
foreach ($File in Get-ChildItem -LiteralPath $ScanRoot -Recurse -File -Include *.h,*.hpp,*.cpp) {
foreach ($Match in [regex]::Matches([IO.File]::ReadAllText($File.FullName),
'(?m)^\s*(?:class|struct|enum(?:\s+class)?|using)\s+([FTUE][A-Z][A-Za-z0-9_]*)\b')) {
[void]$TypeNames.Add($Match.Groups[1].Value)
}
}
}
if ($TypeNames.Count -eq 0) { throw 'Could not derive Unreal-style type names from the copied implementation.' }
$Dump = @(& $Dumpbin /SYMBOLS $LibraryPath 2>&1)
if ($LASTEXITCODE -eq -1073741502) { throw 'dumpbin.exe exited with 0xC0000142; stopping as required.' }
if ($LASTEXITCODE -ne 0) { throw "dumpbin.exe failed with exit code $LASTEXITCODE" }
if ($Dump.Count -lt 1000) {
throw "dumpbin returned only $($Dump.Count) lines; Core object symbols are not visible enough to verify."
}
$Violations = [Collections.Generic.List[string]]::new()
$Alternation = ($TypeNames | Sort-Object { $_.Length } -Descending | ForEach-Object { [regex]::Escape($_) }) -join '|'
$GlobalTypePattern = [regex]::new('(?<![A-Za-z0-9_:])(?:' + $Alternation + ')(?![A-Za-z0-9_])',
[Text.RegularExpressions.RegexOptions]::Compiled)
$PrivateTypePattern = [regex]::new('vfcore::detail::(?:class\s+|struct\s+|enum\s+)?(?:' + $Alternation + ')(?![A-Za-z0-9_])',
[Text.RegularExpressions.RegexOptions]::Compiled)
$PrivateDestructorPattern = [regex]::new('~(?:' + $Alternation + ')(?![A-Za-z0-9_])',
[Text.RegularExpressions.RegexOptions]::Compiled)
foreach ($Line in $Dump) {
if ($Line -notmatch '\|') { continue }
$Symbol = ($Line -split '\|',2)[1]
$DemangleOpen = $Symbol.IndexOf('(')
if ($DemangleOpen -lt 0) { continue }
$Demangled = $Symbol.Substring($DemangleOpen+1)
# dumpbin repeats decorated-only linker helpers in parentheses when it has no readable
# demangled form. Ignore those and remove known vfcore-qualified type spellings, including the
# repeated destructor spelling after "::~", before looking for global type names.
if ($Demangled -match '^\s*[?$]') { continue }
$Demangled = $PrivateTypePattern.Replace($Demangled,'')
$Demangled = $PrivateDestructorPattern.Replace($Demangled,'')
# A type preceded by a scope separator belongs to a namespace. An unqualified instance in
# the demangled dump is a global Unreal-style type and can ODR-collide with the engine.
if ($GlobalTypePattern.IsMatch($Demangled)) {
$Violations.Add($Line.Trim())
}
}
if ($Violations.Count -gt 0) {
$ResultLines = @("CORE_LINK_SAFETY_FAIL global Unreal-style symbols: $($Violations.Count)") +
@($Violations | Select-Object -First 40)
if ($ResultPath) { [IO.File]::WriteAllLines([IO.Path]::GetFullPath($ResultPath),$ResultLines) }
$ResultLines | ForEach-Object { Write-Output $_ }
throw "Core archive contains $($Violations.Count) unqualified Unreal-style type symbols."
}
$ResultLines = @("CORE_LINK_SAFETY_PASS checked $($Dump.Count) dumpbin lines against $($TypeNames.Count) copied Unreal-style type names.")
if ($ResultPath) { [IO.File]::WriteAllLines([IO.Path]::GetFullPath($ResultPath),$ResultLines) }
$ResultLines | ForEach-Object { Write-Output $_ }
@@ -0,0 +1,54 @@
#pragma once
// Every standard or platform header used by the copied implementation must be included before
// its wrapper opens vfcore::detail. This prevents system declarations from being nested in the
// private namespace when the implementation includes its Unreal-shaped compatibility headers.
#include <algorithm>
#include <array>
#include <atomic>
#include <bit>
#include <cassert>
#include <cctype>
#include <charconv>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
#include <immintrin.h>
#include <initializer_list>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <math.h>
#include <memory>
#include <memory_resource>
#include <mutex>
#include <new>
#include <numeric>
#include <optional>
#include <shared_mutex>
#include <span>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include <vector>
// VFJson is a small, stateless standalone input parser with no Unreal-shaped names. Keeping its
// value tree shared lets the viewer resolve its reference world and Core snapshot from one parse.
#include "Json.h"
@@ -1,12 +1,15 @@
#pragma once
#include "OwnerWorldLoader.h"
#include "Json.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <memory>
#include <string>
namespace VoxelForgeCore
{
// Private standalone adapter: keeps the compatibility holders behind the public value API.
ResolvedWorldSnapshot MakeStandaloneSnapshot(std::shared_ptr<FStandaloneOwnerWorld> owner);
// Standalone-only bridge. Core parses into its namespaced compatibility snapshot and applies
// the same command-line overrides as the viewer's reference loader.
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
bool roundTunnels,bool enhancedTunnelWalls,std::string& error);
}
+37 -11
View File
@@ -1,20 +1,18 @@
#include "CoreNamespacePreamble.h"
#include "StandaloneSnapshotAdapter.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
namespace vfcore::detail
{
#include "FarShellsInternal.h"
#include "VoxelClipmapDesiredTiles.h"
#include "VoxelMarchingCubesMesher.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <bit>
#include <cmath>
#include <cstring>
#include <map>
#include <numeric>
#include <stdexcept>
}
namespace VoxelForgeCore
{
using namespace ::vfcore::detail;
struct ResolvedWorldSnapshot::Data
{
std::shared_ptr<FStandaloneOwnerWorld> owner;
@@ -185,8 +183,36 @@ namespace VoxelForgeCore
}
};
ResolvedWorldSnapshot MakeStandaloneSnapshot(std::shared_ptr<FStandaloneOwnerWorld> owner)
{ return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner)); }
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
bool roundTunnels,bool enhancedTunnelWalls,std::string& error)
{
auto owner=std::make_shared<FStandaloneOwnerWorld>();
if(!StandaloneOwnerLoader::Resolve(jsonRoot,*owner,error))return {};
const bool seedChanged=owner->Seed!=seed;
owner->Settings->Seed=seed;
owner->Seed=seed;
owner->ClipRadius=clipRadius;
owner->Settings->ClipRadius=clipRadius;
if(seedChanged)
{
owner->Manager=std::make_unique<UVoxelStrateManager>();
if(!owner->Manager->Initialize(owner->Settings.get(),seed))
{error="standalone copied strate manager failed while applying viewer settings";return {};}
owner->Generator=std::make_unique<UVoxelGenerator>();
owner->Generator->InitializeSettings(owner->Settings.get());
owner->Generator->SetStrateManager(owner->Manager.get());
}
owner->Generator->bStandaloneResearchRoundTunnels=roundTunnels;
owner->Generator->bStandaloneResearchEnhancedWalls=enhancedTunnelWalls;
if(maxLodOverride>=0)
{
owner->MaxClipLevel=maxLodOverride;
owner->Settings->MaxClipLevel=maxLodOverride;
}
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner));
}
std::optional<ResolvedWorldSnapshot> ResolvedWorldSnapshot::FromJson(
std::string_view json,std::string& error)