wip(assembler): M3 round 3 -- separated islands + Unreal island route (owner build 18:46)
Islands read as separate (91.89% open sky at mid-height), underside cavity closed, rim teeth were capture shading. Island manifest import + BuildHost proof passed on the pre-cache tree; M1 SurfaceWorld and M2 V1/V2 gates passed. Known open: final XY-column cache compiled in the owner build but is unverified; island LOD0 p95 ~155 ms misses the 133 ms sprint deadline (legacy ~43 ms); the island manifest only fits the BuildHost fixture layout (owner's world: Core init fails, empty world) -> round 4 builds plans inside Unreal. 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
4afea7c802
commit
48fa028086
37 files changed
+3788
-107
No files matched your search
@@ -8,6 +8,7 @@
|
||||
#include <charconv>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
@@ -403,9 +404,91 @@ namespace world_assembly_detail
|
||||
HashWord(hash, manifest.schemaVersion, 4);
|
||||
HashWord(hash, manifest.recipe.contentHash, 8);
|
||||
HashWord(hash, manifest.plan.contentHash, 8);
|
||||
// Preserve the historical SurfaceWorld manifest digest when no island plan is attached.
|
||||
if (!manifest.floatingIslands.empty())
|
||||
{
|
||||
HashWord(hash, 0x464c4f415449534cull, 8);
|
||||
HashWord(hash, manifest.floatingIslands.size(), 4);
|
||||
for (const auto& islands : manifest.floatingIslands)
|
||||
{
|
||||
HashWord(hash, static_cast<std::uint32_t>(islands.strateIndex), 4);
|
||||
HashWord(hash, static_cast<std::uint32_t>(islands.topChunkZ), 4);
|
||||
HashWord(hash, static_cast<std::uint32_t>(islands.bottomChunkZ), 4);
|
||||
HashWord(hash, islands.manifest.contentHash, 8);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline void AppendJsonString(std::ostringstream& out, std::string_view value)
|
||||
{
|
||||
out << '"';
|
||||
for (const unsigned char ch : value)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case '"': out << "\\\""; break;
|
||||
case '\\': out << "\\\\"; break;
|
||||
case '\b': out << "\\b"; break;
|
||||
case '\f': out << "\\f"; break;
|
||||
case '\n': out << "\\n"; break;
|
||||
case '\r': out << "\\r"; break;
|
||||
case '\t': out << "\\t"; break;
|
||||
default:
|
||||
if (ch < 0x20) out << "\\u00" << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << static_cast<unsigned int>(ch) << std::dec;
|
||||
else out << static_cast<char>(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
out << '"';
|
||||
}
|
||||
|
||||
inline void AppendJsonValue(std::ostringstream& out, const VFJson::Value& value)
|
||||
{
|
||||
if (std::holds_alternative<std::nullptr_t>(value.Data)) out << "null";
|
||||
else if (const auto* boolean = std::get_if<bool>(&value.Data)) out << (*boolean ? "true" : "false");
|
||||
else if (const auto* number = std::get_if<double>(&value.Data))
|
||||
{
|
||||
char buffer[64];
|
||||
const auto result = std::to_chars(buffer, buffer + sizeof(buffer), *number,
|
||||
std::chars_format::general, std::numeric_limits<double>::max_digits10);
|
||||
if (result.ec == std::errc{}) out.write(buffer, result.ptr - buffer);
|
||||
else out << '0';
|
||||
}
|
||||
else if (const auto* string = std::get_if<std::string>(&value.Data)) AppendJsonString(out, *string);
|
||||
else if (const auto* array = std::get_if<VFJson::Value::Array>(&value.Data))
|
||||
{
|
||||
out << '[';
|
||||
for (std::size_t i = 0; i < array->size(); ++i)
|
||||
{ if (i) out << ','; AppendJsonValue(out, (*array)[i]); }
|
||||
out << ']';
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& object = std::get<VFJson::Value::Object>(value.Data);
|
||||
std::vector<std::string_view> keys;
|
||||
keys.reserve(object.size());
|
||||
for (const auto& entry : object) keys.push_back(entry.first);
|
||||
std::sort(keys.begin(), keys.end());
|
||||
out << '{';
|
||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
||||
{
|
||||
if (i) out << ',';
|
||||
AppendJsonString(out, keys[i]); out << ':';
|
||||
AppendJsonValue(out, object.at(std::string(keys[i])));
|
||||
}
|
||||
out << '}';
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string JsonText(const VFJson::Value& value)
|
||||
{
|
||||
std::ostringstream out;
|
||||
AppendJsonValue(out, value);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline bool ParseRecipe(const VFJson::Value& value, WorldAssemblyRecipeV1& recipe,
|
||||
std::string& error)
|
||||
{
|
||||
@@ -566,6 +649,26 @@ namespace world_assembly_detail
|
||||
if (error.empty()) error = "world assembly manifest is missing a required field";
|
||||
return std::nullopt;
|
||||
}
|
||||
const VFJson::Value& islandPlans = value.Get("floating_islands");
|
||||
if (!std::holds_alternative<std::nullptr_t>(islandPlans.Data))
|
||||
{
|
||||
if (!islandPlans.IsArray() || islandPlans.AsArray().size() > MaxStrateCount)
|
||||
{ error = "world assembly floating-island plans must be an array of at most sixty-four entries"; return std::nullopt; }
|
||||
for (const VFJson::Value& islandValue : islandPlans.AsArray())
|
||||
{
|
||||
WorldAssemblyFloatingIslandPlanV1 entry;
|
||||
if (!islandValue.IsObject()
|
||||
|| !ReadI32(islandValue.Get("strate_index"), entry.strateIndex)
|
||||
|| !ReadI32(islandValue.Get("top_chunk_z"), entry.topChunkZ)
|
||||
|| !ReadI32(islandValue.Get("bottom_chunk_z"), entry.bottomChunkZ))
|
||||
{ error = "world assembly floating-island slot is missing its resolved strate bounds"; return std::nullopt; }
|
||||
const std::string nestedJson = JsonText(islandValue.Get("manifest"));
|
||||
auto parsedIsland = FloatingIslandManifestV1::FromJson(nestedJson, error);
|
||||
if (!parsedIsland) return std::nullopt;
|
||||
entry.manifest = std::move(*parsedIsland);
|
||||
manifest.floatingIslands.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
if (!manifest.validate(error)) return std::nullopt;
|
||||
return manifest;
|
||||
}
|
||||
@@ -1292,8 +1395,10 @@ bool WorldAssemblyManifest::finalize(std::string& error)
|
||||
if (schemaVersion != WorldAssemblySchemaVersion || plan.schemaVersion != WorldAssemblySchemaVersion
|
||||
|| !world_assembly_detail::ValidateRecipe(recipe, error))
|
||||
{ if (error.empty()) error = "world assembly manifest or plan version is unsupported"; return false; }
|
||||
if (plan.strates.empty() || plan.strates.size() > world_assembly_detail::MaxStrateCount)
|
||||
{ error = "world assembly plan must contain one to sixty-four finite strates"; return false; }
|
||||
if ((plan.strates.empty() && floatingIslands.empty())
|
||||
|| plan.strates.size() > world_assembly_detail::MaxStrateCount
|
||||
|| floatingIslands.size() > world_assembly_detail::MaxStrateCount)
|
||||
{ error = "world assembly must contain SurfaceWorld or floating-island plans within the sixty-four-strate limit"; return false; }
|
||||
std::vector<std::int32_t> strateIds;
|
||||
for (auto& stratum : plan.strates)
|
||||
{
|
||||
@@ -1323,6 +1428,25 @@ bool WorldAssemblyManifest::finalize(std::string& error)
|
||||
strateIds.push_back(stratum.strateIndex);
|
||||
stratum.contentHash = world_assembly_detail::HashStratum(stratum);
|
||||
}
|
||||
std::vector<std::int32_t> islandStrateIds;
|
||||
for (auto& islands : floatingIslands)
|
||||
{
|
||||
if (islands.strateIndex < 0 || islands.topChunkZ < islands.bottomChunkZ
|
||||
|| std::find(islandStrateIds.begin(), islandStrateIds.end(), islands.strateIndex) != islandStrateIds.end())
|
||||
{ error = "world assembly contains invalid or duplicate floating-island strate bounds"; return false; }
|
||||
if (!islands.manifest.validate(error)) return false;
|
||||
if (islands.manifest.recipe.seed != recipe.seed)
|
||||
{ error = "floating-island seed does not match the world assembly season seed"; return false; }
|
||||
constexpr std::int64_t CoreVoxelsPerLegacyChunk = 16;
|
||||
const std::int64_t bottomQ = static_cast<std::int64_t>(islands.bottomChunkZ)
|
||||
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t topQ = (static_cast<std::int64_t>(islands.topChunkZ) + 1)
|
||||
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
|
||||
if (islands.manifest.recipe.layoutMinimumZSubvoxels < bottomQ
|
||||
|| islands.manifest.recipe.layoutMaximumZSubvoxels > topQ)
|
||||
{ error = "floating-island recipe layout escapes the resolved 12.5 cm legacy strate bounds"; return false; }
|
||||
islandStrateIds.push_back(islands.strateIndex);
|
||||
}
|
||||
recipe.contentHash = world_assembly_detail::HashRecipe(recipe);
|
||||
plan.contentHash = world_assembly_detail::HashPlan(plan);
|
||||
contentHash = world_assembly_detail::HashManifest(*this);
|
||||
@@ -1337,13 +1461,19 @@ bool WorldAssemblyManifest::validate(std::string& error) const
|
||||
const std::uint64_t expectedManifest = contentHash;
|
||||
std::vector<std::uint64_t> expectedStrates;
|
||||
for (const auto& stratum : plan.strates) expectedStrates.push_back(stratum.contentHash);
|
||||
std::vector<std::uint64_t> expectedIslandManifests;
|
||||
for (const auto& islands : floatingIslands) expectedIslandManifests.push_back(islands.manifest.contentHash);
|
||||
if (!copy.finalize(error)) return false;
|
||||
if (expectedRecipe != copy.recipe.contentHash || expectedPlan != copy.plan.contentHash
|
||||
|| expectedManifest != copy.contentHash || expectedStrates.size() != copy.plan.strates.size())
|
||||
|| expectedManifest != copy.contentHash || expectedStrates.size() != copy.plan.strates.size()
|
||||
|| expectedIslandManifests.size() != copy.floatingIslands.size())
|
||||
{ error = "world assembly recipe, plan or manifest content hash does not match its fields"; return false; }
|
||||
for (std::size_t i = 0; i < expectedStrates.size(); ++i)
|
||||
if (expectedStrates[i] != copy.plan.strates[i].contentHash)
|
||||
{ error = "world assembly stratum content hash does not match its fields"; return false; }
|
||||
for (std::size_t i = 0; i < expectedIslandManifests.size(); ++i)
|
||||
if (expectedIslandManifests[i] != copy.floatingIslands[i].manifest.contentHash)
|
||||
{ error = "world assembly floating-island content hash does not match its fields"; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1474,7 +1604,23 @@ std::string WorldAssemblyManifest::ToJson() const
|
||||
}
|
||||
out << "]}";
|
||||
}
|
||||
out << "]}}";
|
||||
if (value.floatingIslands.empty()) out << "]}}";
|
||||
else
|
||||
{
|
||||
out << "]},\"floating_islands\":[";
|
||||
for (std::size_t i = 0; i < value.floatingIslands.size(); ++i)
|
||||
{
|
||||
const auto& islands = value.floatingIslands[i];
|
||||
if (i) out << ',';
|
||||
const std::string nested = islands.manifest.ToJson();
|
||||
if (nested.empty()) return {};
|
||||
out << "{\"strate_index\":" << islands.strateIndex
|
||||
<< ",\"top_chunk_z\":" << islands.topChunkZ
|
||||
<< ",\"bottom_chunk_z\":" << islands.bottomChunkZ
|
||||
<< ",\"manifest\":" << nested << '}';
|
||||
}
|
||||
out << "]}";
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user