wip(assembler): M1 round 1 -- manifest format, block contract, bound audit, native Heightfield2D
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
This commit is contained in:
1 parent
d5ce5d675d
commit
874c796c65
20 files changed
+2880
-12
No files matched your search
@@ -1547,6 +1547,8 @@ namespace
|
||||
bool bRoundTunnels=false;
|
||||
bool bRoundTunnelEnhanced=true;
|
||||
bool bShapeWorld=false;
|
||||
bool bWorldAssemblerPrototype=false;
|
||||
bool bWorldAssemblerAuditOnly=false;
|
||||
bool bShapePipelineActive=false;
|
||||
bool bNeighborhoodReadyReported=false;
|
||||
bool bShapeAuditOnly=false;
|
||||
@@ -7704,6 +7706,358 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
return pass&&analyticDirectionsSame&&analyticForwardCovered==1
|
||||
&&analyticReverseCovered==1&&packedAnalyticRenderSame?0:12;
|
||||
}
|
||||
|
||||
std::int64_t WorldAssemblyFixed(double voxels)
|
||||
{return static_cast<std::int64_t>(std::llround(voxels*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));}
|
||||
|
||||
std::int64_t WorldAssemblyWavelength(double frequency,double fallbackVoxels)
|
||||
{
|
||||
const double wavelength=std::isfinite(frequency)&&frequency>0.0?1.0/frequency:fallbackVoxels;
|
||||
return std::max<std::int64_t>(1,WorldAssemblyFixed(wavelength));
|
||||
}
|
||||
|
||||
bool AttachRound1WorldAssemblyPrototype(FStandaloneOwnerWorld& owner,VFJson::Value& root,
|
||||
std::string& error,const std::string& manifestOutputPath)
|
||||
{
|
||||
if(!owner.Manager||!owner.Generator)
|
||||
{error="world assembler prototype needs the resolved standalone owner world";return false;}
|
||||
int32 surfaceChunkZ=0,topChunkZ=0,bottomChunkZ=0;
|
||||
bool foundSurface=false;
|
||||
for(int32 chunkZ=1024;chunkZ>=-4096;--chunkZ)
|
||||
{
|
||||
if(owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,chunkZ))
|
||||
!=ECaveGeneratorType::SurfaceWorld)continue;
|
||||
surfaceChunkZ=chunkZ;
|
||||
foundSurface=owner.Manager->GetStrateChunkZBounds(chunkZ,topChunkZ,bottomChunkZ);
|
||||
break;
|
||||
}
|
||||
if(!foundSurface)
|
||||
{error="world assembler prototype could not find a finite SurfaceWorld strate";return false;}
|
||||
const FIntVector chunk(0,0,surfaceChunkZ);
|
||||
const FSurfaceGenerationParams surface=owner.Manager->GetSurfaceParamsForChunk(chunk);
|
||||
const FBiomeContext biomeContext=owner.Manager->GetBiomeContextForChunk(chunk);
|
||||
const std::int64_t bottomVoxel=static_cast<std::int64_t>(bottomChunkZ)*CHUNK_SIZE;
|
||||
const std::int64_t heightVoxels=static_cast<std::int64_t>(topChunkZ-bottomChunkZ+1)*CHUNK_SIZE;
|
||||
const double baseHeight=static_cast<double>(bottomVoxel)
|
||||
+static_cast<double>(heightVoxels)*surface.BaseGroundRelative;
|
||||
const double elevation=std::max(16.0,static_cast<double>(surface.ElevationRange));
|
||||
const FBiomeMapParams& map=biomeContext.Map;
|
||||
|
||||
VoxelForgeCore::WorldAssemblyManifest manifest;
|
||||
auto& recipe=manifest.recipe;
|
||||
recipe.seed=static_cast<uint32>(owner.Seed);
|
||||
recipe.biomeCellSizeSubvoxels=WorldAssemblyFixed(map.CellSize);
|
||||
recipe.biomeWarpSubvoxels=WorldAssemblyFixed(map.WarpStrength);
|
||||
recipe.biomeWarpWavelengthSubvoxels=WorldAssemblyWavelength(map.WarpFrequency,556.0);
|
||||
recipe.biomeBorderBlendSubvoxels=WorldAssemblyFixed(map.BorderBlend);
|
||||
VoxelForgeCore::WorldAssemblyBiomeProfileV1 lowland;
|
||||
lowland.stableId=0x1001u;
|
||||
lowland.baseHeightSubvoxels=WorldAssemblyFixed(baseHeight-elevation*0.15);
|
||||
lowland.continentAmplitudeSubvoxels=WorldAssemblyFixed(elevation*0.45);
|
||||
lowland.continentWavelengthSubvoxels=WorldAssemblyWavelength(surface.ContinentFrequency,800.0);
|
||||
lowland.mountainAmplitudeSubvoxels=WorldAssemblyFixed(elevation*0.12*surface.MountainStrength);
|
||||
lowland.mountainWavelengthSubvoxels=WorldAssemblyWavelength(surface.MountainFrequency,192.0);
|
||||
lowland.detailAmplitudeSubvoxels=WorldAssemblyFixed(std::max(1.0,static_cast<double>(surface.SurfaceRoughness)));
|
||||
lowland.detailWavelengthSubvoxels=WorldAssemblyWavelength(surface.DetailFrequency,24.0);
|
||||
VoxelForgeCore::WorldAssemblyBiomeProfileV1 upland=lowland;
|
||||
upland.stableId=0x1002u;
|
||||
upland.baseHeightSubvoxels=WorldAssemblyFixed(baseHeight+elevation*0.20);
|
||||
upland.continentAmplitudeSubvoxels=WorldAssemblyFixed(elevation*0.80);
|
||||
upland.continentWavelengthSubvoxels=WorldAssemblyWavelength(surface.ContinentFrequency*0.72,1120.0);
|
||||
upland.mountainAmplitudeSubvoxels=WorldAssemblyFixed(elevation*0.42*std::max(0.25,static_cast<double>(surface.MountainStrength)));
|
||||
upland.mountainWavelengthSubvoxels=WorldAssemblyWavelength(surface.MountainFrequency*0.75,256.0);
|
||||
upland.detailAmplitudeSubvoxels=WorldAssemblyFixed(std::max(2.0,static_cast<double>(surface.SurfaceRoughness)*1.5));
|
||||
upland.detailWavelengthSubvoxels=WorldAssemblyWavelength(surface.DetailFrequency*0.8,30.0);
|
||||
recipe.biomes={lowland,upland};
|
||||
|
||||
VoxelForgeCore::WorldAssemblyStratumPlanV1 stratum;
|
||||
stratum.strateIndex=owner.Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(surfaceChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
|
||||
stratum.topChunkZ=topChunkZ;
|
||||
stratum.bottomChunkZ=bottomChunkZ;
|
||||
stratum.capHeightSubvoxels=WorldAssemblyFixed(static_cast<double>(topChunkZ+1)*CHUNK_SIZE-4.0);
|
||||
stratum.capUndulationSubvoxels=0;
|
||||
stratum.capWavelengthSubvoxels=WorldAssemblyFixed(512.0);
|
||||
manifest.plan.strates.push_back(std::move(stratum));
|
||||
if(!manifest.finalize(error))return false;
|
||||
const std::string manifestText=manifest.ToJson();
|
||||
if(!manifestOutputPath.empty())
|
||||
{
|
||||
const std::filesystem::path outputPath(manifestOutputPath);
|
||||
std::error_code directoryError;
|
||||
if(!outputPath.parent_path().empty())
|
||||
std::filesystem::create_directories(outputPath.parent_path(),directoryError);
|
||||
if(directoryError)
|
||||
{error="could not create the world assembly manifest output directory";return false;}
|
||||
std::ofstream output(outputPath,std::ios::binary);
|
||||
if(!output)
|
||||
{error="could not open the world assembly manifest output path";return false;}
|
||||
output.write(manifestText.data(),static_cast<std::streamsize>(manifestText.size()));
|
||||
if(!output)
|
||||
{error="could not write the world assembly manifest payload";return false;}
|
||||
}
|
||||
VFJson::Value manifestJson;
|
||||
if(!VFJson::Parser(manifestText).Parse(manifestJson,error))return false;
|
||||
auto* rootObject=std::get_if<VFJson::Value::Object>(&root.Data);
|
||||
if(!rootObject)
|
||||
{error="owner snapshot root is not a JSON object";return false;}
|
||||
(*rootObject)["world_assembly"]=std::move(manifestJson);
|
||||
std::printf("[WorldAssemblerPrototype] recipe_version=%u evaluator_version=%u strata=%zu biomes=%zu strate_index=%d chunk_z=%d..%d serialized_bytes=%zu recipe_hash=%016llx plan_hash=%016llx manifest_hash=%016llx source=owner_surface_params_and_biome_map route_points=0 features=0\n",
|
||||
manifest.recipe.schemaVersion,manifest.recipe.evaluatorVersion,manifest.plan.strates.size(),
|
||||
manifest.recipe.biomes.size(),manifest.plan.strates.front().strateIndex,topChunkZ,bottomChunkZ,
|
||||
manifestText.size(),static_cast<unsigned long long>(manifest.recipe.contentHash),
|
||||
static_cast<unsigned long long>(manifest.plan.contentHash),
|
||||
static_cast<unsigned long long>(manifest.contentHash));
|
||||
return true;
|
||||
}
|
||||
|
||||
int RunWorldAssemblerAudit(FStandaloneOwnerWorld& owner,
|
||||
const VoxelForgeCore::ResolvedWorldSnapshot& snapshot,const std::string& captureDirectory)
|
||||
{
|
||||
const auto* manifest=snapshot.worldAssembly();
|
||||
if(!manifest||manifest->plan.strates.empty())
|
||||
{std::fprintf(stderr,"[WorldAssemblerAudit] no shipped world_assembly payload was resolved.\n");return 10;}
|
||||
const auto& plan=manifest->plan.strates.front();
|
||||
const VoxelForgeCore::Heightfield2D block(manifest->recipe,plan);
|
||||
if(!block.isValid())
|
||||
{std::fprintf(stderr,"[WorldAssemblerAudit] Heightfield2D rejected its recipe or plan.\n");return 10;}
|
||||
const auto contract=block.contract();
|
||||
const auto influence=block.influenceBounds();
|
||||
const auto detail=block.detailEnvelope();
|
||||
const auto bounds=VoxelForgeCore::AuditHeightfieldBounds(block,256,
|
||||
0x574f524c44415331ull^manifest->contentHash);
|
||||
std::printf("[WorldAssemblerBlock] name=Heightfield2D evaluator=%u reads=biome writes=height,rock influence_xy=global influence_z_q=%lld..%lld detail_envelope_q=%lld minimum_feature_q=%lld status=%s\n",
|
||||
contract.evaluatorVersion,static_cast<long long>(influence.minimumZSubvoxels),
|
||||
static_cast<long long>(influence.maximumZSubvoxels),
|
||||
static_cast<long long>(detail.maximumAbsoluteDisplacementSubvoxels),
|
||||
static_cast<long long>(detail.minimumFeatureSizeSubvoxels),bounds.passed?"PASS":"FAIL");
|
||||
std::printf("[WorldAssemblerBoundAudit] boxes=%llu point_samples=%llu missed_bounds=%llu unknown_bounds=%llu deliberately_wrong_bound_detected=%s status=%s\n",
|
||||
static_cast<unsigned long long>(bounds.boxesTested),
|
||||
static_cast<unsigned long long>(bounds.pointSamples),
|
||||
static_cast<unsigned long long>(bounds.missedBounds),
|
||||
static_cast<unsigned long long>(bounds.unknownBounds),
|
||||
bounds.negativeControlDetected?"true":"false",bounds.passed?"PASS":"FAIL");
|
||||
|
||||
struct Query {std::int64_t x=0,y=0,z=0;};
|
||||
constexpr int side=64;
|
||||
std::vector<Query> queries;
|
||||
queries.reserve(side*side);
|
||||
for(int y=0;y<side;++y)
|
||||
for(int x=0;x<side;++x)
|
||||
{
|
||||
const std::int64_t xq=(static_cast<std::int64_t>(x)-side/2)*128
|
||||
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t yq=(static_cast<std::int64_t>(y)-side/2)*128
|
||||
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const auto column=block.point(xq,yq,0);
|
||||
const std::int64_t offset=(static_cast<std::int64_t>((x+3*y)%9)-4)
|
||||
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
queries.push_back({xq,yq,column.groundHeightSubvoxels+offset});
|
||||
}
|
||||
std::vector<VoxelForgeCore::HeightfieldPointSample> oneWorker(queries.size());
|
||||
const auto evaluate=[&](std::uint32_t workers,
|
||||
std::vector<VoxelForgeCore::HeightfieldPointSample>& output)
|
||||
{
|
||||
const auto begin=std::chrono::steady_clock::now();
|
||||
std::vector<std::thread> threads;
|
||||
for(std::uint32_t worker=0;worker<workers;++worker)
|
||||
{
|
||||
const std::size_t first=queries.size()*worker/workers;
|
||||
const std::size_t last=queries.size()*(worker+1)/workers;
|
||||
auto task=[&,first,last]
|
||||
{
|
||||
for(std::size_t i=first;i<last;++i)
|
||||
output[i]=block.point(queries[i].x,queries[i].y,queries[i].z);
|
||||
};
|
||||
if(workers==1)task();
|
||||
else threads.emplace_back(std::move(task));
|
||||
}
|
||||
for(auto& thread:threads)thread.join();
|
||||
return std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-begin).count();
|
||||
};
|
||||
const auto hashSamples=[](const std::vector<VoxelForgeCore::HeightfieldPointSample>& samples)
|
||||
{
|
||||
std::uint64_t hash=14695981039346656037ull;
|
||||
const auto add=[&](std::uint64_t value,int bytes)
|
||||
{
|
||||
for(int i=0;i<bytes;++i){hash^=static_cast<uint8>(value>>(8*i));hash*=1099511628211ull;}
|
||||
};
|
||||
for(const auto& sample:samples)
|
||||
{
|
||||
add(static_cast<std::uint64_t>(sample.groundHeightSubvoxels),8);
|
||||
add(static_cast<std::uint64_t>(sample.capHeightSubvoxels),8);
|
||||
add(sample.biomeId,4);add(sample.neighborBiomeId,4);add(sample.biomeWeightQ16,4);
|
||||
add(static_cast<std::uint64_t>(sample.densitySubvoxels),8);
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
const double time1=evaluate(1,oneWorker);
|
||||
const std::uint64_t hash1=hashSamples(oneWorker);
|
||||
std::vector<VoxelForgeCore::HeightfieldPointSample> eightWorkers(queries.size());
|
||||
const double time8=evaluate(8,eightWorkers);
|
||||
const std::uint64_t hash8=hashSamples(eightWorkers);
|
||||
std::vector<VoxelForgeCore::HeightfieldPointSample> sixteenWorkers(queries.size());
|
||||
const double time16=evaluate(16,sixteenWorkers);
|
||||
const std::uint64_t hash16=hashSamples(sixteenWorkers);
|
||||
const bool deterministic=hash1==hash8&&hash1==hash16;
|
||||
std::printf("[WorldAssemblerEvaluatorHash] samples=%zu W1=%016llx W8=%016llx W16=%016llx deterministic=%s\n",
|
||||
queries.size(),static_cast<unsigned long long>(hash1),
|
||||
static_cast<unsigned long long>(hash8),static_cast<unsigned long long>(hash16),
|
||||
deterministic?"true":"false");
|
||||
std::printf("[WorldAssemblerQueryTiming] samples=%zu native_W1_ms=%.3f native_W8_ms=%.3f native_W16_ms=%.3f scope=point_evaluation_only\n",
|
||||
queries.size(),time1,time8,time16);
|
||||
|
||||
double absoluteSum=0.0,maximumError=0.0;
|
||||
std::uint64_t signMismatches=0;
|
||||
const auto legacyStart=std::chrono::steady_clock::now();
|
||||
for(std::size_t i=0;i<queries.size();++i)
|
||||
{
|
||||
const float x=static_cast<float>(queries[i].x
|
||||
/static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
||||
const float y=static_cast<float>(queries[i].y
|
||||
/static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
||||
const float z=static_cast<float>(queries[i].z
|
||||
/static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
||||
const float legacyDensity=owner.Generator->GetDensityAt(x,y,z);
|
||||
const double error=std::abs(static_cast<double>(oneWorker[i].densitySubvoxels
|
||||
/static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel))-legacyDensity);
|
||||
absoluteSum+=error;maximumError=std::max(maximumError,error);
|
||||
if((oneWorker[i].densitySubvoxels<0)!=(legacyDensity<0.0f))
|
||||
++signMismatches;
|
||||
}
|
||||
const double legacyMs=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-legacyStart).count();
|
||||
std::printf("[WorldAssemblerLegacyCompare] samples=%zu mean_abs_density_error=%.4f max_abs_density_error=%.4f sign_mismatches=%llu legacy_point_queries_ms=%.3f status=MEASURED_NOT_PARITY_GATE\n",
|
||||
queries.size(),absoluteSum/queries.size(),maximumError,
|
||||
static_cast<unsigned long long>(signMismatches),legacyMs);
|
||||
const auto centerColumn=block.point(0,0,0);
|
||||
const int32 centerGroundVoxel=static_cast<int32>(std::llround(
|
||||
static_cast<double>(centerColumn.groundHeightSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
||||
VoxelForgeCore::World coreWorld(snapshot);
|
||||
VoxelForgeCore::ShapeBuildRequest coreBuild;
|
||||
coreBuild.minVoxel={-32,-32,centerGroundVoxel-32};
|
||||
coreBuild.maxVoxel={32,32,centerGroundVoxel+32};
|
||||
coreBuild.clipMinVoxel=coreBuild.minVoxel;
|
||||
coreBuild.clipMaxVoxel=coreBuild.maxVoxel;
|
||||
coreBuild.workerLimit=1;
|
||||
coreBuild.validatePassageRoutes=false;
|
||||
FStandaloneCoreScheduler scheduler;
|
||||
std::string coreError;
|
||||
const auto coreCompileStart=std::chrono::steady_clock::now();
|
||||
const bool coreCompiled=coreWorld.compile(coreBuild,scheduler,coreError);
|
||||
const double coreCompileMs=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-coreCompileStart).count();
|
||||
double coreQueryMaximumError=0.0;
|
||||
std::uint64_t coreQuerySamples=0;
|
||||
if(coreCompiled)
|
||||
{
|
||||
for(const std::int64_t dx:{-24ll,0ll,24ll})
|
||||
for(const std::int64_t dy:{-24ll,0ll,24ll})
|
||||
for(const std::int64_t dz:{-8ll,-1ll,0ll,1ll,8ll})
|
||||
{
|
||||
const std::int64_t xq=dx*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t yq=dy*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t zq=centerColumn.groundHeightSubvoxels
|
||||
+dz*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double expected=static_cast<double>(block.point(xq,yq,zq).densitySubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double actual=coreWorld.densityAt({static_cast<double>(xq)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||
static_cast<double>(yq)/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||
static_cast<double>(zq)/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel});
|
||||
coreQueryMaximumError=std::max(coreQueryMaximumError,std::abs(expected-actual));
|
||||
++coreQuerySamples;
|
||||
}
|
||||
}
|
||||
const bool coreQueryPass=coreCompiled&&coreQuerySamples==45&&coreQueryMaximumError<=1.0/256.0;
|
||||
std::printf("[WorldAssemblerCoreQuery] compiled=%s compile_ms=%.3f samples=%llu max_abs_error=%.8f render_meshes_generated=0 readiness=%s status=%s error=%s\n",
|
||||
coreCompiled?"true":"false",coreCompileMs,
|
||||
static_cast<unsigned long long>(coreQuerySamples),coreQueryMaximumError,
|
||||
coreQueryPass?"field_ready_without_meshes":"unavailable",coreQueryPass?"PASS":"FAIL",
|
||||
coreError.empty()?"none":coreError.c_str());
|
||||
if(!captureDirectory.empty())
|
||||
{
|
||||
constexpr int width=320,height=200;
|
||||
constexpr double spanVoxels=3200.0;
|
||||
const std::size_t columnCount=static_cast<std::size_t>(width)*height;
|
||||
std::vector<std::uint8_t> ownerPixels(columnCount*3,0);
|
||||
std::vector<std::uint8_t> nativePixels(ownerPixels.size(),0);
|
||||
std::vector<double> ownerHeights(columnCount,std::numeric_limits<double>::quiet_NaN());
|
||||
std::vector<double> nativeHeights(columnCount,0.0);
|
||||
double minimumHeight=std::numeric_limits<double>::max();
|
||||
double maximumHeight=std::numeric_limits<double>::lowest();
|
||||
std::uint64_t ownerColumns=0;
|
||||
for(int y=0;y<height;++y)
|
||||
for(int x=0;x<width;++x)
|
||||
{
|
||||
const double xVoxel=(static_cast<double>(x)/(width-1)-0.5)*spanVoxels;
|
||||
const double yVoxel=(static_cast<double>(y)/(height-1)-0.5)*spanVoxels;
|
||||
const std::size_t column=static_cast<std::size_t>(y)*width+x;
|
||||
float ownerGround=0.0f,ownerCap=0.0f;
|
||||
if(owner.Generator->GetSurfaceHeightAt(static_cast<float>(xVoxel),
|
||||
static_cast<float>(yVoxel),plan.topChunkZ,ownerGround,ownerCap))
|
||||
{
|
||||
if(std::isfinite(ownerGround))
|
||||
{
|
||||
ownerHeights[column]=ownerGround;
|
||||
minimumHeight=std::min(minimumHeight,static_cast<double>(ownerGround));
|
||||
maximumHeight=std::max(maximumHeight,static_cast<double>(ownerGround));
|
||||
++ownerColumns;
|
||||
}
|
||||
}
|
||||
const auto native=block.point(
|
||||
static_cast<std::int64_t>(std::llround(xVoxel*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)),
|
||||
static_cast<std::int64_t>(std::llround(yVoxel*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)),0);
|
||||
nativeHeights[column]=static_cast<double>(native.groundHeightSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
minimumHeight=std::min(minimumHeight,nativeHeights[column]);
|
||||
maximumHeight=std::max(maximumHeight,nativeHeights[column]);
|
||||
}
|
||||
const auto colorHeight=[&](double heightValue,std::vector<std::uint8_t>& pixels,std::size_t offset)
|
||||
{
|
||||
const double t=std::clamp((heightValue-minimumHeight)
|
||||
/std::max(1.0,maximumHeight-minimumHeight),0.0,1.0);
|
||||
const std::array<std::array<double,3>,4> stops={{{24,68,78},{61,128,75},
|
||||
{166,143,83},{232,229,209}}};
|
||||
const double scaled=t*3.0;
|
||||
const int segment=std::min(2,static_cast<int>(scaled));
|
||||
const double blend=scaled-segment;
|
||||
for(int channel=0;channel<3;++channel)
|
||||
pixels[offset+channel]=static_cast<std::uint8_t>(std::clamp(std::lround(
|
||||
stops[segment][channel]*(1.0-blend)+stops[segment+1][channel]*blend),0l,255l));
|
||||
};
|
||||
for(std::size_t column=0;column<columnCount;++column)
|
||||
{
|
||||
const std::size_t offset=column*3;
|
||||
if(std::isfinite(ownerHeights[column]))
|
||||
colorHeight(ownerHeights[column],ownerPixels,offset);
|
||||
colorHeight(nativeHeights[column],nativePixels,offset);
|
||||
}
|
||||
std::error_code directoryError;
|
||||
std::filesystem::create_directories(captureDirectory,directoryError);
|
||||
const auto writePpm=[&](const std::filesystem::path& path,const std::vector<std::uint8_t>& pixels)
|
||||
{
|
||||
std::ofstream output(path,std::ios::binary);
|
||||
if(!output)return false;
|
||||
output<<"P6\n"<<width<<' '<<height<<"\n255\n";
|
||||
output.write(reinterpret_cast<const char*>(pixels.data()),
|
||||
static_cast<std::streamsize>(pixels.size()));
|
||||
return static_cast<bool>(output);
|
||||
};
|
||||
const bool ownerSaved=!directoryError&&writePpm(
|
||||
std::filesystem::path(captureDirectory)/"owner-reference-height.ppm",ownerPixels);
|
||||
const bool nativeSaved=!directoryError&&writePpm(
|
||||
std::filesystem::path(captureDirectory)/"native-prototype-height.ppm",nativePixels);
|
||||
std::printf("[WorldAssemblerCapturePair] resolution=%dx%d xy_span_vox=%.0f height_range=[%.2f,%.2f] owner_columns=%llu owner_saved=%s native_saved=%s files=owner-reference-height.ppm,native-prototype-height.ppm status=%s\n",
|
||||
width,height,spanVoxels,minimumHeight,maximumHeight,static_cast<unsigned long long>(ownerColumns),
|
||||
ownerSaved?"true":"false",nativeSaved?"true":"false",
|
||||
ownerSaved&&nativeSaved?"PASS":"FAIL");
|
||||
}
|
||||
std::printf("[WorldAssemblerFullViewTiming] status=NOT_MEASURED_IN_AUDIT_MODE; capture_viewer_runs_report_separately\n");
|
||||
return bounds.passed&&deterministic&&coreQueryPass?0:11;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int Argc,char** Argv)
|
||||
@@ -7805,6 +8159,8 @@ int main(int Argc,char** Argv)
|
||||
int32 MaxClipLevelOverride=-1;
|
||||
int32 ShapeCompareLevel=-1,ShapeCompareTileLimit=0;
|
||||
std::string ShapeCompareArea="unlabeled";
|
||||
std::string WorldAssemblyCaptureDir;
|
||||
std::string WorldAssemblyManifestOutputPath;
|
||||
bool bStreamBenchmark=false;
|
||||
bool bStreamPipeline=false;
|
||||
bool bProfileDetailBand=false;
|
||||
@@ -7848,6 +8204,16 @@ int main(int Argc,char** Argv)
|
||||
else if(Arg=="--parity-only")App.bParityOnly=true;
|
||||
else if(Arg=="--density-hash")App.bDensityHash=true;
|
||||
else if(Arg=="--shape-world")App.bShapeWorld=true;
|
||||
else if(Arg=="--world-assembler-prototype")
|
||||
{App.bWorldAssemblerPrototype=true;App.bShapeWorld=true;}
|
||||
else if(Arg=="--world-assembler-audit")
|
||||
{App.bWorldAssemblerPrototype=true;App.bWorldAssemblerAuditOnly=true;}
|
||||
else if(Arg.starts_with("--world-assembler-capture-dir="))
|
||||
WorldAssemblyCaptureDir=std::string(Arg.substr(
|
||||
std::string_view("--world-assembler-capture-dir=").size()));
|
||||
else if(Arg.starts_with("--world-assembler-manifest-output="))
|
||||
WorldAssemblyManifestOutputPath=std::string(Arg.substr(
|
||||
std::string_view("--world-assembler-manifest-output=").size()));
|
||||
else if(Arg=="--gpu-far")
|
||||
App.bGpuFarEnabled=true,App.bGpuFarShaderPrewarm=true,App.bShapeWorld=true,App.bForceGenerate=true,
|
||||
App.bPresentCpuFrameBeforeGpu=true,App.bHoldCpuFrameDuringGpuWarmup=true;
|
||||
@@ -8057,6 +8423,19 @@ int main(int Argc,char** Argv)
|
||||
std::printf("Standalone max clip level override: exported=%d -> max_level=%d.\n",
|
||||
ExportMaxClipLevel,MaxClipLevelOverride);
|
||||
}
|
||||
if(App.bWorldAssemblerPrototype
|
||||
&&!AttachRound1WorldAssemblyPrototype(*App.World,App.OwnerJsonRoot,Error,
|
||||
WorldAssemblyManifestOutputPath))
|
||||
{std::fprintf(stderr,"World assembler prototype setup failed: %s\n",Error.c_str());return 2;}
|
||||
if(App.bWorldAssemblerAuditOnly)
|
||||
{
|
||||
App.Snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(App.OwnerJsonRoot,
|
||||
App.Seed,App.ClipRadius,App.MaxClipLevelOverride,App.bRoundTunnels,
|
||||
App.bRoundTunnelEnhanced,Error);
|
||||
if(!App.Snapshot)
|
||||
{std::fprintf(stderr,"World assembler snapshot setup failed: %s\n",Error.c_str());return 2;}
|
||||
return RunWorldAssemblerAudit(*App.World,App.Snapshot,WorldAssemblyCaptureDir);
|
||||
}
|
||||
const bool bNeedFarShapeWorld=bStreamBenchmark
|
||||
?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels)
|
||||
:(App.bShapeCompare?ShapeCompareLevel>=App.World->FullResClipLevels
|
||||
|
||||
Reference in new issue
Block a user