From f71143fe990333f502504dd9720a3b11e05c3327 Mon Sep 17 00:00:00 2001 From: Ael Date: Tue, 4 Aug 2026 13:27:28 +0200 Subject: [PATCH 1/6] feat(atlas): add private Greater Realm candidate generator --- .gitignore | 14 + .../design/greater-realm-natural-continent.md | 244 + docs/evidence/greater-realm/README.md | 23 + .../greater-realm-private-generation.md | 227 + package-lock.json | 2 +- package.json | 14 +- .../greater-realm-candidate-generator.ts | 6678 +++++++++++++++++ .../atlas/greater-realm-candidate-package.ts | 1995 +++++ .../greater-realm-candidate-rejection.ts | 57 + scripts/atlas/greater-realm-cli.ts | 1970 +++++ scripts/atlas/greater-realm-contracts.ts | 203 + scripts/atlas/greater-realm-geomorphology.ts | 1056 +++ scripts/atlas/greater-realm-git.ts | 263 + .../atlas/greater-realm-legacy-lowlands.ts | 542 ++ .../atlas/greater-realm-private-markers.mjs | 38 + scripts/atlas/greater-realm-private-seed.ts | 65 + .../atlas/greater-realm-private-workspace.ts | 1381 ++++ .../atlas/greater-realm-sanitized-review.ts | 724 ++ scripts/atlas/greater-realm-terrain.ts | 893 +++ .../greater-realm-toolchain-bootstrap.mjs | 786 ++ .../atlas/greater-realm-toolchain-lock.json | 151 + scripts/atlas/greater-realm-topography.ts | 702 ++ scripts/atlas/verify-public-boundary.mjs | 1441 ++++ scripts/qa-observer/local-vite-fs-deny.mjs | 218 +- scripts/qa/agent.mjs | 2 +- tests/farcasterMiniAppContract.test.ts | 2 +- tests/greaterRealmAdvancedInvariants.test.ts | 1382 ++++ tests/greaterRealmCandidateGenerator.test.ts | 759 ++ tests/greaterRealmCandidatePackage.test.ts | 1110 +++ tests/greaterRealmCandidateRejection.test.ts | 71 + tests/greaterRealmCliSecurity.test.ts | 427 ++ tests/greaterRealmGeomorphology.test.ts | 161 + tests/greaterRealmLegacyLowlands.test.ts | 158 + tests/greaterRealmOrdinaryYield.test.ts | 37 + tests/greaterRealmPrivateBoundary.test.ts | 1132 +++ tests/greaterRealmPrivateSeed.test.ts | 54 + tests/greaterRealmSanitizedReview.test.ts | 300 + tests/greaterRealmTerrain.test.ts | 258 + tests/greaterRealmToolchainSecurity.test.ts | 321 + tests/greaterRealmTopography.test.ts | 218 + tests/localVitePublicBoundary.test.ts | 120 +- tests/renderedWebglBrowserProbe.test.ts | 12 +- 42 files changed, 26192 insertions(+), 19 deletions(-) create mode 100644 docs/design/greater-realm-natural-continent.md create mode 100644 docs/evidence/greater-realm/README.md create mode 100644 docs/security/greater-realm-private-generation.md create mode 100644 scripts/atlas/greater-realm-candidate-generator.ts create mode 100644 scripts/atlas/greater-realm-candidate-package.ts create mode 100644 scripts/atlas/greater-realm-candidate-rejection.ts create mode 100644 scripts/atlas/greater-realm-cli.ts create mode 100644 scripts/atlas/greater-realm-contracts.ts create mode 100644 scripts/atlas/greater-realm-geomorphology.ts create mode 100644 scripts/atlas/greater-realm-git.ts create mode 100644 scripts/atlas/greater-realm-legacy-lowlands.ts create mode 100644 scripts/atlas/greater-realm-private-markers.mjs create mode 100644 scripts/atlas/greater-realm-private-seed.ts create mode 100644 scripts/atlas/greater-realm-private-workspace.ts create mode 100644 scripts/atlas/greater-realm-sanitized-review.ts create mode 100644 scripts/atlas/greater-realm-terrain.ts create mode 100644 scripts/atlas/greater-realm-toolchain-bootstrap.mjs create mode 100644 scripts/atlas/greater-realm-toolchain-lock.json create mode 100644 scripts/atlas/greater-realm-topography.ts create mode 100644 scripts/atlas/verify-public-boundary.mjs create mode 100644 tests/greaterRealmAdvancedInvariants.test.ts create mode 100644 tests/greaterRealmCandidateGenerator.test.ts create mode 100644 tests/greaterRealmCandidatePackage.test.ts create mode 100644 tests/greaterRealmCandidateRejection.test.ts create mode 100644 tests/greaterRealmCliSecurity.test.ts create mode 100644 tests/greaterRealmGeomorphology.test.ts create mode 100644 tests/greaterRealmLegacyLowlands.test.ts create mode 100644 tests/greaterRealmOrdinaryYield.test.ts create mode 100644 tests/greaterRealmPrivateBoundary.test.ts create mode 100644 tests/greaterRealmPrivateSeed.test.ts create mode 100644 tests/greaterRealmSanitizedReview.test.ts create mode 100644 tests/greaterRealmTerrain.test.ts create mode 100644 tests/greaterRealmToolchainSecurity.test.ts create mode 100644 tests/greaterRealmTopography.test.ts diff --git a/.gitignore b/.gitignore index ad3fbacd..1de7bffd 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,17 @@ secrets.json *.tar.gz *.tgz *.7z + +# Greater Realm owner-review material is generated outside the repository. +/.warpkeep-private/ +/greater-realm-private/ +seed.bin +batch-seed.bin +manifest.private.json +batch.private.json +selection.private.json +shortlist.private.json +private-preview-* +*.wkgr-atlas +*.wkgr-checkpoint +*.wkgr-private diff --git a/docs/design/greater-realm-natural-continent.md b/docs/design/greater-realm-natural-continent.md new file mode 100644 index 00000000..c4c9b8a6 --- /dev/null +++ b/docs/design/greater-realm-natural-continent.md @@ -0,0 +1,244 @@ +# Greater Realm natural-continent candidate design + +Status: candidate-generation design only + +Atlas: `GENESIS_001_GREATER_REALM` + +Generator: `greater-realm-v2-natural-continent-pr-a.3` +Production state: unchanged + +This document defines the offline, owner-reviewed candidate stage for a future +Greater Realm. It does not add a database table, browser route, renderer, +feature flag, migration, seed reducer, production record, or active mechanic. +No candidate is selected by this pull request. + +## Immutable starting point + +The deployed `GENESIS_001` Lowlands remains the authority for its exact 10,000 +cells, 100 castle slots, water layout, resource sites, forest layout, routes, +and local keys. Candidate generation treats that world as a locked patch. A +candidate may choose one of six axial rotations and a global translation for +future presentation, but it may not rewrite any locked local coordinate or +catalog entry. + +The future global key contract is collision-free and region-qualified: + +```text +:, +``` + +The exact Lowlands transform is private candidate data. A later bridge must +keep current gameplay in local coordinates and prove a reversible mapping +before any atlas record can be activated. + +## Fixed strategic contract + +An eligible candidate has 100,000–150,000 active atlas cells and exactly ten +strategic regions: + +| Tier | Count | Working regions | Aggregate target | +| --- | ---: | --- | ---: | +| I | 6 | The Hegemony Lowlands, Frostmere Reach, Sunscar Expanse, Mirefen Delta, Stonewake Isles, Emberwood March | 68%–74% | +| II | 3 | Crownwood March, Ironveil March, Glasswater March | 22%–27% | +| III | 1 | Throneheart | 3%–6% | + +Tier III must be the smallest region by total cells and passable land. Each +Tier II region has two Tier I neighbours and one Tier III neighbour. The +physical graph contains exactly 18 sealed gates: two for each of the six +Tier-I/Tier-II relationships and two for each of the three +Tier-II/Tier-III relationships. Closed gates are impassable and grant no +combat, ownership, reward, or travel behavior. + +The six Tier I regions reserve future capacity for 100 castles each. Only the +existing Lowlands 100 are active. The other 500 candidate slots are private, +dormant suitability results and must not enter the current castle-slot table. +New slots are dry, at least three cells from a sealed gate, live inside a +same-region passable component of at least 200 cells, and remain at least five +hexes from every previously accepted slot, including the deployed Lowlands. + +## Deterministic generation + +Authority decisions use canonical cell order, integer or fixed-point fields, +stable complete tie-breakers, and counter-addressed random channels. A sample +is a pure function of the private candidate seed, generator version, stage, +axial coordinate, and sample ordinal. There is no mutable random stream and no +`Math.random` in generation authority. + +Each candidate runs independently through these stage families: + +1. Build a larger private axial canvas, 7–12 separated pseudo-tectonic domains, + and 3–8 irregular subduction-style island arcs with crust, age, rock + resistance, buoyancy, volcanic potential, and integer motion vectors. +2. Derive continentalness, basin structure, convergent uplift, rifts, transform + scarps, volcanic relief, and independent macro/meso/local relief channels. +3. Apply synchronous, material-conserving thermal shaping. +4. Derive a connected active atlas mask from geography plus a variable + deep-ocean/fog buffer. Inactive canvas cells are discarded, not seeded. +5. Place the locked Lowlands reserve, then run a real geomorphology stage before + the final fluvial pass. That stage uses preliminary drainage and climate to + apply bounded glacial erosion and moraines, arid wadis and aeolian shaping, + volcanic/caldera relief tied to tectonic evidence, and coherent beach, + cliff, delta, and fjord shaping. It preserves the Lowlands reserve exactly + and proves a material budget for its signed process deltas. +6. Run stable Priority-Flood depression handling on the six-connected graph, + route flats toward legal outlets, prove a drainage DAG, accumulate + discharge, and apply bounded stream-power-like incision and sediment + transport. Re-run thermal relaxation and hydrology, deposit the routed + sediment onto that relaxed surface, and run final hydrology before + reconciling the immutable Lowlands topography and surface. Incision and + deposition never alter protected Lowlands cells. +7. Derive paired topography and visual-biome authority from the same reconciled + elevation, routed flow, geology, climate, water, region, and geomorphology + process fields. The paired result includes slope, aspect, profile/plan + curvature, wetness, exposure, coast/freshwater distance, watersheds, ridges, + temperature, moisture, landforms, and biome classes; visual classifications + cannot drift away from the process evidence that produced the terrain. +8. Grow natural geographic basins, bind the working region identities by + climate/process character, assign the fixed strategic graph, and align tier + barriers and gate saddles with coherent highlands rather than radial bands. +9. Choose dormant castle suitability, potential sites, private chunks and + topography patches, and aggregate quality metrics. + +`sedimentDepth` is the exact non-negative deposit added to the relaxed fluvial +surface before final routing. The generator proves cell by cell that the final +fluvial elevation equals the relaxed base plus this depth, proves zero sediment +inside protected Lowlands cells, and conserves eroded material as deposited +plus exported sediment. PR A keeps this private generation evidence out of all +runtime and persistence schemas. + +Sea level and mask parameters are fixed before a candidate attempt. A result +outside the approved cell range is rejected; the generator never pads, trims, +or adds filler to reach a requested count. + +Aggregate land and water shares describe the continent's topographic +footprint at sea level. Rivers, streams, and enclosed surface-water overlays +remain features of that footprint; they do not silently shrink the continent +when a hydrology threshold changes. + +The design adapts practical ideas from research on +[procedural tectonic structure](https://onlinelibrary.wiley.com/doi/10.1111/cgf.13614), +[coupled uplift and fluvial erosion](https://onlinelibrary.wiley.com/doi/10.1111/cgf.12820), +[Priority-Flood drainage](https://doi.org/10.1016/j.cageo.2013.04.024), and +[tile-based erosion evaluation](https://arxiv.org/abs/2210.14496). It does not +copy third-party code, data, maps, art, labels, or balance tables. + +## Candidate hard gates + +A candidate is ineligible if any of these proofs fail: + +- active count is outside 100,000–150,000; +- the active mask is disconnected, exposes the private canvas, or has an + obvious disc, hexagon, square, long straight cutoff, or filler boundary; +- land does not retain a meaningful deep-ocean/fog buffer; +- the topographic land mask does not contain 2–4 major landmasses and 3–8 + large islands; +- region count, tier ratios, region balance, fixed adjacency, 18 gates, or 600 + total castle capacity differs from the contract; +- a Tier-II parent lacks a dry outer-frontier anchor joined to its dry inner + anchor. The generator first preserves an already-sound natural partition; + only a parent that would miss the Tier-I frontier activates the deterministic + dual-anchor spine repair; +- a closed tier barrier has an ungated land bypass; +- opening all declared gate endpoints exposes any cross-tier edge other than + the exact 18 recorded physical corridors; +- either side of a sealed gate lacks two internally vertex-disjoint dry + approaches, beginning at distinct endpoint neighbours and terminating in + the same independently measured vertex-biconnected regional core of at least + 64 cells. Complete Tarjan blocks are retained; when blocks share an + articulation, only the deterministic largest owner can retain that vertex, + so an incomplete path fragment cannot masquerade as a robust core; +- the completed geological band differs from its independently replayed + same-tier distance field: two cells per side at the Outer Crown (four-cell + local normal) and three per side at the Inner Throne (six-cell local normal). + Curved or intersecting ranges may create a wider longitudinal massif, so + bounded 4–8-cell private normal witnesses cover every strategic region-pair + boundary rather than mislabelling a bend as thickness. Oceans and lakes may + contribute explicit natural-barrier cells. Every final impassable crest cell + must lie inside the band, and the cell graph must remain sealed outside the + exact gate corridors; +- the largest passable component covers less than 80% of a non-Stonewake Tier I + region, 55% of intentionally archipelagic Stonewake Isles, 85% of a Tier II + region, or 90% of Tier III; +- passable-region fragmentation, boundary density, tendrils, radial tier + agreement, or radial Tier-I boundary alignment exceeds its fixed limit; +- the measured final surface is outside 4–6 major ocean/sea bodies, 48–72 + major river networks, 120–240 minor stream heads, or 48–96 lakes; +- a flow cycle, uphill routing edge, unexplained inland sink, or inconsistent + lake exists. Every connected lake body must have one exact filled-surface + elevation and a legal spill or sea-level outlet; adjacent bodies at different + filled elevations must not be merged for counting or proof; +- derived topography is degenerate, a region misses its tier-specific biome + diversity floor, a Tier-I biome exceeds 55% of that region's land, or an + incompatible hot-arid/frozen visual adjacency survives classification; +- geomorphology changes the Lowlands reserve, violates its exact material + budget or climate/tectonic/coastal compatibility, or omits the required + glacial, arid, volcanic, and coastal process evidence; +- the locked Lowlands patch or any protected catalog differs; +- castle candidates lack local passability, spacing, or clearance; +- integer range, stage-digest, deterministic replay, package-integrity, or private + boundary validation fails. + +For this offline topology proof, river and minor-stream corridors are fordable +route surfaces; oceans and lakes are not. This does not activate movement, +bridges, ferries, or any cross-region mechanic. Castle sites and sealed gate +endpoints themselves must still be dry. + +Quality is a vector, not an automatic winner. After each complete private +package has been regenerated and byte-for-byte verified, the owner-only +comparison joins its public aggregate evidence to a coordinate-free private +metric vector. That vector spans outer-boundary/coastal artifacts, passable +region coherence and route fragmentation, throne-route clearance, chunk +population balance, geological and landform alignment, climate/coastal +compatibility, hydrology, and biome diversity/balance. Raw coordinates, seeds, +transforms, hidden-site identities, package paths, and digests never enter the +shortlist. The tooling may produce a diverse shortlist, but only the owner can +select a candidate. + +## Private and public outputs + +The generator workspace lives outside the repository in an owner-only +directory. It contains marked, type-tagged seed envelopes; exact cells, +coordinates and transforms; geology and geomorphology process fields; paired +topography/biome authority; regions, gates, slots and sites; stage digests; +exact chunk and topography-patch manifests; packages; and six marked private +previews. Chunk manifests bind the canonical cell-index set and complete field +payload, while each referenced topography patch separately binds its process +and derived-field inventory and payload. These artifacts are never committed, +served, copied to `public/`, attached to a pull request, or printed to logs. + +The only candidate artifact suitable for Git is a newly constructed sanitized +aggregate report. It may contain: + +- an independently random opaque review handle; +- exact total cell, land, and water counts; +- tier totals and broad per-tier region size ranges; +- aggregate water, geology, topography, hydrology, naturalness, gate, castle, + runtime, and memory metrics; +- boolean hard-proof results; +- selection status, which remains `pending` in this pull request. + +It must not contain coordinates, named exact hidden-region sizes, transforms, +seeds, seed digests, private/layout/stage/package digests, chunk keys, exact +hidden sites, maps, screenshots, previews, paths, or reconstructive data. + +## Review and future pull requests + +The candidate batch contains 8–16 eligible worlds; twelve is the preferred +review set. Private owner review includes comparable silhouette, hillshade, +biome, hydrology, topology/fog, and mountain/gate views. The comparison tool +deterministically produces an unranked, diverse three-to-five-candidate +shortlist using Pareto/vector separation across verified private package +aggregates as well as the sanitized public metrics. It records `pending`, +carries no recommendation, and has no automatic-selection side effect. The +private shortlist stores only opaque candidate handles, objective directions, +and hard-constraint labels—never metric values or reconstructive material. No +scalar score makes the final choice. + +Only an explicit owner approval may be recorded as a private selection receipt. +After that approval, a separate pull request may bind the selected private +package to an additive, inactive SpacetimeDB schema. Later pull requests +separately cover the Lowlands bridge, fog-safe atlas renderer, caller-bounded +streaming, visible-region assets, guarded seeding, and an explicitly authorized +production release. Selection does not authorize any of those steps. PR A +changes no schema, runtime, renderer, public generated asset, deployment, or +production record; the current Lowlands remains the only active world. diff --git a/docs/evidence/greater-realm/README.md b/docs/evidence/greater-realm/README.md new file mode 100644 index 00000000..8e3315e9 --- /dev/null +++ b/docs/evidence/greater-realm/README.md @@ -0,0 +1,23 @@ +# Greater Realm candidate evidence + +This directory accepts only canonical aggregate review reports produced by +`atlas:export-sanitized-review` and verified by +`atlas:verify-sanitized-review`. + +Allowed evidence is newly constructed from an exact allowlist and contains only +opaque random candidate handles, aggregate counts, broad size ranges, +topography/biome/geomorphology/hydrology quality metrics, boolean proof +outcomes, rounded performance figures, selection status, and one report digest. +It must never contain seeds or seed digests, generation-canvas coordinates, +cell or chunk payloads, process fields, topography patches, +layout/stage/package/private digests, exact region geometry, transforms, gates, +sites, candidate previews, screenshots, or paths to the owner-review workspace. + +The owner-only source packages, marked seed envelopes, exact chunk/topography +authority bindings, and six-map preview sets live outside every Git worktree +under a mode-0700 private workspace. Private comparison may create a +deterministic, unranked and diverse three-to-five-candidate shortlist, but it +cannot select a world. A candidate remains `pending` until the owner records a +separate explicit approval; no report in this directory is a selection, +activation, schema/runtime change, deployment instruction, or production +record. PR A leaves the deployed Lowlands untouched. diff --git a/docs/security/greater-realm-private-generation.md b/docs/security/greater-realm-private-generation.md new file mode 100644 index 00000000..0ec63e06 --- /dev/null +++ b/docs/security/greater-realm-private-generation.md @@ -0,0 +1,227 @@ +# Greater Realm private-generation boundary + +The Greater Realm generator handles unrevealed world geometry. Its exact +outputs are security-sensitive game authority even before a production schema +exists. This document applies to the candidate-generation pull request only. + +## Assets and trust boundaries + +| Data | Classification | Allowed location | +| --- | --- | --- | +| Root/candidate seed bytes | Private authority | Owner-only workspace outside the repository | +| Exact canvas, cells, geology/geomorphology processes, paired topography/biomes, regions, gates, slots, sites, fields, transforms | Private authority | Owner-only workspace outside the repository | +| Chunk/topography-patch manifests, package/layout/stage digests, toolchain records, and inventories | Private operational data | Owner-only workspace outside the repository | +| Candidate maps and contact sheets | Private owner-review data | Owner-only workspace outside the repository | +| Aggregate allowlisted candidate metrics | Public sanitized evidence | `docs/evidence/greater-realm/` after strict validation | +| Generator source and synthetic fixtures | Public source | `scripts/atlas/` and `tests/` | + +The browser, Vite `public/` tree, production `dist/`, source maps, Git history, +pull-request comments, CI artifacts, logs, and public SpacetimeDB tables are not +private storage. + +## Local workspace controls + +The tool accepts no secret through arguments or environment variables. It +creates a 256-bit candidate root internally. Secrets are never +included in an error, path, log, metric, preview watermark, or public handle. + +The private workspace must: + +- resolve to an absolute canonical directory outside the repository; +- be owned by the current user with directory mode `0700` and file mode `0600`; +- reject symbolic links and special files in every inspected path component; +- use an exclusive operation lock; +- publish bounded files atomically without following a replacement leaf; +- assemble an entire review batch in an opaque owner-only staging directory, + attest the complete tree, atomically claim a no-replace owner-only envelope, + move the tree beneath an opaque payload name, and expose that payload only + through an atomically linked commit marker; logical readers reject every + claim or envelope that is not fully committed, while failed cleanup removes + only identity-pinned entries and otherwise leaves the claim fail-closed; +- store batch and candidate seeds in strict, type-tagged private envelopes so + renamed seed files retain a scan-visible private marker while derivation + continues to use only the extracted 32-byte payload; +- bind every package to generator version, source commit, parameters, seed + identity, stage digests, and exact file inventory; +- bind every private chunk to its canonical cell-index digest and full authority + field payload, then bind its referenced topography patch to the same cells, + generation/topography/partition versions, exact process-and-derived field + inventory, payload length, and payload digest; +- include final elevation, each glacial/arid/volcanic/coastal elevation delta, + and the corresponding process masks/classes in that canonical inventory so + the private physical-process metrics can be independently reproduced; retain + raw geomorphology climate fields separately from final derived climate, and + retain process-output elevation so process input is exactly output minus the + total delta; +- bind the single dormant Tier III throne anchor as private atlas geometry and + private manifest coordinates; expose only its boolean proof publicly; +- fail closed on a stale lock, substitution, permission drift, oversized + package, unknown file, or digest mismatch. + +Private paths are also ignored defensively. Ignore rules are not the security +boundary: a tracked-file scanner independently rejects private magic, +extensions, fields, images, or directories in Git and release surfaces. + +Owned `Buffer` and typed-array copies are overwritten in `finally` blocks when +their lifetime ends, including temporary seed digests and failed atlas +serialization or persistence. Private candidate retirement also clears the +coordinate lookup captured by its indexed grid before clearing canonical field +arrays. + +This is defense in depth, not a secure-erasure guarantee: V8 strings and +garbage-collected/native-library copies cannot be reliably overwritten, and +unlinking cannot erase copies retained by journaled, copy-on-write, or SSD +storage. Run the generator as a short-lived owner-only process on encrypted +local storage, with core dumps and external heap inspection disabled. + +## Trusted toolchain bootstrap + +Every supported `atlas:*` operation enters through +`scripts/atlas/greater-realm-toolchain-bootstrap.mjs`. The bootstrap imports +only Node built-ins and completes its checks before it loads local `tsx`, +TypeScript, esbuild, Sharp, libvips, or their JavaScript dependencies. It: + +- requires the configured Node 22.13+ / npm 10 lock boundary; +- cross-checks direct exact pins in `package.json` and exact package versions + and npm SHA-512 integrity records between `package-lock.json` and the + committed Greater Realm toolchain lock; +- rejects unsupported host profiles, unsafe ownership or permissions, + symbolic links in registry package contents, nested resolution overrides, + special files, changed package aliases, and unverified module-resolution + shadows; and +- hashes every executable-package file, executable-bit classification, + relative path, and byte count against the committed package-tree records + before launching the pinned absolute `tsx` entrypoint. Package-manager + `.bin` launch shims are not part of registry package trees and are excluded; + the child receives only the trusted Node executable directory in `PATH`, so + those shims cannot participate in command resolution. + +The bootstrap also rejects Node/module-resolution, native-loader, esbuild, and +Sharp override environment variables before verification and removes them +from the child environment. This is a fail-closed check, not a claim that the +bootstrap can undo code a hostile loader variable already injected into its +own process. + +The committed lock currently supports Apple Silicon macOS (including the +optional `fsevents` load reachable from `tsx`) and glibc x64 Linux (where that +macOS-only module must not resolve). The check is local and does not need +network access. Establish `node_modules` with +a clean, locked install (`npm ci`) from reviewed `package.json` and +`package-lock.json`; do not bless an existing working tree by regenerating the +toolchain lock from it. Run `npm run atlas:toolchain-preflight` after install, +then use the `npm run atlas:*` commands rather than invoking `tsx` or the atlas +CLI directly. + +The bootstrap passes a manifest digest and host profile to the child process so +private package metadata can be bound to the completed preflight. That receipt +is diagnostic provenance, not a signature, secret, or independently trusted +capability: a caller that bypasses the bootstrap can forge environment text, +but the supported CLI refuses an absent or inconsistent receipt. Likewise, the +runtime version/artifact record describes the process that produced a package; +it is not itself evidence that code was safe before it ran. + +This boundary assumes a reviewed repository, trusted Node executable, and an +operating system not already controlled by another process with the owner's +privileges. The identity checks narrow filesystem races while reading, but do +not claim to stop an active same-user attacker from replacing files between a +successful preflight and child startup. Use a quiescent owner-only checkout; +re-run a clean locked install and the preflight if dependency state may have +changed. + +## Sanitization model + +Public evidence is built field by field from an exact schema. Private objects +are never copied and then “redacted.” The validator recursively rejects unknown +keys and rejects seed, coordinate, transform, chunk, layout, package, stage, +image, path, and reconstructive fields regardless of nesting. + +The final sanitized JSON export pins the full destination-directory chain and +temporary inode, requires current-owner mode `0644`, exact size, link count, +and byte-for-byte contents, and installs with a no-clobber hard link. Parent or +temporary-path substitution fails without deleting the substituted entry. + +Public review handles are random labels independent of seed or layout. PR A +does not publish a layout digest: pairing a reconstructive digest with a weak +or leaked seed would create an offline guessing oracle. Public evidence carries +its own canonical-document digest, calculated with that one digest field +omitted. + +Candidate timing and process-lifetime peak-memory values are rounded and +excluded from world identity. The memory field is deliberately named +`processPeakMemoryMiBRounded`; Node's `maxRSS` is not a per-candidate peak. +Timestamps, machine paths, host details, preview encodings, and tool diagnostics +are also excluded from authority digests. + +## Determinism and integrity + +Generation uses named, counter-addressed random channels and canonical cell +ordering. Adding a later random consumer cannot perturb an existing stage. +Stable queues include a complete cell-index tie-break. Thermal and erosion +passes use double buffers rather than scan-order mutation. Persistable fields +use checked integers/fixed point; visual previews may use floating-point +presentation only. + +Every batch attempt ordinal is regenerated before review. The rejection ledger +is an exact discriminated union: a completed candidate may record a +`proof-rejection` with its active-cell count and failed proofs, while only an +allowlisted, typed bounded-search exhaustion may record a +`geography-exhaustion` with its exact rejection code. Ordinary errors -- +including invariant, malformed-input, filesystem, package, and toolchain +failures -- abort the atomic batch instead of being converted into candidate +rejections. Both rejection forms must reproduce exactly. Accepted attempts must +reproduce the complete candidate binary, private manifest, chunk and +topography-patch manifests, marked seed envelopes, and preview set. +Verification requires exact bytes, bounded sizes, complete inventories, +authority bindings, and recorded SHA-256 values. Each preview must exactly +match the regenerated marked PNG before its bounded decode/dimension check. A +candidate generated alone, first, or last must have the same authoritative +output. + +Comparison is also deterministic but is not selection. Only metrics captured +from regenerated, byte-for-byte verified private candidate packages may enter +the comparison vector. It covers coordinate-free outer-boundary/coastal, +route/playability, chunk-balance, geological/topographic, climate/landform, and +biome axes. It may write only a private, unranked three-to-five-candidate +shortlist containing opaque handles and axis/constraint labels—not the metric +values—after enforcing route redundancy, 4–8-cell barrier width, zero +incompatible visual adjacencies, and zero incompatible biome/landform pairs. +The shortlist remains `selectionStatus: pending`, with no recommendation and no +automatic selection. Recording a choice requires a separate explicit owner +approval and private receipt; PR A does not record one. + +## Required negative tests + +The pull request must prove that it rejects: + +- a seed-like argument or environment field; +- a repository-contained, public, symbolic-link, special-file, group-readable, + or world-readable private workspace; +- unknown nested public-report fields; +- coordinates, seeds, transforms, chunk keys, private digests, image paths, or + preview references in public evidence; +- tracked private package extensions or private magic in source, docs, + `public/`, `dist/`, and source maps; +- source/config/seed/package substitution; +- a malformed or wrongly typed seed envelope, including a renamed private seed + that still carries the private marker; +- chunk, topography-patch, process-field, cell-index, inventory, or toolchain + substitution; +- a changed `tsx` or native dependency tree and package-lock integrity drift, + before any injected dependency code executes; +- nondeterministic stage output, integer overflow, flow cycles, uphill routing, + inconsistent lake surfaces/spills, or Lowlands catalog drift. + +Tests use programmatically generated synthetic bytes and tiny synthetic grids. +They do not include a realistic seed string, private candidate map, or broad +secret-scanner exception. + +## Residual risk + +An owner workstation, unencrypted storage, backups, swap, screen capture, or +manually shared preview can still disclose a candidate. Candidate review should +therefore use encrypted owner-controlled storage and should never attach exact +maps to GitHub. A later server design must independently prove caller-bounded +hidden-region access; this offline boundary does not make client-side fog safe. +PR A changes no SpacetimeDB schema, browser/runtime data, renderer, public +generated asset, deployment, or production authority; the current Lowlands is +untouched. diff --git a/package-lock.json b/package-lock.json index ebef4c7f..5f1b7337 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "yaml": "2.9.0" }, "engines": { - "node": ">=22.11 <23", + "node": ">=22.13 <23", "npm": ">=10.9.8 <11" } }, diff --git a/package.json b/package.json index c0d24252..f6a73d7b 100644 --- a/package.json +++ b/package.json @@ -13,18 +13,26 @@ "url": "https://github.com/ael-dev3/Warpkeep/issues" }, "engines": { - "node": ">=22.11 <23", + "node": ">=22.13 <23", "npm": ">=10.9.8 <11" }, "packageManager": "npm@10.9.8", "type": "module", "scripts": { "dev": "vite", - "build": "tsc -b && node scripts/verify-runtime-assets.mjs && node scripts/verify-hegemony-gold-mine-candidates.mjs && node scripts/verify-hegemony-gold-mine-runtime.mjs && node scripts/verify-hegemony-wheat-farm-runtime-assets.mjs && node scripts/verify-hegemony-logging-camp-runtime-assets.mjs && node scripts/verify-hegemony-stone-quarry-runtime-assets.mjs && node scripts/verify-hegemony-supply-wagon-assets.mjs && node scripts/verify-hegemony-tree-runtime-assets.mjs && vite build && node scripts/verify-production-dist-exclusions.mjs && node scripts/verify-farcaster-miniapp.mjs", + "build": "tsc -b && node scripts/verify-runtime-assets.mjs && node scripts/verify-hegemony-gold-mine-candidates.mjs && node scripts/verify-hegemony-gold-mine-runtime.mjs && node scripts/verify-hegemony-wheat-farm-runtime-assets.mjs && node scripts/verify-hegemony-logging-camp-runtime-assets.mjs && node scripts/verify-hegemony-stone-quarry-runtime-assets.mjs && node scripts/verify-hegemony-supply-wagon-assets.mjs && node scripts/verify-hegemony-tree-runtime-assets.mjs && vite build && node scripts/verify-production-dist-exclusions.mjs && node scripts/atlas/verify-public-boundary.mjs && node scripts/verify-farcaster-miniapp.mjs", "preview": "vite preview", - "check": "npm run verify:licenses && npm run verify:runtime-assets && npm run verify:file-sizes && npm test && npm run typecheck && npm run build", + "check": "npm run verify:licenses && npm run verify:atlas-public-boundary && npm run verify:runtime-assets && npm run verify:file-sizes && npm test && npm run typecheck && npm run build", "validate:pages-config": "node scripts/validate-pages-deploy-config.mjs", "verify:licenses": "node scripts/verify-license-policy.mjs", + "verify:atlas-public-boundary": "node scripts/atlas/verify-public-boundary.mjs", + "atlas:toolchain-preflight": "node scripts/atlas/greater-realm-toolchain-bootstrap.mjs --verify-only", + "atlas:generate-candidates": "node scripts/atlas/greater-realm-toolchain-bootstrap.mjs generate-candidates", + "atlas:compare-candidates": "node scripts/atlas/greater-realm-toolchain-bootstrap.mjs compare-candidates", + "atlas:verify-private-package": "node scripts/atlas/greater-realm-toolchain-bootstrap.mjs verify-private-package", + "atlas:export-sanitized-review": "node scripts/atlas/greater-realm-toolchain-bootstrap.mjs export-sanitized-review", + "atlas:verify-sanitized-review": "node scripts/atlas/greater-realm-toolchain-bootstrap.mjs verify-sanitized-review", + "atlas:select-candidate": "node scripts/atlas/greater-realm-toolchain-bootstrap.mjs select-candidate", "verify:alpha-production": "node scripts/verify-alpha-production.mjs --require-auth-v2-enabled", "verify:alpha-production:operator": "node scripts/verify-alpha-production.mjs --require-auth-v2-enabled --require-rpc-role-attestation", "verify:alpha-production:contained": "node scripts/verify-alpha-production.mjs --require-auth-v2", diff --git a/scripts/atlas/greater-realm-candidate-generator.ts b/scripts/atlas/greater-realm-candidate-generator.ts new file mode 100644 index 00000000..36269684 --- /dev/null +++ b/scripts/atlas/greater-realm-candidate-generator.ts @@ -0,0 +1,6678 @@ +import { createHash, createHmac } from 'node:crypto'; + +import { + GREATER_REALM_REQUIRED_CASTLE_SLOT_COUNT, + GREATER_REALM_REQUIRED_GATE_COUNT, + type GreaterRealmSanitizedCandidateSource, +} from './greater-realm-contracts'; +import { rejectGreaterRealmCandidate } from './greater-realm-candidate-rejection'; +import { + GREATER_REALM_AXIAL_DIRECTIONS, + accumulateGreaterRealmSingleFlow, + createGreaterRealmMultiscaleIntegerField, + digestGreaterRealmTerrainStage, + erodeGreaterRealmThermally, + greaterRealmCounterRandomU32, + greaterRealmTerrainChannelId, + indexGreaterRealmAxialGrid, + priorityFloodGreaterRealmHexGrid, + routeGreaterRealmSingleFlow, + type AxialCoordinate, + type GreaterRealmTerrainSeed, + type GreaterRealmSingleFlowRouting, + type IndexedAxialGrid, + type IntegerTerrainArray, +} from './greater-realm-terrain'; +import { + GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1, + GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1, + assertGreaterRealmLegacyLowlandsPatchLocked, + inverseGlobalToLegacyLowlands, + transformLegacyLowlandsToGlobal, + type LegacyLowlandsAtlasTransform, +} from './greater-realm-legacy-lowlands'; +import { + shapeGreaterRealmGeomorphology, + type GreaterRealmGeomorphologyMetrics, +} from './greater-realm-geomorphology'; +import { + deriveGreaterRealmTopography, +} from './greater-realm-topography'; + +export const GREATER_REALM_GENERATOR_VERSION = + 'greater-realm-v2-natural-continent-pr-a.3' as const; +export const GREATER_REALM_PRIVATE_PACKAGE_MAGIC = 'WKGR-PRIVATE-ATLAS-V1' as const; +export const GREATER_REALM_PRIVATE_MANIFEST_KIND = + 'warpkeep.greater-realm.private-candidate.v1' as const; + +const PRIVATE_CANVAS_RADIUS = 270; +const ACTIVE_DEEP_OCEAN_BUFFER_MINIMUM = 20; +const ACTIVE_DEEP_OCEAN_BUFFER_SPAN = 9; +const SEA_LEVEL = 0; +const REGION_COUNT = 10; +const TIER_I_REGION_COUNT = 6; +const TIER_II_REGION_COUNT = 3; +const TIER_III_REGION_INDEX = 9; +const HEX_NEIGHBOR_COUNT = 6; +const INT32_MAX = 0x7fff_ffff; +const WATER_DRY = 0; +const WATER_OCEAN = 1; +const WATER_LAKE = 2; +const WATER_RIVER = 3; +const WATER_STREAM = 4; +const WATER_SEA = 5; +// Calibrated against ordinary deterministic continents so the generated river +// network falls inside the reviewed 48–72 range without truncating valid +// watersheds after the fact. The flow authority remains continuous; this is +// only the classification threshold between a major river and a minor stream. +const MAJOR_RIVER_DISCHARGE = 144; + +export const GREATER_REALM_REGION_SPECS = Object.freeze([ + Object.freeze({ id: 'T1_LOWLANDS', name: 'The Hegemony Lowlands', tier: 1 }), + Object.freeze({ id: 'T1_FROSTMERE', name: 'Frostmere Reach', tier: 1 }), + Object.freeze({ id: 'T1_SUNSCAR', name: 'Sunscar Expanse', tier: 1 }), + Object.freeze({ id: 'T1_MIREFEN', name: 'Mirefen Delta', tier: 1 }), + Object.freeze({ id: 'T1_STONEWAKE', name: 'Stonewake Isles', tier: 1 }), + Object.freeze({ id: 'T1_EMBERWOOD', name: 'Emberwood March', tier: 1 }), + Object.freeze({ id: 'T2_CROWNWOOD', name: 'Crownwood March', tier: 2 }), + Object.freeze({ id: 'T2_IRONVEIL', name: 'Ironveil March', tier: 2 }), + Object.freeze({ id: 'T2_GLASSWATER', name: 'Glasswater March', tier: 2 }), + Object.freeze({ id: 'T3_THRONEHEART', name: 'Throneheart', tier: 3 }), +] as const); + +const GREATER_REALM_PROVISIONAL_GATE_GRAPH = Object.freeze([ + Object.freeze([0, 6]), + Object.freeze([1, 6]), + Object.freeze([2, 7]), + Object.freeze([3, 7]), + Object.freeze([4, 8]), + Object.freeze([5, 8]), + Object.freeze([6, 9]), + Object.freeze([7, 9]), + Object.freeze([8, 9]), +] as const); + +export type GreaterRealmGateGraphEdge = readonly [number, number]; + +export type GreaterRealmPseudoTectonicDomain = Readonly<{ + id: number; + q: number; + r: number; + crustClass: 0 | 1 | 2; + motionQ: number; + motionR: number; + buoyancy: number; + resistance: number; + volcanicPotential: number; + age: number; +}>; + +export type GreaterRealmPrivateGate = Readonly<{ + gateIndex: number; + firstRegion: number; + secondRegion: number; + firstCell: number; + secondCell: number; + firstApproachPath: readonly number[]; + firstAlternateApproachPath: readonly number[]; + secondApproachPath: readonly number[]; + secondAlternateApproachPath: readonly number[]; +}>; + +export type GreaterRealmPrivateBarrierCrossSection = Readonly<{ + firstCell: number; + secondCell: number; + system: 1 | 2; + firstSideCellCount: number; + waterAssistedCellCount: number; + cells: readonly number[]; +}>; + +export type GreaterRealmPrivateCandidate = Readonly<{ + candidateOrdinal: number; + seedMaterial: Buffer; + candidateSeed: Uint32Array; + domains: readonly GreaterRealmPseudoTectonicDomain[]; + grid: IndexedAxialGrid; + legacyLowlandsTransform: LegacyLowlandsAtlasTransform; + legacyLowlandsCell: Uint8Array; + legacyLowlandsProtectedCell: Uint8Array; + legacyLowlandsReserveCell: Uint8Array; + legacyLowlandsCastleSlot: Uint8Array; + bedrockElevation: Int32Array; + elevation: Int32Array; + filledElevation: Int32Array; + sedimentDepth: Uint16Array; + flowReceiver: Int32Array; + flowAccumulation: BigUint64Array; + domainId: Uint8Array; + geologyId: Uint8Array; + tectonicUplift: Int32Array; + rockResistance: Int32Array; + geomorphologyElevation: Int32Array; + geomorphologyTemperature: Int32Array; + geomorphologyMoisture: Int32Array; + geomorphologyTotalDelta: Int32Array; + geomorphologyGlacialDelta: Int32Array; + geomorphologyAridDelta: Int32Array; + geomorphologyVolcanicDelta: Int32Array; + geomorphologyCoastalDelta: Int32Array; + geomorphologyGlacialMask: Uint8Array; + geomorphologyAridMask: Uint8Array; + geomorphologyVolcanicMask: Uint8Array; + geomorphologyVolcanicAnchorMask: Uint8Array; + geomorphologyCoastalMask: Uint8Array; + geomorphologyCoastalClass: Uint8Array; + regionId: Uint8Array; + tierId: Uint8Array; + waterRegime: Uint8Array; + biomeId: Uint8Array; + landformId: Uint8Array; + slope: Uint16Array; + aspect: Uint8Array; + profileCurvature: Int32Array; + planCurvature: Int32Array; + wetnessIndex: Uint16Array; + exposure: Int32Array; + distanceToCoast: Uint16Array; + distanceToFreshwater: Uint16Array; + watershedId: Int32Array; + ridgeId: Int32Array; + temperature: Int32Array; + moisture: Int32Array; + barrier: Uint8Array; + geologicalBarrierBand: Uint8Array; + castleSlot: Uint8Array; + resourcePotential: Uint8Array; + corePotential: Uint8Array; + throneAnchor: Uint8Array; + tierOneSemanticPermutation: readonly number[]; + gateGraph: readonly GreaterRealmGateGraphEdge[]; + gates: readonly GreaterRealmPrivateGate[]; + barrierCrossSections: readonly GreaterRealmPrivateBarrierCrossSection[]; + stageDigests: Readonly>; + aggregate: Omit; + privateMetrics: Readonly<{ + activeBoundaryCells: number; + maximumBoundaryRadiusShareBasisPoints: number; + rotationalSimilarityBasisPoints: number; + maximumAlignedBoundaryRun: number; + minimumBoundaryLandDistance: number; + saltwaterBoundaryBasisPoints: number; + majorLandmassCount: number; + largeIslandCount: number; + smallIslandCount: number; + mountainBarrierCells: number; + measuredMinimumBarrierWidth: number; + measuredMaximumBarrierWidth: number; + gateRouteRedundancyProof: boolean; + lakeBasinCandidates: number; + riverMouthCandidates: number; + streamHeadCandidates: number; + resourcePotentialSites: number; + corePotentialSites: number; + chunkCount: number; + chunkPopulationMinimum: number; + chunkPopulationMedian: number; + chunkPopulationP95: number; + chunkPopulationMaximum: number; + erodedMaterialUnits: number; + depositedMaterialUnits: number; + exportedSedimentUnits: number; + minimumLargestPassableRegionShareBasisPoints: number; + largestPassableRegionSharesBasisPoints: readonly number[]; + minorPassableFragmentSharesBasisPoints: readonly number[]; + passableBoundaryDensityBasisPoints: readonly number[]; + passableTendrilSharesBasisPoints: readonly number[]; + tierRadialAgreementBasisPoints: number; + radialTierOneBoundaryShareBasisPoints: number; + highlandBarrierShareBasisPoints: number; + barrierMeanElevationAdvantage: number; + barrierMeanUpliftAdvantage: number; + geomorphology: GreaterRealmGeomorphologyMetrics; + throneAnchorBarrierClearance: number; + tierThreePassableLandCells: number; + smallestOtherRegionPassableLandCells: number; + eligibilityFailureCodes: readonly string[]; + }>; +}>; + +function fail(code: string): never { + throw new Error(code); +} + +function assertRootSeed(rootSeed: Uint8Array): void { + if (!(rootSeed instanceof Uint8Array) || rootSeed.byteLength !== 32) { + fail('GREATER_REALM_ROOT_SEED_INVALID'); + } +} + +function axialDistance(q: number, r: number, otherQ = 0, otherR = 0): number { + const deltaQ = q - otherQ; + const deltaR = r - otherR; + return Math.max(Math.abs(deltaQ), Math.abs(deltaR), Math.abs(-deltaQ - deltaR)); +} + +function hexDot(q1: number, r1: number, q2: number, r2: number): number { + return 2 * q1 * q2 + q1 * r2 + r1 * q2 + 2 * r1 * r2; +} + +function clamp(value: number, minimum: number, maximum: number): number { + return Math.max(minimum, Math.min(maximum, value)); +} + +function integerSquareRoot(value: number): number { + if (!Number.isSafeInteger(value) || value < 0) fail('GREATER_REALM_INTEGER_SQRT_INVALID'); + if (value < 2) return value; + let low = 1; + let high = Math.min(value, 1 << 26); + while (low <= high) { + const middle = Math.floor((low + high) / 2); + const square = middle * middle; + if (square === value) return middle; + if (square < value) low = middle + 1; + else high = middle - 1; + } + return high; +} + +function deriveCandidateSeedMaterial( + rootSeed: Uint8Array, + candidateOrdinal: number, +): Buffer { + assertRootSeed(rootSeed); + if (!Number.isSafeInteger(candidateOrdinal) || candidateOrdinal < 0) { + fail('GREATER_REALM_CANDIDATE_ORDINAL_INVALID'); + } + return createHmac('sha256', rootSeed) + .update(GREATER_REALM_GENERATOR_VERSION, 'utf8') + .update('\0candidate\0', 'utf8') + .update(String(candidateOrdinal), 'utf8') + .digest(); +} + +function deriveCandidateSeed( + seedMaterial: Uint8Array, +): Uint32Array { + const digest = createHash('sha256') + .update('warpkeep-greater-realm-u32-v1\0', 'utf8') + .update(seedMaterial) + .digest(); + try { + return new Uint32Array([ + digest.readUInt32LE(0), + digest.readUInt32LE(4), + digest.readUInt32LE(8), + digest.readUInt32LE(12), + ]); + } finally { + digest.fill(0); + } +} + +let sharedPrivateCanvas: IndexedAxialGrid | undefined; + +function greaterRealmPrivateCanvasAuthority(): IndexedAxialGrid { + if (sharedPrivateCanvas) return sharedPrivateCanvas; + const coordinates: AxialCoordinate[] = []; + for (let q = -PRIVATE_CANVAS_RADIUS; q <= PRIVATE_CANVAS_RADIUS; q += 1) { + const minimumR = Math.max(-PRIVATE_CANVAS_RADIUS, -q - PRIVATE_CANVAS_RADIUS); + const maximumR = Math.min(PRIVATE_CANVAS_RADIUS, -q + PRIVATE_CANVAS_RADIUS); + for (let r = minimumR; r <= maximumR; r += 1) coordinates.push({ q, r }); + } + sharedPrivateCanvas = indexGreaterRealmAxialGrid(coordinates); + return sharedPrivateCanvas; +} + +/** A defensive snapshot; callers can never mutate the generator's cached authority grid. */ +export function greaterRealmPrivateCanvas(): IndexedAxialGrid { + const canvas = greaterRealmPrivateCanvasAuthority(); + return Object.freeze({ + cellCount: canvas.cellCount, + q: new Int32Array(canvas.q), + r: new Int32Array(canvas.r), + neighbors: new Int32Array(canvas.neighbors), + indexOf: canvas.indexOf, + }); +} + +function separatedDomains( + candidateSeed: GreaterRealmTerrainSeed, +): readonly GreaterRealmPseudoTectonicDomain[] { + const count = 7 + ( + greaterRealmCounterRandomU32( + candidateSeed, + greaterRealmTerrainChannelId('tectonic-domain-count'), + 0, + 0, + ) % 6 + ); + const domains: GreaterRealmPseudoTectonicDomain[] = []; + const positionChannel = greaterRealmTerrainChannelId('tectonic-domain-position'); + for (let ordinal = 0; ordinal < 4_096 && domains.length < count; ordinal += 1) { + const qRandom = greaterRealmCounterRandomU32(candidateSeed, positionChannel, 0, 0, ordinal * 2); + const rRandom = greaterRealmCounterRandomU32(candidateSeed, positionChannel, 0, 0, ordinal * 2 + 1); + const q = (qRandom % 361) - 180; + const minimumR = Math.max(-180, -q - 180); + const maximumR = Math.min(180, -q + 180); + const r = minimumR + (rRandom % (maximumR - minimumR + 1)); + if (domains.some(domain => axialDistance(q, r, domain.q, domain.r) < 46)) continue; + const id = domains.length; + const attribute = (sample: number) => greaterRealmCounterRandomU32( + candidateSeed, + greaterRealmTerrainChannelId('tectonic-domain-attribute'), + q, + r, + sample, + ); + const motion = GREATER_REALM_AXIAL_DIRECTIONS[attribute(1) % 6]!; + domains.push(Object.freeze({ + id, + q, + r, + crustClass: (id === 0 ? 0 : attribute(0) % 3) as 0 | 1 | 2, + motionQ: motion.q * (1 + (attribute(2) % 3)), + motionR: motion.r * (1 + (attribute(3) % 3)), + buoyancy: (attribute(4) % 5_001) - 2_000, + resistance: 3_000 + (attribute(5) % 5_001), + volcanicPotential: attribute(6) % 10_001, + age: 1_000 + (attribute(7) % 9_001), + })); + } + if (domains.length !== count) { + rejectGreaterRealmCandidate('GREATER_REALM_TECTONIC_DOMAIN_PLACEMENT_FAILED'); + } + return Object.freeze(domains); +} + +function nearestDomains( + grid: IndexedAxialGrid, + domains: readonly GreaterRealmPseudoTectonicDomain[], +): Readonly<{ + domainId: Uint8Array; + nearestDistance: Uint16Array; + boundaryCloseness: Uint16Array; +}> { + const domainId = new Uint8Array(grid.cellCount); + const nearestDistance = new Uint16Array(grid.cellCount); + const boundaryCloseness = new Uint16Array(grid.cellCount); + for (let index = 0; index < grid.cellCount; index += 1) { + let nearest = INT32_MAX; + let second = INT32_MAX; + let nearestId = 0; + for (const domain of domains) { + const distance = axialDistance(grid.q[index]!, grid.r[index]!, domain.q, domain.r); + if (distance < nearest || (distance === nearest && domain.id < nearestId)) { + second = nearest; + nearest = distance; + nearestId = domain.id; + } else if (distance < second) { + second = distance; + } + } + domainId[index] = nearestId; + nearestDistance[index] = nearest; + boundaryCloseness[index] = clamp(96 - (second - nearest), 0, 96); + } + return Object.freeze({ domainId, nearestDistance, boundaryCloseness }); +} + +function macroGeology( + grid: IndexedAxialGrid, + candidateSeed: GreaterRealmTerrainSeed, + domains: readonly GreaterRealmPseudoTectonicDomain[], +): Readonly<{ + bedrock: Int32Array; + uplift: Int32Array; + resistance: Int32Array; + domainId: Uint8Array; + geologyId: Uint8Array; +}> { + const nearest = nearestDomains(grid, domains); + const relief = createGreaterRealmMultiscaleIntegerField(grid, candidateSeed, [ + { channel: 'relief-macro', amplitude: 7_200, smoothingPasses: 24, selfWeight: 3 }, + { channel: 'relief-meso', amplitude: 2_800, smoothingPasses: 7, selfWeight: 2 }, + { channel: 'relief-local', amplitude: 800, smoothingPasses: 1, selfWeight: 2 }, + ]); + const bedrock = new Int32Array(grid.cellCount); + const uplift = new Int32Array(grid.cellCount); + const resistance = new Int32Array(grid.cellCount); + const geologyId = new Uint8Array(grid.cellCount); + const continentCenterChannel = greaterRealmTerrainChannelId('dominant-continent-center'); + const continentCenterQ = ( + greaterRealmCounterRandomU32(candidateSeed, continentCenterChannel, 0, 0, 0) % 45 + ) - 22; + const continentCenterR = ( + greaterRealmCounterRandomU32(candidateSeed, continentCenterChannel, 0, 0, 1) % 45 + ) - 22; + const islandChannel = greaterRealmTerrainChannelId('subduction-island-arcs'); + const islandArcCount = 3 + ( + greaterRealmCounterRandomU32(candidateSeed, islandChannel, 0, 0, 0) % 6 + ); + const islandAnchors: Array> = []; + const islandLobes: Array> = []; + for (let attempt = 0; attempt < 4_096 && islandAnchors.length < islandArcCount; attempt += 1) { + const q = (greaterRealmCounterRandomU32( + candidateSeed, + islandChannel, + 0, + 0, + attempt * 2 + 1, + ) % 421) - 210; + const minimumR = Math.max(-210, -q - 210); + const maximumR = Math.min(210, -q + 210); + const r = minimumR + (greaterRealmCounterRandomU32( + candidateSeed, + islandChannel, + 0, + 0, + attempt * 2 + 2, + ) % (maximumR - minimumR + 1)); + const radiusFromContinent = axialDistance(q, r, continentCenterQ, continentCenterR); + // This materialized geological envelope includes mountain shoulders and + // natural water/escarpment portions of the strategic barrier. The narrower + // `barrier` mask below is the actually impassable crest/cut. + if ( + radiusFromContinent < 200 + || radiusFromContinent > 220 + || islandAnchors.some(anchor => axialDistance(q, r, anchor.q, anchor.r) < 38) + ) continue; + const arc = islandAnchors.length; + islandAnchors.push(Object.freeze({ q, r })); + const direction = GREATER_REALM_AXIAL_DIRECTIONS[greaterRealmCounterRandomU32( + candidateSeed, + islandChannel, + q, + r, + 8_000 + arc, + ) % HEX_NEIGHBOR_COUNT]!; + const lobeCount = 2 + (greaterRealmCounterRandomU32( + candidateSeed, + islandChannel, + q, + r, + 9_000 + arc, + ) % 3); + for (let lobe = 0; lobe < lobeCount; lobe += 1) { + const offset = (lobe * 5) - Math.floor(((lobeCount - 1) * 5) / 2); + islandLobes.push(Object.freeze({ + q: q + direction.q * offset, + r: r + direction.r * offset, + radius: 11 + (greaterRealmCounterRandomU32( + candidateSeed, + islandChannel, + q, + r, + 10_000 + arc * 4 + lobe, + ) % 5), + peak: 42_000 + (greaterRealmCounterRandomU32( + candidateSeed, + islandChannel, + q, + r, + 20_000 + arc * 4 + lobe, + ) % 10_001), + })); + } + } + if (islandAnchors.length !== islandArcCount) { + rejectGreaterRealmCandidate('GREATER_REALM_ISLAND_ARC_PLACEMENT_FAILED'); + } + for (let index = 0; index < grid.cellCount; index += 1) { + const q = grid.q[index]!; + const r = grid.r[index]!; + const distanceFromCenter = axialDistance(q, r, continentCenterQ, continentCenterR); + const domain = domains[nearest.domainId[index]!]!; + const crustBias = domain.crustClass === 0 ? 2_500 : domain.crustClass === 1 ? -3_200 : -300; + const boundary = nearest.boundaryCloseness[index]!; + let interaction = 0; + for (let directionIndex = 0; directionIndex < HEX_NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[index * HEX_NEIGHBOR_COUNT + directionIndex]!; + if (neighbor < 0 || nearest.domainId[neighbor] === nearest.domainId[index]) continue; + const other = domains[nearest.domainId[neighbor]!]!; + const towardQ = other.q - domain.q; + const towardR = other.r - domain.r; + const relativeQ = domain.motionQ - other.motionQ; + const relativeR = domain.motionR - other.motionR; + interaction = Math.max(interaction, hexDot(relativeQ, relativeR, towardQ, towardR)); + } + const convergent = clamp(Math.floor(interaction / 16), 0, 80); + const divergent = clamp(Math.floor(-interaction / 20), 0, 60); + const volcanic = Math.floor((domain.volcanicPotential * boundary) / 160); + const upliftValue = boundary * (24 + convergent) + volcanic - divergent * 40; + const continentalUplift = clamp(upliftValue, -12_000, 18_000); + const edgeOcean = Math.max(0, distanceFromCenter - 188) * 480; + const broadContinent = 8_800 - distanceFromCenter * 62; + let islandArcUplift = 0; + let islandArcTrench = 0; + for (const lobe of islandLobes) { + const distance = axialDistance(q, r, lobe.q, lobe.r); + if (distance <= lobe.radius) { + islandArcUplift = Math.max( + islandArcUplift, + Math.floor((lobe.peak * (lobe.radius - distance + 1)) / (lobe.radius + 1)), + ); + } else if (distance <= lobe.radius + 4) { + // A subduction trench keeps each coherent arc geologically separate + // from the continental shelf and neighboring arcs. The taper is wide + // enough to survive thermal relaxation without becoming a hard ring. + islandArcTrench = Math.max( + islandArcTrench, + 18_000 - (distance - lobe.radius - 1) * 3_000, + ); + } + } + // Island arcs are themselves tectonic highlands. Record a bounded share + // of their endogenic rise in the authoritative uplift field so later + // ridge/barrier reasoning cannot mistake them for unexplained peaks. + uplift[index] = clamp( + continentalUplift + Math.floor(islandArcUplift / 4), + -12_000, + 18_000, + ); + bedrock[index] = clamp( + broadContinent + crustBias + domain.buoyancy + relief[index]! + continentalUplift + + islandArcUplift - (islandArcUplift > 0 ? 0 : islandArcTrench) - edgeOcean, + -60_000, + 60_000, + ); + resistance[index] = domain.resistance; + geologyId[index] = (domain.id % 8) + 1; + } + return Object.freeze({ + bedrock, + uplift, + resistance, + domainId: nearest.domainId, + geologyId, + }); +} + +function distanceFromMask( + grid: IndexedAxialGrid, + starts: Uint8Array, +): Uint16Array { + if (starts.length !== grid.cellCount) fail('GREATER_REALM_DISTANCE_MASK_INVALID'); + const distance = new Uint16Array(grid.cellCount); + distance.fill(0xffff); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (let index = 0; index < grid.cellCount; index += 1) { + if (starts[index] !== 1) continue; + distance[index] = 0; + queue[tail++] = index; + } + if (tail === 0) fail('GREATER_REALM_DISTANCE_MASK_EMPTY'); + while (head < tail) { + const cell = queue[head++]!; + const nextDistance = distance[cell]! + 1; + for (let directionIndex = 0; directionIndex < HEX_NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + directionIndex]!; + if (neighbor < 0 || distance[neighbor]! <= nextDistance) continue; + distance[neighbor] = nextDistance; + queue[tail++] = neighbor; + } + } + return distance; +} + +function activeMask( + canvas: IndexedAxialGrid, + candidateSeed: GreaterRealmTerrainSeed, + shapedElevation: Int32Array, +): Readonly<{ + mask: Uint8Array; + distanceToLand: Uint16Array; +}> { + const land = new Uint8Array(canvas.cellCount); + let landCellCount = 0; + for (let index = 0; index < canvas.cellCount; index += 1) { + if (shapedElevation[index]! > SEA_LEVEL) { + land[index] = 1; + landCellCount += 1; + } + } + if (landCellCount === 0) rejectGreaterRealmCandidate('GREATER_REALM_ACTIVE_MASK_EMPTY'); + const distanceToLand = distanceFromMask(canvas, land); + const mask = new Uint8Array(canvas.cellCount); + const boundaryChannel = greaterRealmTerrainChannelId('active-deep-ocean-boundary'); + for (let index = 0; index < canvas.cellCount; index += 1) { + const variableBuffer = ACTIVE_DEEP_OCEAN_BUFFER_MINIMUM + ( + greaterRealmCounterRandomU32( + candidateSeed, + boundaryChannel, + Math.floor(canvas.q[index]! / 9), + Math.floor(canvas.r[index]! / 9), + ) % ACTIVE_DEEP_OCEAN_BUFFER_SPAN + ); + const canvasClearance = PRIVATE_CANVAS_RADIUS - axialDistance(canvas.q[index]!, canvas.r[index]!); + if (distanceToLand[index]! <= variableBuffer && canvasClearance >= 8) mask[index] = 1; + } + const components = [...connectedComponents(canvas, mask)] + .sort((first, second) => second.length - first.length || first[0]! - second[0]!); + if (components.length === 0) rejectGreaterRealmCandidate('GREATER_REALM_ACTIVE_MASK_EMPTY'); + mask.fill(0); + for (const index of components[0]!) mask[index] = 1; + const inactive = new Uint8Array(canvas.cellCount); + for (let index = 0; index < canvas.cellCount; index += 1) { + if (mask[index] === 0) inactive[index] = 1; + } + for (const component of connectedComponents(canvas, inactive)) { + const reachesCanvasBoundary = component.some(cell => { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (canvas.neighbors[cell * HEX_NEIGHBOR_COUNT + direction] === -1) return true; + } + return false; + }); + if (!reachesCanvasBoundary) { + for (const cell of component) mask[cell] = 1; + } + } + return Object.freeze({ mask, distanceToLand }); +} + +function activeGridFromCanvas( + canvas: IndexedAxialGrid, + mask: Uint8Array, +): Readonly<{ grid: IndexedAxialGrid; sourceIndexes: Uint32Array }> { + const coordinates: AxialCoordinate[] = []; + const source: number[] = []; + for (let index = 0; index < canvas.cellCount; index += 1) { + if (mask[index] !== 1) continue; + coordinates.push({ q: canvas.q[index]!, r: canvas.r[index]! }); + source.push(index); + } + const grid = indexGreaterRealmAxialGrid(coordinates); + const sourceByKey = new Map(); + for (const index of source) sourceByKey.set(`${canvas.q[index]},${canvas.r[index]}`, index); + const sourceIndexes = new Uint32Array(grid.cellCount); + for (let index = 0; index < grid.cellCount; index += 1) { + const sourceIndex = sourceByKey.get(`${grid.q[index]},${grid.r[index]}`); + if (sourceIndex === undefined) fail('GREATER_REALM_ACTIVE_GRID_MAPPING_FAILED'); + sourceIndexes[index] = sourceIndex; + } + return Object.freeze({ grid, sourceIndexes }); +} + +function projectInt32(source: Int32Array, indexes: Uint32Array): Int32Array { + const output = new Int32Array(indexes.length); + for (let index = 0; index < indexes.length; index += 1) output[index] = source[indexes[index]!]!; + return output; +} + +function projectUint8(source: Uint8Array, indexes: Uint32Array): Uint8Array { + const output = new Uint8Array(indexes.length); + for (let index = 0; index < indexes.length; index += 1) output[index] = source[indexes[index]!]!; + return output; +} + +type GreaterRealmLegacyPlacement = Readonly<{ + transform: LegacyLowlandsAtlasTransform; + worldCell: Uint8Array; + protectedCell: Uint8Array; + reserveCell: Uint8Array; + castleSlot: Uint8Array; + proof: boolean; +}>; + +/** + * Reserve one exact, reversible placement for the deployed Lowlands patch. + * The protected mask is the union of all 10,000 canonical cells and the + * current Water-v1 cells; the four-cell halo lets future PRs join new geology + * to that immutable patch without cutting a tier barrier through it. + */ +function placeLegacyLowlands( + grid: IndexedAxialGrid, + candidateSeed: GreaterRealmTerrainSeed, + existingElevation: Int32Array, +): GreaterRealmLegacyPlacement { + assertGreaterRealmLegacyLowlandsPatchLocked(); + const patch = GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1; + const protectedByKey = new Map(); + for (const tile of patch.world.tiles) protectedByKey.set(tile.key, tile); + for (const waterCell of patch.water.cells) { + protectedByKey.set(waterCell.cellKey, waterCell); + } + const protectedCoordinates = [...protectedByKey.values()]; + const boundary = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction] === -1) { + boundary[cell] = 1; + break; + } + } + } + const clearance = distanceFromMask(grid, boundary); + const targetChannel = greaterRealmTerrainChannelId('legacy-lowlands-placement-target'); + const targetQ = 148 + ( + greaterRealmCounterRandomU32(candidateSeed, targetChannel, 0, 0, 0) % 17 + ) - 8; + const targetR = ( + greaterRealmCounterRandomU32(candidateSeed, targetChannel, 0, 0, 1) % 25 + ) - 12; + const centers = Array.from({ length: grid.cellCount }, (_, cell) => cell) + .filter(cell => clearance[cell]! >= 70 && existingElevation[cell]! > SEA_LEVEL) + .sort((first, second) => { + const firstScore = axialDistance(grid.q[first]!, grid.r[first]!, targetQ, targetR) * 1_000 + - clamp(existingElevation[first]!, -12_000, 12_000) * 20 + + (greaterRealmCounterRandomU32( + candidateSeed, + targetChannel, + grid.q[first]!, + grid.r[first]!, + 2, + ) % 1_000); + const secondScore = axialDistance(grid.q[second]!, grid.r[second]!, targetQ, targetR) * 1_000 + - clamp(existingElevation[second]!, -12_000, 12_000) * 20 + + (greaterRealmCounterRandomU32( + candidateSeed, + targetChannel, + grid.q[second]!, + grid.r[second]!, + 2, + ) % 1_000); + return firstScore - secondScore || first - second; + }); + if (centers.length === 0) { + rejectGreaterRealmCandidate('GREATER_REALM_LEGACY_LOWLANDS_PLACEMENT_MISSING'); + } + const firstRotation = greaterRealmCounterRandomU32( + candidateSeed, + targetChannel, + 0, + 0, + 3, + ) % 6; + for (const center of centers.slice(0, 1_024)) { + for (let rotationOffset = 0; rotationOffset < 6; rotationOffset += 1) { + const transform: LegacyLowlandsAtlasTransform = Object.freeze({ + rotationSteps: ((firstRotation + rotationOffset) % 6) as 0 | 1 | 2 | 3 | 4 | 5, + globalOffsetQ: grid.q[center]!, + globalOffsetR: grid.r[center]!, + }); + const protectedCell = new Uint8Array(grid.cellCount); + let placementValid = true; + for (const localCoordinate of protectedCoordinates) { + const globalCoordinate = transformLegacyLowlandsToGlobal(localCoordinate, transform); + const globalIndex = grid.indexOf(globalCoordinate); + if (globalIndex < 0 || protectedCell[globalIndex] === 1) { + placementValid = false; + break; + } + protectedCell[globalIndex] = 1; + } + if (!placementValid) continue; + + const worldCell = new Uint8Array(grid.cellCount); + let mappedWorldCells = 0; + let underlyingLandCells = 0; + for (const tile of patch.world.tiles) { + const globalCoordinate = transformLegacyLowlandsToGlobal(tile, transform); + const globalIndex = grid.indexOf(globalCoordinate); + if (globalIndex < 0 || worldCell[globalIndex] === 1) { + placementValid = false; + break; + } + const roundTrip = inverseGlobalToLegacyLowlands(globalCoordinate, transform); + if (roundTrip.q !== tile.q || roundTrip.r !== tile.r) { + placementValid = false; + break; + } + worldCell[globalIndex] = 1; + mappedWorldCells += 1; + if (existingElevation[globalIndex]! > SEA_LEVEL) underlyingLandCells += 1; + } + if ( + !placementValid + || mappedWorldCells !== GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.worldCellCount + || underlyingLandCells * 100 < mappedWorldCells * 70 + ) { + continue; + } + + const castleSlot = new Uint8Array(grid.cellCount); + let mappedCastleSlots = 0; + for (const slot of patch.castleSlots.rows) { + const globalIndex = grid.indexOf(transformLegacyLowlandsToGlobal(slot, transform)); + if (globalIndex < 0 || worldCell[globalIndex] !== 1 || castleSlot[globalIndex] === 1) { + placementValid = false; + break; + } + castleSlot[globalIndex] = 1; + mappedCastleSlots += 1; + } + if (!placementValid || mappedCastleSlots !== GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.castleSlotCount) { + continue; + } + + let reserveCell = new Uint8Array(protectedCell); + for (let pass = 0; pass < 4; pass += 1) { + const expanded = new Uint8Array(reserveCell); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (reserveCell[cell] !== 1) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0) expanded[neighbor] = 1; + } + } + reserveCell = expanded; + } + return Object.freeze({ + transform, + worldCell, + protectedCell, + reserveCell, + castleSlot, + proof: true, + }); + } + } + rejectGreaterRealmCandidate('GREATER_REALM_LEGACY_LOWLANDS_PLACEMENT_FAILED'); +} + +function fluvialPass( + grid: IndexedAxialGrid, + elevation: Int32Array, + resistance: Int32Array, + protectedCell: Uint8Array, +): Readonly<{ + elevation: Int32Array; + filledElevation: Int32Array; + flowReceiver: Int32Array; + flowAccumulation: BigUint64Array; + sedimentDepth: Uint16Array; + erodedMaterialUnits: number; + depositedMaterialUnits: number; + exportedSedimentUnits: number; +}> { + if ( + elevation.length !== grid.cellCount + || resistance.length !== grid.cellCount + || protectedCell.length !== grid.cellCount + ) fail('GREATER_REALM_FLUVIAL_INPUT_LENGTH_INVALID'); + const seaOutlets: number[] = []; + const localContribution = new Uint32Array(grid.cellCount); + for (let index = 0; index < grid.cellCount; index += 1) { + if (elevation[index]! <= SEA_LEVEL) seaOutlets.push(index); + else localContribution[index] = 1; + } + if (seaOutlets.length === 0) { + rejectGreaterRealmCandidate('GREATER_REALM_OCEAN_OUTLETS_MISSING'); + } + const firstFlood = priorityFloodGreaterRealmHexGrid(grid, elevation, seaOutlets); + const firstRouting = routeGreaterRealmSingleFlow(grid, firstFlood); + const firstAccumulation = accumulateGreaterRealmSingleFlow( + grid, + firstFlood.filledElevation, + firstRouting, + localContribution, + ); + const carved = new Int32Array(elevation); + const sedimentFlux = new BigUint64Array(grid.cellCount); + let erodedMaterial = 0n; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const receiver = firstRouting.receiver[cell]!; + if ( + receiver < 0 + || elevation[cell]! <= SEA_LEVEL + || protectedCell[cell] === 1 + ) continue; + const slope = Math.max(0, firstFlood.filledElevation[cell]! - firstFlood.filledElevation[receiver]!); + const discharge = Number(firstAccumulation[cell]!); + const incision = clamp( + Math.floor((integerSquareRoot(discharge) * Math.min(slope, 4_000) * 48) / resistance[cell]!), + 0, + 1_200, + ); + carved[cell] = clamp(carved[cell]! - incision, -60_000, 60_000); + sedimentFlux[cell] = BigInt(incision); + erodedMaterial += BigInt(incision); + } + + // Relax the incised land before routing sediment. Deposition therefore + // belongs to the authoritative final fluvial surface instead of describing + // a transient surface that a later thermal pass would move away. + const relaxed = erodeGreaterRealmThermally(grid, carved, { + iterations: 2, + talus: 1_100, + transferNumerator: 1, + transferDenominator: 24, + }).elevation; + const postThermalOutlets: number[] = []; + const postThermalContribution = new Uint32Array(grid.cellCount); + for (let index = 0; index < grid.cellCount; index += 1) { + if (relaxed[index]! <= SEA_LEVEL) postThermalOutlets.push(index); + else postThermalContribution[index] = 1; + } + if (postThermalOutlets.length === 0) { + rejectGreaterRealmCandidate('GREATER_REALM_OCEAN_OUTLETS_MISSING'); + } + const postThermalFlood = priorityFloodGreaterRealmHexGrid( + grid, + relaxed, + postThermalOutlets, + ); + const postThermalRouting = routeGreaterRealmSingleFlow(grid, postThermalFlood); + const postThermalAccumulation = accumulateGreaterRealmSingleFlow( + grid, + postThermalFlood.filledElevation, + postThermalRouting, + postThermalContribution, + ); + const depositedElevation = new Int32Array(relaxed); + const sedimentDepth = new Uint16Array(grid.cellCount); + let depositedMaterial = 0n; + let exportedSediment = 0n; + for (let orderIndex = grid.cellCount - 1; orderIndex >= 0; orderIndex -= 1) { + const cell = postThermalRouting.order[orderIndex]!; + const available = sedimentFlux[cell]!; + if (available === 0n) continue; + const receiver = postThermalRouting.receiver[cell]!; + let deposited = 0; + if ( + receiver >= 0 + && relaxed[cell]! > SEA_LEVEL + && protectedCell[cell] !== 1 + ) { + const slope = Math.max( + 0, + postThermalFlood.filledElevation[cell]! + - postThermalFlood.filledElevation[receiver]!, + ); + const discharge = Number(postThermalAccumulation[cell]!); + if (slope < 160 && discharge > 80) { + deposited = Math.min( + 240, + Number(available / 4n), + Math.max(0, 60_000 - relaxed[cell]!), + ); + sedimentDepth[cell] = deposited; + depositedElevation[cell] += deposited; + depositedMaterial += BigInt(deposited); + } + } + const remaining = available - BigInt(deposited); + if (receiver >= 0) sedimentFlux[receiver] = sedimentFlux[receiver]! + remaining; + else exportedSediment += remaining; + } + if (erodedMaterial !== depositedMaterial + exportedSediment) { + fail('GREATER_REALM_SEDIMENT_BUDGET_MISMATCH'); + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + depositedElevation[cell] !== relaxed[cell]! + sedimentDepth[cell]! + || (protectedCell[cell] === 1 && sedimentDepth[cell] !== 0) + ) fail('GREATER_REALM_FINAL_SEDIMENT_SURFACE_MISMATCH'); + } + const outlets: number[] = []; + for (let index = 0; index < grid.cellCount; index += 1) { + if (depositedElevation[index]! <= SEA_LEVEL) outlets.push(index); + } + if (outlets.length === 0) { + rejectGreaterRealmCandidate('GREATER_REALM_OCEAN_OUTLETS_MISSING'); + } + const flood = priorityFloodGreaterRealmHexGrid(grid, depositedElevation, outlets); + const routing = routeGreaterRealmSingleFlow(grid, flood); + const accumulation = accumulateGreaterRealmSingleFlow( + grid, + flood.filledElevation, + routing, + postThermalContribution, + ); + return Object.freeze({ + elevation: depositedElevation, + filledElevation: flood.filledElevation, + flowReceiver: routing.receiver, + flowAccumulation: accumulation, + sedimentDepth, + erodedMaterialUnits: Number(erodedMaterial), + depositedMaterialUnits: Number(depositedMaterial), + exportedSedimentUnits: Number(exportedSediment), + }); +} + +function reconcileLegacyLowlandsTopography( + grid: IndexedAxialGrid, + legacy: GreaterRealmLegacyPlacement, + bedrockElevation: Int32Array, + erodedElevation: Int32Array, +): Readonly<{ + bedrockElevation: Int32Array; + elevation: Int32Array; + filledElevation: Int32Array; + flowReceiver: Int32Array; + flowAccumulation: BigUint64Array; +}> { + const adjustedBedrock = new Int32Array(bedrockElevation); + const adjustedElevation = new Int32Array(erodedElevation); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (legacy.protectedCell[cell] !== 1) continue; + adjustedBedrock[cell] = Math.max(200, adjustedBedrock[cell]!); + adjustedElevation[cell] = Math.max(300, adjustedElevation[cell]!); + } + for (const waterCell of GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1.water.enabledCells) { + const cell = grid.indexOf(transformLegacyLowlandsToGlobal(waterCell, legacy.transform)); + if (cell < 0 || legacy.protectedCell[cell] !== 1) { + fail('GREATER_REALM_LEGACY_WATER_TOPOGRAPHY_MISSING'); + } + const target = waterCell.regime === 'ocean' ? -600 : waterCell.regime === 'lake' ? -180 : -80; + adjustedBedrock[cell] = Math.min(adjustedBedrock[cell]!, target - 80); + adjustedElevation[cell] = Math.min(adjustedElevation[cell]!, target); + } + const outlets: number[] = []; + const localContribution = new Uint32Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (adjustedElevation[cell]! <= SEA_LEVEL) outlets.push(cell); + else localContribution[cell] = 1; + } + if (outlets.length === 0) { + rejectGreaterRealmCandidate('GREATER_REALM_RECONCILED_OCEAN_OUTLETS_MISSING'); + } + const flood = priorityFloodGreaterRealmHexGrid(grid, adjustedElevation, outlets); + const generatedRouting = routeGreaterRealmSingleFlow(grid, flood); + const receiver = new Int32Array(generatedRouting.receiver); + const legacyWaterByKey = new Map( + GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1.water.enabledCells.map( + waterCell => [waterCell.cellKey, waterCell] as const, + ), + ); + // Priority-Flood correctly treats sea-level cells as outlets, but the + // deployed twelve-river DAG is immutable authority. Restore those exact + // downstream edges before accumulating the reconciled Greater Realm flow. + for (const waterCell of GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1.water.enabledCells) { + if (waterCell.regime !== 'river') continue; + const cell = grid.indexOf(transformLegacyLowlandsToGlobal(waterCell, legacy.transform)); + if (cell < 0) fail('GREATER_REALM_LEGACY_RIVER_ROUTING_MISSING'); + if (waterCell.downstreamWaterCellKey === undefined) { + receiver[cell] = -1; + continue; + } + const downstreamWaterCell = legacyWaterByKey.get(waterCell.downstreamWaterCellKey); + if (downstreamWaterCell?.regime !== 'river') { + fail('GREATER_REALM_LEGACY_RIVER_ROUTING_MISSING'); + } + const downstream = grid.indexOf(transformLegacyLowlandsToGlobal( + downstreamWaterCell, + legacy.transform, + )); + if (downstream < 0) fail('GREATER_REALM_LEGACY_RIVER_ROUTING_MISSING'); + receiver[cell] = downstream; + } + const routing = rebuildFlowRoutingFromReceivers(grid, receiver); + const flowAccumulation = accumulateGreaterRealmSingleFlow( + grid, + flood.filledElevation, + routing, + localContribution, + ); + return Object.freeze({ + bedrockElevation: adjustedBedrock, + elevation: adjustedElevation, + filledElevation: flood.filledElevation, + flowReceiver: routing.receiver, + flowAccumulation, + }); +} + +function rebuildFlowRoutingFromReceivers( + grid: IndexedAxialGrid, + receiver: Int32Array, +): GreaterRealmSingleFlowRouting { + if (receiver.length !== grid.cellCount) fail('GREATER_REALM_FLOW_INPUT_LENGTH_INVALID'); + const outlets = new Uint8Array(grid.cellCount); + const order = new Uint32Array(grid.cellCount); + const rank = new Uint32Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (receiver[cell] !== -1) continue; + outlets[cell] = 1; + queue[tail++] = cell; + } + let orderIndex = 0; + while (head < tail) { + const cell = queue[head++]!; + order[orderIndex] = cell; + rank[cell] = orderIndex; + orderIndex += 1; + const upstream: number[] = []; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && receiver[neighbor] === cell) upstream.push(neighbor); + } + upstream.sort((first, second) => first - second); + for (const upstreamCell of upstream) queue[tail++] = upstreamCell; + } + if (orderIndex !== grid.cellCount || tail !== grid.cellCount) { + fail('GREATER_REALM_FLOW_CYCLE'); + } + return Object.freeze({ receiver, order, rank, outlets }); +} + +type GreaterRealmStrategicFrontierEntry = Readonly<{ + cell: number; + depth: number; + priority: number; +}>; + +function strategicFrontierLess( + first: GreaterRealmStrategicFrontierEntry, + second: GreaterRealmStrategicFrontierEntry, +): boolean { + return first.priority < second.priority + || (first.priority === second.priority && first.depth < second.depth) + || ( + first.priority === second.priority + && first.depth === second.depth + && first.cell < second.cell + ); +} + +function strategicFrontierPush( + heap: GreaterRealmStrategicFrontierEntry[], + entry: GreaterRealmStrategicFrontierEntry, +): void { + let index = heap.length; + heap.push(entry); + while (index > 0) { + const parent = Math.floor((index - 1) / 2); + if (!strategicFrontierLess(entry, heap[parent]!)) break; + heap[index] = heap[parent]!; + index = parent; + } + heap[index] = entry; +} + +function strategicFrontierPop( + heap: GreaterRealmStrategicFrontierEntry[], +): GreaterRealmStrategicFrontierEntry | undefined { + const first = heap[0]; + const last = heap.pop(); + if (!first || !last || heap.length === 0) return first; + let index = 0; + while (true) { + const left = index * 2 + 1; + if (left >= heap.length) break; + const right = left + 1; + const child = right < heap.length && strategicFrontierLess(heap[right]!, heap[left]!) + ? right + : left; + if (!strategicFrontierLess(heap[child]!, last)) break; + heap[index] = heap[child]!; + index = child; + } + heap[index] = last; + return first; +} + +function growNaturalStrategicBasin( + grid: IndexedAxialGrid, + targetCount: number, + seeds: readonly number[], + blocked: Uint8Array, + terrainCost: Int32Array, + depthWeight: number, + countedCell?: Uint8Array, +): Uint8Array { + const selected = new Uint8Array(grid.cellCount); + const bestPriority = new Int32Array(grid.cellCount); + bestPriority.fill(0x7fff_ffff); + const heap: GreaterRealmStrategicFrontierEntry[] = []; + let selectedCount = 0; + + const offer = (cell: number, depth: number) => { + if (cell < 0 || blocked[cell] === 1 || selected[cell] === 1) return; + const priority = terrainCost[cell]! + depth * depthWeight; + if (priority >= bestPriority[cell]!) return; + bestPriority[cell] = priority; + strategicFrontierPush(heap, Object.freeze({ cell, depth, priority })); + }; + for (const seed of [...seeds].sort((first, second) => first - second)) { + if (seed < 0 || blocked[seed] === 1 || selected[seed] === 1) continue; + selected[seed] = 1; + selectedCount += countedCell ? countedCell[seed]! : 1; + } + if (seeds.length === 0 || selectedCount > targetCount) { + fail('GREATER_REALM_STRATEGIC_BASIN_SEED_INVALID'); + } + for (const seed of seeds) { + if (selected[seed] !== 1) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + offer(grid.neighbors[seed * HEX_NEIGHBOR_COUNT + direction]!, 1); + } + } + while (selectedCount < targetCount) { + let entry = strategicFrontierPop(heap); + if (!entry) { + // Natural masks can contain islands separated by blocked water/reserve + // bands. Restart from the cheapest remaining basin cell so construction + // is total; coherence/island metrics decide whether the result is fit. + let restart = -1; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (blocked[cell] === 1 || selected[cell] === 1) continue; + if ( + restart < 0 + || terrainCost[cell]! < terrainCost[restart]! + || (terrainCost[cell] === terrainCost[restart] && cell < restart) + ) restart = cell; + } + if (restart < 0) { + rejectGreaterRealmCandidate('GREATER_REALM_STRATEGIC_BASIN_CAPACITY_INVARIANT'); + } + entry = Object.freeze({ cell: restart, depth: 0, priority: terrainCost[restart]! }); + bestPriority[restart] = entry.priority; + } + if ( + selected[entry.cell] === 1 + || bestPriority[entry.cell] !== entry.priority + ) continue; + selected[entry.cell] = 1; + selectedCount += countedCell ? countedCell[entry.cell]! : 1; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + offer( + grid.neighbors[entry.cell * HEX_NEIGHBOR_COUNT + direction]!, + entry.depth + 1, + ); + } + } + return selected; +} + +function rebalanceCoherentStrategicGroups( + grid: IndexedAxialGrid, + cells: readonly number[], + groupCount: number, + costs: Int32Array, + forcedGroup: Int8Array, + initialAssignment: Uint8Array, +): Uint8Array { + const assignment = new Uint8Array(initialAssignment); + const ordinalByCell = new Int32Array(grid.cellCount); + ordinalByCell.fill(-1); + const counts = new Uint32Array(groupCount); + const targets = new Uint32Array(groupCount); + for (let group = 0; group < groupCount; group += 1) { + targets[group] = Math.floor(cells.length / groupCount) + + (group < cells.length % groupCount ? 1 : 0); + } + for (let ordinal = 0; ordinal < cells.length; ordinal += 1) { + const cell = cells[ordinal]!; + ordinalByCell[cell] = ordinal; + counts[assignment[cell]!] += 1; + } + + type Transfer = Readonly<{ + cell: number; + from: number; + to: number; + priority: number; + }>; + const heap: Transfer[] = []; + const less = (first: Transfer, second: Transfer) => first.priority < second.priority + || (first.priority === second.priority && first.cell < second.cell) + || (first.priority === second.priority && first.cell === second.cell && first.to < second.to); + const push = (entry: Transfer) => { + let index = heap.length; + heap.push(entry); + while (index > 0) { + const parent = Math.floor((index - 1) / 2); + if (!less(entry, heap[parent]!)) break; + heap[index] = heap[parent]!; + index = parent; + } + heap[index] = entry; + }; + const pop = (): Transfer | undefined => { + if (heap.length === 0) return undefined; + const first = heap[0]!; + const last = heap.pop()!; + if (heap.length === 0) return first; + let index = 0; + while (true) { + const left = index * 2 + 1; + if (left >= heap.length) break; + const right = left + 1; + const child = right < heap.length && less(heap[right]!, heap[left]!) ? right : left; + if (!less(heap[child]!, last)) break; + heap[index] = heap[child]!; + index = child; + } + heap[index] = last; + return first; + }; + const offer = (cell: number, to: number) => { + const ordinal = ordinalByCell[cell]!; + if (ordinal < 0 || forcedGroup[cell]! >= 0) return; + const from = assignment[cell]!; + if (from === to || counts[from]! <= targets[from]! || counts[to]! >= targets[to]!) return; + let adjacentToTarget = 0; + let adjacentToSource = 0; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0) continue; + if (assignment[neighbor] === to) adjacentToTarget += 1; + else if (assignment[neighbor] === from) adjacentToSource += 1; + } + if (adjacentToTarget === 0) return; + const delta = costs[ordinal * groupCount + to]! - costs[ordinal * groupCount + from]!; + // Peel coherent boundary bands instead of teleporting isolated cells. The + // terrain cost keeps those bands attached to the same natural basin field. + const priority = delta + adjacentToSource * 360 - adjacentToTarget * 520; + push(Object.freeze({ cell, from, to, priority })); + }; + for (const cell of cells) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && ordinalByCell[neighbor]! >= 0) offer(cell, assignment[neighbor]!); + } + } + let remaining = [...counts].reduce( + (total, count, group) => total + Math.max(0, count - targets[group]!), + 0, + ); + while (remaining > 0) { + let transfer = pop(); + if (!transfer) { + // A sea channel can disconnect two otherwise valid continental basins. + // Rebalance the best whole-envelope cell deterministically instead of + // aborting the candidate; later land-coherence gates reject a bad island + // allocation, while water-envelope quotas remain total and reproducible. + let fallback: Transfer | undefined; + for (const cell of cells) { + const ordinal = ordinalByCell[cell]!; + const from = assignment[cell]!; + if (forcedGroup[cell]! >= 0 || counts[from]! <= targets[from]!) continue; + for (let to = 0; to < groupCount; to += 1) { + if (counts[to]! >= targets[to]!) continue; + const priority = costs[ordinal * groupCount + to]! + - costs[ordinal * groupCount + from]!; + const candidate = Object.freeze({ cell, from, to, priority }); + if (!fallback || less(candidate, fallback)) fallback = candidate; + } + } + if (!fallback) fail('GREATER_REALM_STRATEGIC_GROUP_REBALANCE_INVARIANT'); + assignment[fallback.cell] = fallback.to; + counts[fallback.from] -= 1; + counts[fallback.to] += 1; + remaining -= 1; + continue; + } + if ( + assignment[transfer.cell] !== transfer.from + || counts[transfer.from]! <= targets[transfer.from]! + || counts[transfer.to]! >= targets[transfer.to]! + ) continue; + let stillAdjacent = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[transfer.cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && assignment[neighbor] === transfer.to) { + stillAdjacent = true; + break; + } + } + if (!stillAdjacent) continue; + assignment[transfer.cell] = transfer.to; + counts[transfer.from] -= 1; + counts[transfer.to] += 1; + remaining -= 1; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[transfer.cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || ordinalByCell[neighbor]! < 0) continue; + offer(neighbor, transfer.to); + offer(transfer.cell, assignment[neighbor]!); + } + } + return assignment; +} + +function assignCoherentStrategicGroups( + grid: IndexedAxialGrid, + cells: readonly number[], + groupCount: number, + costs: Int32Array, + forcedGroup: Int8Array, +): Uint8Array { + if (costs.length !== cells.length * groupCount || forcedGroup.length !== grid.cellCount) { + fail('GREATER_REALM_STRATEGIC_GROUP_INPUT_INVALID'); + } + const initialAssignment = new Uint8Array(grid.cellCount); + initialAssignment.fill(0xff); + for (let ordinal = 0; ordinal < cells.length; ordinal += 1) { + const cell = cells[ordinal]!; + const fixed = forcedGroup[cell]!; + if (fixed >= groupCount) fail('GREATER_REALM_STRATEGIC_GROUP_FORCE_INVALID'); + let selected = fixed; + if (selected < 0) { + selected = 0; + let selectedCost = costs[ordinal * groupCount]!; + for (let group = 1; group < groupCount; group += 1) { + const cost = costs[ordinal * groupCount + group]!; + if (cost < selectedCost || (cost === selectedCost && group < selected)) { + selected = group; + selectedCost = cost; + } + } + } + initialAssignment[cell] = selected; + } + return rebalanceCoherentStrategicGroups( + grid, + cells, + groupCount, + costs, + forcedGroup, + initialAssignment, + ); +} + +function assignTiersAndRegions( + grid: IndexedAxialGrid, + candidateSeed: GreaterRealmTerrainSeed, + elevation: Int32Array, + uplift: Int32Array, + domainId: Uint8Array, + flowAccumulation: BigUint64Array, + legacyReserveCell: Uint8Array, +): Readonly<{ + tierId: Uint8Array; + regionId: Uint8Array; + tierCounts: readonly [number, number, number]; + regionCounts: readonly number[]; +}> { + const tierIIIShare = 400 + ( + greaterRealmCounterRandomU32( + candidateSeed, + greaterRealmTerrainChannelId('tier-three-share'), + 0, + 0, + ) % 151 + ); + const tierIIShare = 2_250 + ( + greaterRealmCounterRandomU32( + candidateSeed, + greaterRealmTerrainChannelId('tier-two-share'), + 0, + 0, + ) % 401 + ); + const tierThreeCount = Math.round((grid.cellCount * tierIIIShare) / 10_000); + const tierTwoCount = Math.round((grid.cellCount * tierIIShare) / 10_000); + let reservedCount = 0; + for (const reserved of legacyReserveCell) reservedCount += reserved === 1 ? 1 : 0; + if (reservedCount >= grid.cellCount - tierThreeCount - tierTwoCount) { + rejectGreaterRealmCandidate('GREATER_REALM_LEGACY_LOWLANDS_RESERVE_TOO_LARGE'); + } + const basinField = createGreaterRealmMultiscaleIntegerField(grid, candidateSeed, [ + { channel: 'strategic-tier-basin-macro', amplitude: 5_600, smoothingPasses: 18, selfWeight: 3 }, + { channel: 'strategic-tier-basin-local', amplitude: 1_800, smoothingPasses: 5, selfWeight: 2 }, + ]); + const terrainCost = new Int32Array(grid.cellCount); + const boundaryMask = new Uint8Array(grid.cellCount); + let activeQTotal = 0; + let activeRTotal = 0; + let legacyQTotal = 0; + let legacyRTotal = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + activeQTotal += grid.q[cell]!; + activeRTotal += grid.r[cell]!; + if (legacyReserveCell[cell] === 1) { + legacyQTotal += grid.q[cell]!; + legacyRTotal += grid.r[cell]!; + } + let flowMagnitude = 0; + for (let flow = flowAccumulation[cell]!; flow > 1n; flow >>= 1n) flowMagnitude += 1; + const elevationValue = elevation[cell]!; + const waterPenalty = elevationValue <= SEA_LEVEL + ? 7_000 + Math.min(8_000, Math.floor(Math.abs(elevationValue) / 3)) + : 0; + const basinElevation = elevationValue > SEA_LEVEL + ? Math.min(9_000, Math.floor(Math.abs(elevationValue - 1_400) / 3)) + : 0; + const highlandPenalty = Math.floor(Math.max(0, uplift[cell]!) / 3); + terrainCost[cell] = clamp( + basinField[cell]! + waterPenalty + basinElevation + highlandPenalty - flowMagnitude * 150, + -30_000, + 60_000, + ); + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction] === -1) { + boundaryMask[cell] = 1; + break; + } + } + } + const distanceToActiveBoundary = distanceFromMask(grid, boundaryMask); + const activeCenterQ = Math.round(activeQTotal / grid.cellCount); + const activeCenterR = Math.round(activeRTotal / grid.cellCount); + const legacyCenterQ = Math.round(legacyQTotal / reservedCount); + const legacyCenterR = Math.round(legacyRTotal / reservedCount); + let tierThreeSeed = -1; + let tierThreeSeedScore = Number.POSITIVE_INFINITY; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + legacyReserveCell[cell] === 1 + || elevation[cell]! <= SEA_LEVEL + || distanceToActiveBoundary[cell]! < 42 + || axialDistance(grid.q[cell]!, grid.r[cell]!, legacyCenterQ, legacyCenterR) < 72 + ) continue; + const centerDistance = axialDistance( + grid.q[cell]!, + grid.r[cell]!, + activeCenterQ, + activeCenterR, + ); + const score = terrainCost[cell]! + centerDistance * 18; + if (score < tierThreeSeedScore || (score === tierThreeSeedScore && cell < tierThreeSeed)) { + tierThreeSeed = cell; + tierThreeSeedScore = score; + } + } + if (tierThreeSeed < 0) { + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (legacyReserveCell[cell] === 1 || elevation[cell]! <= SEA_LEVEL) continue; + if ( + tierThreeSeed < 0 + || terrainCost[cell]! < terrainCost[tierThreeSeed]! + || (terrainCost[cell] === terrainCost[tierThreeSeed] && cell < tierThreeSeed) + ) tierThreeSeed = cell; + } + } + if (tierThreeSeed < 0) { + rejectGreaterRealmCandidate('GREATER_REALM_TIER_THREE_CAPACITY_INVARIANT'); + } + + const tierThreeBlocked = new Uint8Array(legacyReserveCell); + const tierThreeMask = growNaturalStrategicBasin( + grid, + tierThreeCount, + [tierThreeSeed], + tierThreeBlocked, + terrainCost, + 94, + ); + const tierTwoSeeds: number[] = []; + const tierTwoBlocked = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierThreeMask[cell] === 1 || legacyReserveCell[cell] === 1) { + tierTwoBlocked[cell] = 1; + } + if (tierThreeMask[cell] !== 1) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierThreeMask[neighbor] !== 1 + && legacyReserveCell[neighbor] !== 1 + ) tierTwoSeeds.push(neighbor); + } + } + const tierTwoMask = growNaturalStrategicBasin( + grid, + tierTwoCount, + [...new Set(tierTwoSeeds)], + tierTwoBlocked, + terrainCost, + 52, + ); + const tierId = new Uint8Array(grid.cellCount); + tierId.fill(1); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierThreeMask[cell] === 1) tierId[cell] = 3; + else if (tierTwoMask[cell] === 1) tierId[cell] = 2; + } + + let tierTwoBoundary: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierId[cell] !== 2 || elevation[cell]! <= SEA_LEVEL) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && tierId[neighbor] === 3) { + tierTwoBoundary.push(cell); + break; + } + } + } + if (tierTwoBoundary.length < 3) { + tierTwoBoundary = Array.from({ length: grid.cellCount }, (_, cell) => cell) + .filter(cell => tierId[cell] === 2 && elevation[cell]! > SEA_LEVEL); + } + if (tierTwoBoundary.length < 3) { + tierTwoBoundary = Array.from({ length: grid.cellCount }, (_, cell) => cell) + .filter(cell => tierId[cell] === 2); + } + if (tierTwoBoundary.length < 3) { + rejectGreaterRealmCandidate('GREATER_REALM_TIER_TWO_CAPACITY_INVARIANT'); + } + const parentAnchors: number[] = []; + const firstParentAnchor = [...tierTwoBoundary].sort((first, second) => { + const score = (cell: number) => axialDistance( + grid.q[cell]!, + grid.r[cell]!, + legacyCenterQ, + legacyCenterR, + ) * 120 + terrainCost[cell]!; + return score(first) - score(second) || first - second; + })[0]!; + parentAnchors.push(firstParentAnchor); + while (parentAnchors.length < 3) { + let selected = -1; + let selectedScore = Number.NEGATIVE_INFINITY; + for (const cell of tierTwoBoundary) { + if (parentAnchors.includes(cell)) continue; + const separation = Math.min(...parentAnchors.map(anchor => axialDistance( + grid.q[cell]!, + grid.r[cell]!, + grid.q[anchor]!, + grid.r[anchor]!, + ))); + const score = separation * 1_000 - terrainCost[cell]!; + if (score > selectedScore || (score === selectedScore && cell < selected)) { + selected = cell; + selectedScore = score; + } + } + if (selected < 0) fail('GREATER_REALM_PARENT_BASIN_ANCHOR_MISSING'); + parentAnchors.push(selected); + } + const parentNoise = Array.from( + { length: 3 }, + (_, parent) => createGreaterRealmMultiscaleIntegerField(grid, candidateSeed, [ + { + channel: `strategic-parent-basin-${parent}`, + amplitude: 1_200, + smoothingPasses: 9, + selfWeight: 3, + }, + ]), + ); + const regionId = new Uint8Array(grid.cellCount); + const parentId = new Uint8Array(grid.cellCount); + parentId.fill(0xff); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierId[cell] === 3) regionId[cell] = TIER_III_REGION_INDEX; + } + const strategicCells = Array.from({ length: grid.cellCount }, (_, cell) => cell) + .filter(cell => tierId[cell] !== 3); + const parentCosts = new Int32Array(strategicCells.length * parentAnchors.length); + for (let ordinal = 0; ordinal < strategicCells.length; ordinal += 1) { + const cell = strategicCells[ordinal]!; + for (let parent = 0; parent < parentAnchors.length; parent += 1) { + const anchor = parentAnchors[parent]!; + const domainAffinity = domainId[cell] === domainId[anchor] ? -300 : 300; + parentCosts[ordinal * parentAnchors.length + parent] = + axialDistance( + grid.q[cell]!, + grid.r[cell]!, + grid.q[anchor]!, + grid.r[anchor]!, + ) * 92 + + parentNoise[parent]![cell]! + + domainAffinity + + Math.floor(Math.abs(elevation[cell]! - elevation[anchor]!) / 300); + } + } + const tierOneCells = strategicCells.filter(cell => tierId[cell] === 1); + const tierTwoCells = strategicCells.filter(cell => tierId[cell] === 2); + // Every middle realm must span the full strategic ring: one dry anchor at + // the Throneheart frontier and one at the outer-realm frontier, joined by a + // compact dry spine. Without this constraint, a balanced Voronoi envelope + // can consume its entire quota beside Tier III and leave one parent with no + // possible outer gate at all. Try all parent orders so the three spines are + // vertex-disjoint; if geography makes that impossible, still pin distinct + // outer anchors and let the coherent partition grow toward them. + const tierTwoOuterDryBoundary = tierTwoCells.filter(cell => { + if (elevation[cell]! <= SEA_LEVEL) return false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[neighbor] === 1 + && elevation[neighbor]! > SEA_LEVEL + ) return true; + } + return false; + }); + const outerDryMask = new Uint8Array(grid.cellCount); + for (const cell of tierTwoOuterDryBoundary) outerDryMask[cell] = 1; + const parentSpineOrders = [ + [0, 1, 2], + [0, 2, 1], + [1, 0, 2], + [1, 2, 0], + [2, 0, 1], + [2, 1, 0], + ] as const; + let parentTierTwoSpines: number[][] | undefined; + if (tierTwoOuterDryBoundary.length >= 3) { + for (const order of parentSpineOrders) { + const claimed = new Uint8Array(grid.cellCount); + const paths = Array.from({ length: 3 }, () => [] as number[]); + let complete = true; + for (const parent of order) { + const start = parentAnchors[parent]!; + const previous = new Int32Array(grid.cellCount); + previous.fill(-2); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + let target = -1; + previous[start] = -1; + queue[tail++] = start; + while (head < tail && target < 0) { + const cell = queue[head++]!; + if (cell !== start && outerDryMask[cell] === 1 && claimed[cell] === 0) { + target = cell; + break; + } + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || previous[neighbor] !== -2 + || claimed[neighbor] === 1 + || tierId[neighbor] !== 2 + || elevation[neighbor]! <= SEA_LEVEL + || parentAnchors.some((anchor, owner) => owner !== parent && anchor === neighbor) + ) continue; + previous[neighbor] = cell; + queue[tail++] = neighbor; + } + } + if (target < 0) { + complete = false; + break; + } + for (let cell = target; cell >= 0; cell = previous[cell]!) { + paths[parent]!.push(cell); + } + paths[parent]!.reverse(); + for (const cell of paths[parent]!) claimed[cell] = 1; + } + if (complete) { + parentTierTwoSpines = paths; + break; + } + } + } + if (!parentTierTwoSpines) { + const usedOuter = new Set(); + parentTierTwoSpines = parentAnchors.map((anchor) => { + const outer = [...tierTwoOuterDryBoundary] + .filter(cell => !usedOuter.has(cell)) + .sort((first, second) => ( + axialDistance( + grid.q[first]!, + grid.r[first]!, + grid.q[anchor]!, + grid.r[anchor]!, + ) - axialDistance( + grid.q[second]!, + grid.r[second]!, + grid.q[anchor]!, + grid.r[anchor]!, + ) || terrainCost[first]! - terrainCost[second]! || first - second + ))[0]; + if (outer === undefined) return [anchor]; + usedOuter.add(outer); + return outer === anchor ? [anchor] : [anchor, outer]; + }); + } + const costsFor = (cells: readonly number[]) => { + const byCell = new Map(strategicCells.map((cell, ordinal) => [cell, ordinal] as const)); + const costs = new Int32Array(cells.length * parentAnchors.length); + for (let ordinal = 0; ordinal < cells.length; ordinal += 1) { + const source = byCell.get(cells[ordinal]!)!; + for (let parent = 0; parent < parentAnchors.length; parent += 1) { + costs[ordinal * parentAnchors.length + parent] = + parentCosts[source * parentAnchors.length + parent]!; + } + } + return costs; + }; + const forcedTierOneParent = new Int8Array(grid.cellCount); + forcedTierOneParent.fill(-1); + for (const cell of tierOneCells) { + if (legacyReserveCell[cell] === 1) forcedTierOneParent[cell] = 0; + } + const forcedTierTwoParent = new Int8Array(grid.cellCount); + forcedTierTwoParent.fill(-1); + const anchorOnlyTierTwoParent = new Int8Array(grid.cellCount); + anchorOnlyTierTwoParent.fill(-1); + for (let parent = 0; parent < parentAnchors.length; parent += 1) { + anchorOnlyTierTwoParent[parentAnchors[parent]!] = parent; + for (const cell of parentTierTwoSpines[parent]!) { + forcedTierTwoParent[cell] = parent; + } + } + const baselineTierTwoParents = assignCoherentStrategicGroups( + grid, + tierTwoCells, + parentAnchors.length, + costsFor(tierTwoCells), + anchorOnlyTierTwoParent, + ); + const baselineOuterDryContact = new Uint8Array(parentAnchors.length); + for (const cell of tierTwoOuterDryBoundary) { + baselineOuterDryContact[baselineTierTwoParents[cell]!] = 1; + } + const tierTwoParents = baselineOuterDryContact.every(contact => contact === 1) + ? baselineTierTwoParents + : assignCoherentStrategicGroups( + grid, + tierTwoCells, + parentAnchors.length, + costsFor(tierTwoCells), + forcedTierTwoParent, + ); + for (const cell of tierTwoCells) parentId[cell] = tierTwoParents[cell]!; + const tierOneAnchors: number[] = []; + for (let parent = 0; parent < parentAnchors.length; parent += 1) { + const candidates = tierOneCells.filter(cell => { + if (elevation[cell]! <= SEA_LEVEL) return false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[neighbor] === 2 + && parentId[neighbor] === parent + && elevation[neighbor]! > SEA_LEVEL + ) return true; + } + return false; + }); + const fallback = candidates.length > 0 + ? candidates + : tierOneCells.filter(cell => { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && tierId[neighbor] === 2 && parentId[neighbor] === parent) return true; + } + return false; + }); + const anchor = [...(fallback.length > 0 ? fallback : tierOneCells)].sort((first, second) => ( + axialDistance( + grid.q[first]!, + grid.r[first]!, + grid.q[parentAnchors[parent]!]!, + grid.r[parentAnchors[parent]!]!, + ) - axialDistance( + grid.q[second]!, + grid.r[second]!, + grid.q[parentAnchors[parent]!]!, + grid.r[parentAnchors[parent]!]!, + ) || terrainCost[first]! - terrainCost[second]! || first - second + ))[0]!; + tierOneAnchors.push(anchor); + forcedTierOneParent[anchor] = parent; + } + // Preserve one dry Lowlands-to-Crown spine before the capacity-balanced + // partition. The later child split widens its end into a natural gate basin. + const lowlandsPrevious = new Int32Array(grid.cellCount); + lowlandsPrevious.fill(-2); + const lowlandsQueue = new Uint32Array(grid.cellCount); + let lowlandsHead = 0; + let lowlandsTail = 0; + for (const cell of tierOneCells) { + if (legacyReserveCell[cell] !== 1 || elevation[cell]! <= SEA_LEVEL) continue; + lowlandsPrevious[cell] = -1; + lowlandsQueue[lowlandsTail++] = cell; + } + while (lowlandsHead < lowlandsTail && lowlandsPrevious[tierOneAnchors[0]!] === -2) { + const cell = lowlandsQueue[lowlandsHead++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || tierId[neighbor] !== 1 + || elevation[neighbor]! <= SEA_LEVEL + || lowlandsPrevious[neighbor] !== -2 + ) continue; + lowlandsPrevious[neighbor] = cell; + lowlandsQueue[lowlandsTail++] = neighbor; + } + } + if (lowlandsPrevious[tierOneAnchors[0]!] !== -2) { + for ( + let cell = tierOneAnchors[0]!; + cell >= 0; + cell = lowlandsPrevious[cell]! + ) forcedTierOneParent[cell] = 0; + } + const tierOneParents = assignCoherentStrategicGroups( + grid, + tierOneCells, + parentAnchors.length, + costsFor(tierOneCells), + forcedTierOneParent, + ); + for (const cell of tierOneCells) parentId[cell] = tierOneParents[cell]!; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierId[cell] === 2) regionId[cell] = TIER_I_REGION_COUNT + parentId[cell]!; + } + for (let parent = 0; parent < parentAnchors.length; parent += 1) { + const cells = strategicCells.filter(cell => tierId[cell] === 1 && parentId[cell] === parent); + const children = [parent * 2, parent * 2 + 1] as const; + const rawParentTierTwoBoundary = cells.filter(cell => { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[neighbor] === 2 + && parentId[neighbor] === parent + ) return true; + } + return false; + }); + const dryParentTierTwoBoundary = rawParentTierTwoBoundary.filter(cell => { + if (elevation[cell]! <= SEA_LEVEL) return false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[neighbor] === 2 + && parentId[neighbor] === parent + && elevation[neighbor]! > SEA_LEVEL + ) return true; + } + return false; + }); + const parentTierTwoBoundary = dryParentTierTwoBoundary.length >= 2 + ? dryParentTierTwoBoundary + : rawParentTierTwoBoundary.length >= 2 + ? rawParentTierTwoBoundary + : cells; + const firstChildAnchor = parent === 0 + ? [...parentTierTwoBoundary].sort((first, second) => { + const score = (cell: number) => axialDistance( + grid.q[cell]!, + grid.r[cell]!, + legacyCenterQ, + legacyCenterR, + ) * 100 + terrainCost[cell]!; + return score(first) - score(second) || first - second; + })[0]! + : [...parentTierTwoBoundary].sort((first, second) => ( + terrainCost[first]! - terrainCost[second]! || first - second + ))[0]!; + const secondChildAnchor = [...parentTierTwoBoundary].sort((first, second) => { + const score = (cell: number) => axialDistance( + grid.q[cell]!, + grid.r[cell]!, + grid.q[firstChildAnchor]!, + grid.r[firstChildAnchor]!, + ) * 1_000 - terrainCost[cell]!; + return score(second) - score(first) || first - second; + })[0]!; + const lowlandsApproach = new Set(); + if (parent === 0) { + const allowed = new Uint8Array(grid.cellCount); + const target = new Uint8Array(grid.cellCount); + const previous = new Int32Array(grid.cellCount); + previous.fill(-2); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (const cell of cells) { + if (elevation[cell]! <= SEA_LEVEL) continue; + allowed[cell] = 1; + if (legacyReserveCell[cell] === 1) { + previous[cell] = -1; + queue[tail++] = cell; + } + } + target[firstChildAnchor] = 1; + let reached = -1; + while (head < tail && reached < 0) { + const cell = queue[head++]!; + if (target[cell] === 1) { + reached = cell; + break; + } + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || allowed[neighbor] !== 1 || previous[neighbor] !== -2) continue; + previous[neighbor] = cell; + queue[tail++] = neighbor; + } + } + if (reached >= 0) { + for (let cell = reached; cell >= 0; cell = previous[cell]!) lowlandsApproach.add(cell); + } else { + for (const cell of cells) { + if (legacyReserveCell[cell] === 1) lowlandsApproach.add(cell); + } + } + } + const blocked = new Uint8Array(grid.cellCount); + blocked.fill(1); + for (const cell of cells) { + blocked[cell] = 0; + } + const firstChildCore = growNaturalStrategicBasin( + grid, + Math.min(960, Math.max(1, Math.floor(cells.length / 16))), + [firstChildAnchor], + blocked, + terrainCost, + 48, + ); + for (const cell of cells) { + if (firstChildCore[cell] === 1) blocked[cell] = 1; + } + const childSeeds = [secondChildAnchor]; + const reachable = new Uint8Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + const floodComponent = (start: number) => { + let head = 0; + let tail = 0; + const component: number[] = []; + reachable[start] = 1; + queue[tail++] = start; + while (head < tail) { + const cell = queue[head++]!; + component.push(cell); + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || blocked[neighbor] === 1 || reachable[neighbor] === 1) continue; + reachable[neighbor] = 1; + queue[tail++] = neighbor; + } + } + return component; + }; + floodComponent(secondChildAnchor); + for (const cell of cells) { + if (blocked[cell] === 1 || reachable[cell] === 1) continue; + const component = floodComponent(cell); + component.sort((first, second) => terrainCost[first]! - terrainCost[second]! || first - second); + childSeeds.push(component[0]!); + } + const countedLand = new Uint8Array(grid.cellCount); + let parentLandCount = 0; + for (const cell of cells) { + if (elevation[cell]! <= SEA_LEVEL) continue; + countedLand[cell] = 1; + parentLandCount += 1; + } + const desiredSecondCount = Math.floor(cells.length / 2); + const secondChildBasin = growNaturalStrategicBasin( + grid, + Math.floor(parentLandCount / 2), + childSeeds, + blocked, + terrainCost, + 72, + countedLand, + ); + let secondCount = cells.reduce((total, cell) => total + secondChildBasin[cell]!, 0); + if (secondCount > desiredSecondCount) { + const removableWater = cells.filter(cell => ( + secondChildBasin[cell] === 1 + && elevation[cell]! <= SEA_LEVEL + && cell !== secondChildAnchor + )); + removableWater.sort((first, second) => { + const score = (cell: number) => axialDistance( + grid.q[cell]!, + grid.r[cell]!, + grid.q[secondChildAnchor]!, + grid.r[secondChildAnchor]!, + ) * 80 + terrainCost[cell]!; + return score(second) - score(first) || second - first; + }); + for (const cell of removableWater) { + if (secondCount <= desiredSecondCount) break; + secondChildBasin[cell] = 0; + secondCount -= 1; + } + } else if (secondCount < desiredSecondCount) { + const additionalWater = cells.filter(cell => ( + secondChildBasin[cell] !== 1 + && blocked[cell] !== 1 + && elevation[cell]! <= SEA_LEVEL + )); + additionalWater.sort((first, second) => { + const score = (cell: number) => axialDistance( + grid.q[cell]!, + grid.r[cell]!, + grid.q[secondChildAnchor]!, + grid.r[secondChildAnchor]!, + ) * 80 + terrainCost[cell]!; + return score(first) - score(second) || first - second; + }); + for (const cell of additionalWater) { + if (secondCount >= desiredSecondCount) break; + secondChildBasin[cell] = 1; + secondCount += 1; + } + } + let retainedSecondCount = cells.reduce((total, cell) => total + ( + secondChildBasin[cell] === 1 + && firstChildCore[cell] !== 1 + && !(parent === 0 && (legacyReserveCell[cell] === 1 || lowlandsApproach.has(cell))) + ? 1 + : 0 + ), 0); + if (retainedSecondCount < desiredSecondCount) { + const expansionCandidates = cells.filter(cell => ( + secondChildBasin[cell] !== 1 + && firstChildCore[cell] !== 1 + && !(parent === 0 && (legacyReserveCell[cell] === 1 || lowlandsApproach.has(cell))) + )); + expansionCandidates.sort((first, second) => { + const score = (cell: number) => axialDistance( + grid.q[cell]!, + grid.r[cell]!, + grid.q[secondChildAnchor]!, + grid.r[secondChildAnchor]!, + ) * 96 + terrainCost[cell]!; + return score(first) - score(second) || first - second; + }); + for (const cell of expansionCandidates) { + if (retainedSecondCount >= desiredSecondCount) break; + secondChildBasin[cell] = 1; + retainedSecondCount += 1; + } + } + for (const cell of cells) { + regionId[cell] = ( + secondChildBasin[cell] === 1 + && firstChildCore[cell] !== 1 + && !(parent === 0 && (legacyReserveCell[cell] === 1 || lowlandsApproach.has(cell))) + ) ? children[1] : children[0]; + } + } + const tierCounts: [number, number, number] = [0, 0, 0]; + const regionCounts = Array(REGION_COUNT).fill(0); + for (let index = 0; index < grid.cellCount; index += 1) { + tierCounts[tierId[index]! - 1] += 1; + regionCounts[regionId[index]!] += 1; + } + return Object.freeze({ tierId, regionId, tierCounts, regionCounts: Object.freeze(regionCounts) }); +} + +function remapTierOneNaturalBasinsByCharacter( + grid: IndexedAxialGrid, + provisional: Readonly<{ + tierId: Uint8Array; + regionId: Uint8Array; + tierCounts: readonly [number, number, number]; + }>, + elevation: Int32Array, + uplift: Int32Array, + flowAccumulation: BigUint64Array, + domainId: Uint8Array, + domains: readonly GreaterRealmPseudoTectonicDomain[], + waterRegime: Uint8Array, + temperatureField: Int32Array, + moistureField: Int32Array, + glacialMask: Uint8Array, + aridMask: Uint8Array, + volcanicMask: Uint8Array, + coastalMask: Uint8Array, + coastalClass: Uint8Array, +): Readonly<{ + tierId: Uint8Array; + regionId: Uint8Array; + tierCounts: readonly [number, number, number]; + regionCounts: readonly number[]; + semanticPermutation: readonly number[]; + gateGraph: readonly GreaterRealmGateGraphEdge[]; +}> { + // Provisional T1 basin IDs carry topology only. Working identities are + // assigned afterward from climate/process evidence so no named region is + // created by painting a biome into a preselected wedge. + const scoreSums = Array.from({ length: TIER_I_REGION_COUNT }, () => ( + Array(TIER_I_REGION_COUNT - 1).fill(0n) + )); + const landCounts = new Uint32Array(TIER_I_REGION_COUNT); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const basin = provisional.regionId[cell]!; + if (provisional.tierId[cell] !== 1 || basin === 0 || elevation[cell]! <= SEA_LEVEL) continue; + landCounts[basin] += 1; + const temperature = temperatureField[cell]!; + const moisture = moistureField[cell]!; + let flowMagnitude = 0; + for (let flow = flowAccumulation[cell]!; flow > 1n; flow >>= 1n) flowMagnitude += 1; + let coastNeighbors = 0; + let wetNeighbors = 0; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0) continue; + if ( + waterRegime[neighbor] === WATER_OCEAN + || waterRegime[neighbor] === WATER_SEA + || waterRegime[neighbor] === WATER_LAKE + ) coastNeighbors += 1; + if (waterRegime[neighbor] >= 2) wetNeighbors += 1; + } + const domain = domains[domainId[cell]!]!; + const scores = [ + Math.max(0, 5_000 - temperature) * 3 + + Math.floor(Math.max(0, elevation[cell]! - 7_000) / 3) + + glacialMask[cell]! * 12_000, + Math.max(0, temperature - 3_500) * 2 + + Math.max(0, -moisture - 500) * 2 + + aridMask[cell]! * 12_000 + + Math.max(0, 8 - flowMagnitude) * 180, + flowMagnitude * 520 + + wetNeighbors * 1_500 + + Math.max(0, moisture) * 2 + + (coastalClass[cell] === 3 ? 8_000 : 0), + coastNeighbors * 3_400 + + coastalMask[cell]! * 5_000 + + (coastalClass[cell] > 0 ? 2_500 : 0) + + Math.floor(Math.max(0, elevation[cell]!) / 8), + Math.floor(domain.volcanicPotential * 3) + + Math.floor(Math.max(0, uplift[cell]!) / 2) + + volcanicMask[cell]! * 12_000, + ] as const; + for (let role = 0; role < scores.length; role += 1) { + scoreSums[basin]![role] += BigInt(scores[role]!); + } + } + const normalizedScores = scoreSums.map((scores, basin) => scores.map(score => { + const count = landCounts[basin]!; + return count > 0 ? score / BigInt(count) : 0n; + })); + for (let basin = 1; basin < TIER_I_REGION_COUNT; basin += 1) { + const passableMask = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + provisional.regionId[cell] === basin + && strategicallyPassableSurface(waterRegime[cell]!) + ) passableMask[cell] = 1; + } + const components = connectedComponents(grid, passableMask); + const total = components.reduce((sum, component) => sum + component.length, 0); + const largest = components.reduce( + (maximum, component) => Math.max(maximum, component.length), + 0, + ); + const meaningfulIslands = components.filter(component => component.length >= 64).length; + const islandiness = total === 0 ? 0 : 10_000 - Math.round((largest * 10_000) / total); + normalizedScores[basin]![3] += BigInt( + islandiness * 4 + Math.min(10, meaningfulIslands) * 500, + ); + } + let bestScore: bigint | undefined; + let bestPermutation: readonly number[] | undefined; + // Exactly 5! bounded integer assignments. Loop order is the complete + // lexicographic tie-break, so equal scores replay identically. + for (let first = 1; first <= 5; first += 1) { + for (let second = 1; second <= 5; second += 1) { + if (second === first) continue; + for (let third = 1; third <= 5; third += 1) { + if (third === first || third === second) continue; + for (let fourth = 1; fourth <= 5; fourth += 1) { + if (fourth === first || fourth === second || fourth === third) continue; + for (let fifth = 1; fifth <= 5; fifth += 1) { + if ([first, second, third, fourth].includes(fifth)) continue; + const permutation = [0, first, second, third, fourth, fifth] as const; + let score = 0n; + for (let basin = 1; basin <= 5; basin += 1) { + score += normalizedScores[basin]![permutation[basin]! - 1]!; + } + if (bestScore === undefined || score > bestScore) { + bestScore = score; + bestPermutation = permutation; + } + } + } + } + } + } + if (!bestPermutation) fail('GREATER_REALM_SEMANTIC_REGION_REMAP_FAILED'); + const regionId = new Uint8Array(provisional.regionId); + const regionCounts = Array(REGION_COUNT).fill(0); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const provisionalRegion = provisional.regionId[cell]!; + if (provisionalRegion > 0 && provisionalRegion < TIER_I_REGION_COUNT) { + regionId[cell] = bestPermutation[provisionalRegion]!; + } + regionCounts[regionId[cell]!] += 1; + } + const gateGraph = GREATER_REALM_PROVISIONAL_GATE_GRAPH.map(([first, second]) => ( + Object.freeze([ + first > 0 && first < TIER_I_REGION_COUNT ? bestPermutation[first]! : first, + second, + ] as const) + )); + return Object.freeze({ + tierId: provisional.tierId, + regionId, + tierCounts: provisional.tierCounts, + regionCounts: Object.freeze(regionCounts), + semanticPermutation: Object.freeze([...bestPermutation]), + gateGraph: Object.freeze(gateGraph), + }); +} + +function connectedComponents( + grid: IndexedAxialGrid, + included: Uint8Array, +): readonly (readonly number[])[] { + const seen = new Uint8Array(grid.cellCount); + const components: number[][] = []; + const queue = new Uint32Array(grid.cellCount); + for (let start = 0; start < grid.cellCount; start += 1) { + if (included[start] !== 1 || seen[start] === 1) continue; + let head = 0; + let tail = 0; + queue[tail++] = start; + seen[start] = 1; + const component: number[] = []; + while (head < tail) { + const cell = queue[head++]!; + component.push(cell); + for (let directionIndex = 0; directionIndex < HEX_NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + directionIndex]!; + if (neighbor < 0 || included[neighbor] !== 1 || seen[neighbor] === 1) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + } + } + components.push(component); + } + return Object.freeze(components.map(component => Object.freeze(component))); +} + +function connectedComponentsAtEqualSurface( + grid: IndexedAxialGrid, + included: Uint8Array, + surface: Int32Array, +): readonly (readonly number[])[] { + const seen = new Uint8Array(grid.cellCount); + const components: number[][] = []; + const queue = new Uint32Array(grid.cellCount); + for (let start = 0; start < grid.cellCount; start += 1) { + if (included[start] !== 1 || seen[start] === 1) continue; + const level = surface[start]!; + let head = 0; + let tail = 0; + queue[tail++] = start; + seen[start] = 1; + const component: number[] = []; + while (head < tail) { + const cell = queue[head++]!; + component.push(cell); + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || included[neighbor] !== 1 + || seen[neighbor] === 1 + || surface[neighbor] !== level + ) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + } + } + components.push(component); + } + return Object.freeze(components.map(component => Object.freeze(component))); +} + +function repairNaturalRegionLandCoherence( + grid: IndexedAxialGrid, + strategy: Readonly<{ + tierId: Uint8Array; + regionId: Uint8Array; + tierCounts: readonly [number, number, number]; + regionCounts: readonly number[]; + semanticPermutation: readonly number[]; + gateGraph: readonly GreaterRealmGateGraphEdge[]; + }>, + waterRegime: Uint8Array, + legacyProtectedCell: Uint8Array, +): Readonly<{ + tierId: Uint8Array; + regionId: Uint8Array; + tierCounts: readonly [number, number, number]; + regionCounts: readonly number[]; + semanticPermutation: readonly number[]; + gateGraph: readonly GreaterRealmGateGraphEdge[]; +}> { + const tierId = new Uint8Array(strategy.tierId); + const regionId = new Uint8Array(strategy.regionId); + const initialCounts = [...strategy.regionCounts]; + const regionRepairLocked = new Uint8Array(grid.cellCount); + // Keep each region's dominant land body. A detached component is transferred + // only to a same-tier region it physically touches; truly isolated T1 + // islands belong to Stonewake. Equal non-passable water ownership is swapped + // back so the exact region/tier population contract is unchanged. + const repairOrder = [1, 2, 3, 5, 6, 7, 8, 9, 0, 4] as const; + for (const sourceRegion of repairOrder) { + const included = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + regionId[cell] === sourceRegion + && strategicallyPassableSurface(waterRegime[cell]!) + ) included[cell] = 1; + } + const components = [...connectedComponents(grid, included)] + .sort((first, second) => second.length - first.length || first[0]! - second[0]!); + if (components.length <= 1) continue; + const destinationTier = GREATER_REALM_REGION_SPECS[sourceRegion]!.tier + 1; + const tierContactCount = (component: readonly number[]) => { + let count = 0; + for (const cell of component) { + if (waterRegime[cell] !== 0) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[neighbor] === destinationTier + && waterRegime[neighbor] === 0 + ) count += 1; + } + } + return count; + }; + const largestHasTierContact = sourceRegion === TIER_III_REGION_INDEX + || tierContactCount(components[0]!) > 0; + const protectedGateComponent = largestHasTierContact + ? undefined + : components.slice(1) + .filter(component => component.length >= 64 && tierContactCount(component) > 0) + .sort((first, second) => ( + tierContactCount(second) - tierContactCount(first) + || second.length - first.length + || first[0]! - second[0]! + ))[0]; + // Repair to a deliberately stronger pre-barrier target. The later + // geological shoulder mask legitimately removes boundary cells; matching + // the final threshold here left ordinary candidates just below it. + const targetLargestShare = sourceRegion < TIER_I_REGION_COUNT + ? sourceRegion === 4 ? 7_000 : 8_750 + : sourceRegion < TIER_III_REGION_INDEX ? 9_250 : 9_500; + const targetMinorShare = sourceRegion === 4 ? 425 : 250; + const selectedComponents: Array = []; + let remainingPassable = components.reduce((total, component) => total + component.length, 0); + let remainingMinor = components.slice(1).reduce( + (total, component) => total + (component.length < 64 ? component.length : 0), + 0, + ); + for (const component of components.slice(1)) { + if (component === protectedGateComponent) continue; + if (components[0]!.length * 10_000 >= remainingPassable * targetLargestShare) break; + selectedComponents.push(component); + remainingPassable -= component.length; + if (component.length < 64) remainingMinor -= component.length; + } + const alreadySelected = new Set(selectedComponents); + const smallFragments = components.slice(1) + .filter(component => ( + component.length < 64 + && component !== protectedGateComponent + && !alreadySelected.has(component) + )) + .sort((first, second) => first.length - second.length || first[0]! - second[0]!); + for (const component of smallFragments) { + if (remainingMinor * 10_000 <= remainingPassable * targetMinorShare) break; + selectedComponents.push(component); + remainingPassable -= component.length; + remainingMinor -= component.length; + } + for (const component of selectedComponents) { + if (component.some(cell => ( + legacyProtectedCell[cell] === 1 + || ( + regionRepairLocked[cell] === 1 + && sourceRegion !== 4 + && sourceRegion !== 0 + ) + ))) continue; + const contacts = new Uint32Array(REGION_COUNT); + for (const cell of component) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || ( + sourceRegion === TIER_III_REGION_INDEX + ? tierId[neighbor] !== 2 + : tierId[neighbor] !== tierId[cell] + ) + || regionId[neighbor] === sourceRegion + ) continue; + contacts[regionId[neighbor]!] += 1; + } + } + let targetRegion = -1; + let bestContacts = 0; + if (sourceRegion === TIER_III_REGION_INDEX) { + // Throneheart is the only Tier III region, so an offshore fragment has + // no same-tier recipient. Move that land to the nearest adjacent + // middle realm and exchange equal non-passable ownership back into + // Tier III. Tier/region populations remain exact while the throne + // realm's traversable authority stays on one natural land body. + for (let region = TIER_I_REGION_COUNT; region < TIER_III_REGION_INDEX; region += 1) { + if ( + contacts[region]! > bestContacts + || ( + contacts[region] === bestContacts + && contacts[region]! > 0 + && region < targetRegion + ) + ) { + targetRegion = region; + bestContacts = contacts[region]!; + } + } + if (targetRegion < 0) { + let bestDistance = Number.POSITIVE_INFINITY; + for ( + let region = TIER_I_REGION_COUNT; + region < TIER_III_REGION_INDEX; + region += 1 + ) { + const targetMask = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (regionId[cell] === region) targetMask[cell] = 1; + } + const distance = distanceFromMask(grid, targetMask); + const minimum = component.reduce( + (value, cell) => Math.min(value, distance[cell]!), + Number.POSITIVE_INFINITY, + ); + if (minimum < bestDistance || (minimum === bestDistance && region < targetRegion)) { + targetRegion = region; + bestDistance = minimum; + } + } + } + } else { + for (let region = 0; region < REGION_COUNT; region += 1) { + if ( + region === sourceRegion + || GREATER_REALM_REGION_SPECS[region]!.tier + !== GREATER_REALM_REGION_SPECS[sourceRegion]!.tier + ) continue; + if (contacts[region]! > bestContacts) { + targetRegion = region; + bestContacts = contacts[region]!; + } + } + } + if (targetRegion < 0 && sourceRegion < TIER_I_REGION_COUNT) { + // A whole offshore island has no same-landmass neighbour. Assigning + // every such island to Stonewake made that realm an arbitrary bucket + // of disconnected terrain. Instead distribute isolated landmasses to + // the same-tier realm whose dominant body can absorb the fragment + // with the highest projected coherence. This remains deterministic + // and preserves each island as a single political unit. + const currentTopology = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + const currentMinorCells = new Uint32Array(REGION_COUNT); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const componentId = currentTopology.componentId[cell]!; + if ( + componentId >= 0 + && currentTopology.componentSizes[componentId]! < 64 + ) currentMinorCells[regionId[cell]!] += 1; + } + let bestProjectedShare = -1; + let bestMinorBurden = Number.POSITIVE_INFINITY; + for (let region = 0; region < TIER_I_REGION_COUNT; region += 1) { + if (region === sourceRegion) continue; + const projectedTotal = currentTopology.passableCounts[region]! + component.length; + const projectedShare = projectedTotal === 0 + ? 0 + : Math.floor( + (currentTopology.largestCounts[region]! * 10_000) / projectedTotal, + ); + const projectedMinorShare = projectedTotal === 0 + ? 10_000 + : Math.floor(( + currentMinorCells[region]! + + (component.length < 64 ? component.length : 0) + ) * 10_000 / projectedTotal); + const minorAllowance = region === 4 ? 425 : 250; + const minorBurden = Math.floor((projectedMinorShare * 10_000) / minorAllowance); + const preferable = component.length < 64 + ? minorBurden < bestMinorBurden + || (minorBurden === bestMinorBurden && projectedShare > bestProjectedShare) + || ( + minorBurden === bestMinorBurden + && projectedShare === bestProjectedShare + && region < targetRegion + ) + : projectedShare > bestProjectedShare + || (projectedShare === bestProjectedShare && minorBurden < bestMinorBurden) + || ( + projectedShare === bestProjectedShare + && minorBurden === bestMinorBurden + && region < targetRegion + ); + if (preferable) { + targetRegion = region; + bestProjectedShare = projectedShare; + bestMinorBurden = minorBurden; + } + } + } + if (targetRegion < 0 || targetRegion === sourceRegion) continue; + + const waterSwapCandidates: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + regionId[cell] !== targetRegion + || strategicallyPassableSurface(waterRegime[cell]!) + || legacyProtectedCell[cell] === 1 + ) continue; + waterSwapCandidates.push(cell); + } + if (waterSwapCandidates.length < component.length) continue; + waterSwapCandidates.sort((first, second) => { + const sourceContacts = (cell: number) => { + let count = 0; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && regionId[neighbor] === sourceRegion) count += 1; + } + return count; + }; + return sourceContacts(second) - sourceContacts(first) || first - second; + }); + for (const cell of component) { + regionId[cell] = targetRegion; + if (sourceRegion === TIER_III_REGION_INDEX) tierId[cell] = 2; + regionRepairLocked[cell] = 1; + } + for (let index = 0; index < component.length; index += 1) { + const swap = waterSwapCandidates[index]!; + regionId[swap] = sourceRegion; + if (sourceRegion === TIER_III_REGION_INDEX) tierId[swap] = 3; + } + } + // Lowlands (and any analogous island realm) may have its immutable main + // body offshore while a second continental foothold carries its gates. + // If retaining that whole foothold would miss the coherence threshold, + // keep a connected dry saddle district and transfer only its hinterland. + const repairedMask = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + regionId[cell] === sourceRegion + && strategicallyPassableSurface(waterRegime[cell]!) + ) repairedMask[cell] = 1; + } + const repairedComponents = [...connectedComponents(grid, repairedMask)] + .sort((first, second) => second.length - first.length || first[0]! - second[0]!); + const repairedTotal = repairedComponents.reduce( + (total, component) => total + component.length, + 0, + ); + if ( + repairedComponents.length > 1 + && repairedComponents[0]!.length * 10_000 < repairedTotal * targetLargestShare + ) { + const foothold = repairedComponents.slice(1) + .filter(component => component.length >= 96 && tierContactCount(component) >= 2) + .sort((first, second) => ( + tierContactCount(second) - tierContactCount(first) + || second.length - first.length + || first[0]! - second[0]! + ))[0]; + if (foothold) { + const maximumTotal = Math.floor( + (repairedComponents[0]!.length * 10_000) / targetLargestShare, + ); + const excess = repairedTotal - maximumTotal; + const keepCount = Math.max(96, foothold.length - excess - 8); + if (keepCount < foothold.length) { + const footholdMask = new Uint8Array(grid.cellCount); + for (const cell of foothold) footholdMask[cell] = 1; + const seed = [...foothold] + .filter(cell => tierContactCount([cell]) > 0) + .sort((first, second) => first - second)[0]; + if (seed !== undefined) { + const keep = new Uint8Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + keep[seed] = 1; + queue[tail++] = seed; + while (head < tail && tail < keepCount) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || footholdMask[neighbor] !== 1 || keep[neighbor] === 1) continue; + keep[neighbor] = 1; + queue[tail++] = neighbor; + if (tail >= keepCount) break; + } + } + const transfer = foothold.filter(cell => keep[cell] !== 1); + const contacts = new Uint32Array(REGION_COUNT); + for (const cell of transfer) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[neighbor] === tierId[cell] + && regionId[neighbor] !== sourceRegion + ) contacts[regionId[neighbor]!] += 1; + } + } + let targetRegion = -1; + for (let region = 0; region < REGION_COUNT; region += 1) { + if ( + region !== sourceRegion + && GREATER_REALM_REGION_SPECS[region]!.tier + === GREATER_REALM_REGION_SPECS[sourceRegion]!.tier + && (targetRegion < 0 || contacts[region]! > contacts[targetRegion]!) + ) targetRegion = region; + } + if (targetRegion >= 0) { + const waterSwapCandidates = Array.from( + { length: grid.cellCount }, + (_, cell) => cell, + ).filter(cell => ( + regionId[cell] === targetRegion + && !strategicallyPassableSurface(waterRegime[cell]!) + && legacyProtectedCell[cell] === 0 + )); + if (waterSwapCandidates.length >= transfer.length) { + waterSwapCandidates.sort((first, second) => first - second); + for (const cell of transfer) { + regionId[cell] = targetRegion; + regionRepairLocked[cell] = 1; + } + for (let index = 0; index < transfer.length; index += 1) { + regionId[waterSwapCandidates[index]!] = sourceRegion; + } + } + } + } + } + } + } + } + const finalCounts = Array(REGION_COUNT).fill(0); + for (const region of regionId) finalCounts[region] += 1; + if (finalCounts.some((count, region) => count !== initialCounts[region])) { + fail('GREATER_REALM_REGION_COHERENCE_BALANCE_FAILED'); + } + const finalTierCounts: [number, number, number] = [0, 0, 0]; + for (const tier of tierId) finalTierCounts[tier - 1] += 1; + if (finalTierCounts.some((count, tier) => count !== strategy.tierCounts[tier])) { + fail('GREATER_REALM_TIER_COHERENCE_BALANCE_FAILED'); + } + // Every outer/middle realm must own more traversable land than the compact + // throne realm. Grow deficient realms outward from their dominant land body + // through same-tier political boundaries, then exchange an equal number of + // non-passable ownership cells with each donor. Geometry and tier totals are + // untouched; only the natural border follows the usable land more closely. + const initialPassableTopology = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + const passableFloor = initialPassableTopology.passableCounts[TIER_III_REGION_INDEX]! + 512; + const regionsByNeed = Array.from({ length: TIER_III_REGION_INDEX }, (_, region) => region) + .sort((first, second) => ( + initialPassableTopology.passableCounts[first]! + - initialPassableTopology.passableCounts[second]! + || first - second + )); + for (const targetRegion of regionsByNeed) { + const currentTopology = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + const needed = passableFloor - currentTopology.passableCounts[targetRegion]!; + if (needed <= 0) continue; + const largestComponent = currentTopology.largestComponentByRegion[targetRegion]!; + if (largestComponent < 0) continue; + const donorCapacity = currentTopology.passableCounts.map((count, region) => ( + region === targetRegion + || GREATER_REALM_REGION_SPECS[region]!.tier + !== GREATER_REALM_REGION_SPECS[targetRegion]!.tier + ? 0 + : Math.max(0, count - passableFloor) + )); + const seen = new Uint8Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (currentTopology.componentId[cell] !== largestComponent) continue; + seen[cell] = 1; + queue[tail++] = cell; + } + const annex: number[] = []; + const donorCounts = new Uint32Array(REGION_COUNT); + while (head < tail && annex.length < needed) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || seen[neighbor] === 1 + || tierId[neighbor] !== tierId[cell] + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || legacyProtectedCell[neighbor] === 1 + ) continue; + const donor = regionId[neighbor]!; + if (donor !== targetRegion && donorCapacity[donor]! <= 0) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + if (donor !== targetRegion) { + annex.push(neighbor); + donorCounts[donor] += 1; + donorCapacity[donor] -= 1; + if (annex.length >= needed) break; + } + } + } + if (annex.length < needed) continue; + const waterSwaps = new Map(); + const reservedWater = new Set(); + let swapsAvailable = true; + for (let donor = 0; donor < REGION_COUNT; donor += 1) { + const swapCount = donorCounts[donor]!; + if (swapCount === 0) continue; + const candidates: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + reservedWater.has(cell) + || regionId[cell] !== targetRegion + || strategicallyPassableSurface(waterRegime[cell]!) + || legacyProtectedCell[cell] === 1 + ) continue; + candidates.push(cell); + } + candidates.sort((first, second) => { + const contacts = (candidate: number) => { + let count = 0; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[candidate * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && regionId[neighbor] === donor) count += 1; + } + return count; + }; + return contacts(second) - contacts(first) || first - second; + }); + if (candidates.length < swapCount) { + swapsAvailable = false; + break; + } + const selected = candidates.slice(0, swapCount); + for (const cell of selected) reservedWater.add(cell); + waterSwaps.set(donor, selected); + } + if (!swapsAvailable) continue; + for (const cell of annex) regionId[cell] = targetRegion; + for (const [donor, cells] of waterSwaps) { + for (const cell of cells) regionId[cell] = donor; + } + } + const noBarrier = new Uint8Array(grid.cellCount); + const topology = passableRegionTopology(grid, regionId, waterRegime, noBarrier); + const contactScores = Array.from({ length: TIER_I_REGION_COUNT }, () => ( + new Uint32Array(TIER_II_REGION_COUNT) + )); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor <= cell || tierId[cell] === tierId[neighbor]) continue; + const first = regionId[cell]! < TIER_I_REGION_COUNT ? cell : neighbor; + const second = first === cell ? neighbor : cell; + const child = regionId[first]!; + const parent = regionId[second]!; + if (child >= TIER_I_REGION_COUNT || parent < 6 || parent > 8) continue; + if (waterRegime[first] !== 0 || waterRegime[second] !== 0) continue; + const firstComponent = topology.componentId[first]!; + const secondComponent = topology.componentId[second]!; + const durable = firstComponent >= 0 + && secondComponent >= 0 + && topology.componentSizes[firstComponent]! >= 64 + && topology.componentSizes[secondComponent]! >= 64; + contactScores[child]![parent - 6] += durable ? 101 : 1; + } + } + const provisionalParent = new Uint8Array(TIER_I_REGION_COUNT); + for (const [child, parent] of strategy.gateGraph) { + if (child < TIER_I_REGION_COUNT && parent >= 6 && parent <= 8) { + provisionalParent[child] = parent; + } + } + let bestAssignment: readonly number[] | undefined; + let bestBottleneck = -1; + let bestTotal = -1; + let bestProvisionalMatches = -1; + // 3^6 is a tiny, fixed search. Enforce two children per T2 parent and choose + // the graph with the strongest real dry saddle contacts after coherence + // repair; the former basin parent is retained as a deterministic tie-break. + for (let encoded = 0; encoded < 729; encoded += 1) { + let cursor = encoded; + const assignment = Array(TIER_I_REGION_COUNT).fill(0); + const counts = [0, 0, 0]; + let bottleneck = Number.POSITIVE_INFINITY; + let total = 0; + let provisionalMatches = 0; + for (let child = 0; child < TIER_I_REGION_COUNT; child += 1) { + const parentOffset = cursor % 3; + cursor = Math.floor(cursor / 3); + assignment[child] = parentOffset + 6; + counts[parentOffset] += 1; + const score = contactScores[child]![parentOffset]!; + bottleneck = Math.min(bottleneck, score); + total += score; + if (provisionalParent[child] === parentOffset + 6) provisionalMatches += 1; + } + if (counts.some(count => count !== 2)) continue; + if ( + bottleneck > bestBottleneck + || (bottleneck === bestBottleneck && total > bestTotal) + || ( + bottleneck === bestBottleneck + && total === bestTotal + && provisionalMatches > bestProvisionalMatches + ) + ) { + bestAssignment = assignment; + bestBottleneck = bottleneck; + bestTotal = total; + bestProvisionalMatches = provisionalMatches; + } + } + if (!bestAssignment) fail('GREATER_REALM_GATE_PARENT_ASSIGNMENT_FAILED'); + const gateGraph: GreaterRealmGateGraphEdge[] = []; + for (let parent = 6; parent <= 8; parent += 1) { + for (let child = 0; child < TIER_I_REGION_COUNT; child += 1) { + if (bestAssignment[child] === parent) { + gateGraph.push(Object.freeze([child, parent] as const)); + } + } + } + gateGraph.push( + Object.freeze([6, 9] as const), + Object.freeze([7, 9] as const), + Object.freeze([8, 9] as const), + ); + const minimumGateFootholdCells = 512; + const growGateFoothold = (endpoint: number, targetRegion: number): boolean => { + let currentTopology = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + const component = currentTopology.componentId[endpoint]!; + if (component < 0) return false; + if (currentTopology.componentSizes[component]! >= minimumGateFootholdCells) return true; + const seen = new Uint8Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (currentTopology.componentId[cell] !== component) continue; + seen[cell] = 1; + queue[tail++] = cell; + } + const annex: number[] = []; + const donorCounts = new Uint32Array(REGION_COUNT); + while (head < tail && tail < minimumGateFootholdCells) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || seen[neighbor] === 1 + || tierId[neighbor] !== tierId[endpoint] + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || legacyProtectedCell[neighbor] === 1 + ) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + if (regionId[neighbor] !== targetRegion) { + annex.push(neighbor); + donorCounts[regionId[neighbor]!] += 1; + } + if (tail >= minimumGateFootholdCells) break; + } + } + if (tail < minimumGateFootholdCells) return false; + const waterSwaps = new Map(); + const reservedWater = new Set(); + for (let donor = 0; donor < REGION_COUNT; donor += 1) { + const needed = donorCounts[donor]!; + if (needed === 0) continue; + const candidates: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + reservedWater.has(cell) + || regionId[cell] !== targetRegion + || strategicallyPassableSurface(waterRegime[cell]!) + || legacyProtectedCell[cell] === 1 + ) continue; + candidates.push(cell); + } + candidates.sort((first, second) => { + const contacts = (cell: number) => { + let count = 0; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && regionId[neighbor] === donor) count += 1; + } + return count; + }; + return contacts(second) - contacts(first) || first - second; + }); + if (candidates.length < needed) return false; + const selected = candidates.slice(0, needed); + for (const cell of selected) reservedWater.add(cell); + waterSwaps.set(donor, selected); + } + for (const cell of annex) regionId[cell] = targetRegion; + for (const [donor, cells] of waterSwaps) { + for (const cell of cells) regionId[cell] = donor; + } + currentTopology = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + const grownComponent = currentTopology.componentId[endpoint]!; + return grownComponent >= 0 + && currentTopology.componentSizes[grownComponent]! >= minimumGateFootholdCells; + }; + const createBorrowedGateFoothold = ( + targetRegion: number, + adjacentRegion: number, + ): boolean => { + const topology = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + const boundarySeeds: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + tierId[cell] !== GREATER_REALM_REGION_SPECS[targetRegion]!.tier + || regionId[cell] === targetRegion + || waterRegime[cell] !== 0 + || legacyProtectedCell[cell] === 1 + || regionRepairLocked[cell] === 1 + ) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && regionId[neighbor] === adjacentRegion + && waterRegime[neighbor] === 0 + ) { + boundarySeeds.push(cell); + break; + } + } + } + boundarySeeds.sort((first, second) => { + const firstSize = topology.componentSizes[topology.componentId[first]!] ?? 0; + const secondSize = topology.componentSizes[topology.componentId[second]!] ?? 0; + return secondSize - firstSize || first - second; + }); + for (const seed of boundarySeeds.slice(0, 256)) { + const donor = regionId[seed]!; + const donorComponent = topology.componentId[seed]!; + if (donorComponent < 0 || topology.componentSizes[donorComponent]! < 2_048) continue; + const seen = new Uint8Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + seen[seed] = 1; + queue[tail++] = seed; + while (head < tail && tail < minimumGateFootholdCells) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || seen[neighbor] === 1 + || regionId[neighbor] !== donor + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || legacyProtectedCell[neighbor] === 1 + || regionRepairLocked[neighbor] === 1 + ) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + if (tail >= minimumGateFootholdCells) break; + } + } + if (tail < minimumGateFootholdCells) continue; + const patch = Array.from(queue.slice(0, minimumGateFootholdCells)); + let dryContacts = 0; + for (const cell of patch) { + if (waterRegime[cell] !== 0) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && regionId[neighbor] === adjacentRegion + && waterRegime[neighbor] === 0 + ) dryContacts += 1; + } + } + if (dryContacts < 2) continue; + const waterSwapCandidates: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + regionId[cell] === targetRegion + && !strategicallyPassableSurface(waterRegime[cell]!) + && legacyProtectedCell[cell] === 0 + ) waterSwapCandidates.push(cell); + } + if (waterSwapCandidates.length < patch.length) continue; + waterSwapCandidates.sort((first, second) => { + const donorContacts = (cell: number) => { + let count = 0; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && regionId[neighbor] === donor) count += 1; + } + return count; + }; + return donorContacts(second) - donorContacts(first) || first - second; + }); + for (const cell of patch) { + regionId[cell] = targetRegion; + regionRepairLocked[cell] = 1; + } + for (let index = 0; index < patch.length; index += 1) { + regionId[waterSwapCandidates[index]!] = donor; + } + const postBorrowTopology = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + const donorMinimumShare = donor < TIER_I_REGION_COUNT + ? donor === 4 ? 6_500 : 8_500 + : donor < TIER_III_REGION_INDEX ? 9_000 : 9_500; + if (postBorrowTopology.largestSharesBasisPoints[donor]! < donorMinimumShare) { + // A gate district must be borrowed from a shoreline, not carved as a + // stripe through the donor's dominant body. Reject any patch that + // would split that realm below its strict coherence margin. + for (const cell of patch) { + regionId[cell] = donor; + regionRepairLocked[cell] = 0; + } + for (let index = 0; index < patch.length; index += 1) { + regionId[waterSwapCandidates[index]!] = targetRegion; + } + continue; + } + return true; + } + return false; + }; + for (const [firstRegion, secondRegion] of gateGraph) { + let topologyAtGate = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + const edges: Array = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor <= cell || waterRegime[cell] !== 0 || waterRegime[neighbor] !== 0) continue; + if ( + regionId[cell] === firstRegion + && regionId[neighbor] === secondRegion + ) edges.push(Object.freeze([cell, neighbor] as const)); + else if ( + regionId[cell] === secondRegion + && regionId[neighbor] === firstRegion + ) edges.push(Object.freeze([neighbor, cell] as const)); + } + } + edges.sort((first, second) => { + const strength = (edge: readonly [number, number]) => Math.min( + topologyAtGate.componentSizes[topologyAtGate.componentId[edge[0]!]!] ?? 0, + topologyAtGate.componentSizes[topologyAtGate.componentId[edge[1]!]!] ?? 0, + ); + return strength(second) - strength(first) || first[0] - second[0] || first[1] - second[1]; + }); + let edge = edges[0]; + if (!edge) { + createBorrowedGateFoothold(firstRegion, secondRegion); + topologyAtGate = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor <= cell || waterRegime[cell] !== 0 || waterRegime[neighbor] !== 0) continue; + if (regionId[cell] === firstRegion && regionId[neighbor] === secondRegion) { + edges.push(Object.freeze([cell, neighbor] as const)); + } else if (regionId[cell] === secondRegion && regionId[neighbor] === firstRegion) { + edges.push(Object.freeze([neighbor, cell] as const)); + } + } + } + edges.sort((first, second) => { + const strength = (candidate: readonly [number, number]) => Math.min( + topologyAtGate.componentSizes[topologyAtGate.componentId[candidate[0]!]!] ?? 0, + topologyAtGate.componentSizes[topologyAtGate.componentId[candidate[1]!]!] ?? 0, + ); + return strength(second) - strength(first) + || first[0] - second[0] + || first[1] - second[1]; + }); + edge = edges[0]; + } + if (!edge) continue; + if (!growGateFoothold(edge[0], firstRegion)) { + createBorrowedGateFoothold(firstRegion, secondRegion); + } + topologyAtGate = passableRegionTopology( + grid, + regionId, + waterRegime, + new Uint8Array(grid.cellCount), + ); + // The opposite endpoint remains adjacent after a same-tier foothold edit. + if (!growGateFoothold(edge[1], secondRegion)) { + createBorrowedGateFoothold(secondRegion, firstRegion); + } + } + const balancedCounts = Array(REGION_COUNT).fill(0); + const balancedTierCounts = [0, 0, 0]; + for (const region of regionId) balancedCounts[region] += 1; + for (const tier of tierId) balancedTierCounts[tier - 1] += 1; + if (balancedCounts.some((count, region) => count !== initialCounts[region])) { + fail('GREATER_REALM_GATE_FOOTHOLD_BALANCE_FAILED'); + } + if (balancedTierCounts.some((count, tier) => count !== strategy.tierCounts[tier])) { + fail('GREATER_REALM_GATE_FOOTHOLD_TIER_BALANCE_FAILED'); + } + return Object.freeze({ + ...strategy, + tierId, + regionId, + regionCounts: Object.freeze(balancedCounts), + gateGraph: Object.freeze(gateGraph), + }); +} + +type GreaterRealmPassableRegionTopology = Readonly<{ + componentId: Int32Array; + componentSizes: readonly number[]; + largestComponentByRegion: readonly number[]; + passableCounts: readonly number[]; + largestCounts: readonly number[]; + largestSharesBasisPoints: readonly number[]; + proof: boolean; +}>; + +type GreaterRealmRobustRegionTopology = Readonly<{ + articulation: Uint8Array; + componentId: Int32Array; + componentSizes: readonly number[]; +}>; + +function strategicallyPassableSurface(waterRegime: number): boolean { + // Ocean/lake cells require future naval or bridge mechanics. River and + // stream corridors are retained as fordable route surfaces for topology; + // castle and gate endpoint suitability still requires a dry cell. + return waterRegime === 0 || waterRegime === 3 || waterRegime === 4; +} + +function passableRegionTopology( + grid: IndexedAxialGrid, + regionId: Uint8Array, + waterRegime: Uint8Array, + barrier: Uint8Array, +): GreaterRealmPassableRegionTopology { + const componentId = new Int32Array(grid.cellCount); + componentId.fill(-1); + const componentSizes: number[] = []; + const largestComponentByRegion = Array(REGION_COUNT).fill(-1); + const passableCounts = Array(REGION_COUNT).fill(0); + const largestCounts = Array(REGION_COUNT).fill(0); + const queue = new Uint32Array(grid.cellCount); + for (let start = 0; start < grid.cellCount; start += 1) { + if (!strategicallyPassableSurface(waterRegime[start]!) || barrier[start] !== 0) continue; + const region = regionId[start]!; + passableCounts[region] += 1; + if (componentId[start] >= 0) continue; + const id = componentSizes.length; + let head = 0; + let tail = 0; + let size = 0; + componentId[start] = id; + queue[tail++] = start; + while (head < tail) { + const cell = queue[head++]!; + size += 1; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || componentId[neighbor] >= 0 + || regionId[neighbor] !== region + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || barrier[neighbor] !== 0 + ) continue; + componentId[neighbor] = id; + queue[tail++] = neighbor; + } + } + componentSizes.push(size); + if (size > largestCounts[region]!) { + largestCounts[region] = size; + largestComponentByRegion[region] = id; + } + } + // The first pass counted only component roots for cells seen later. Derive + // exact totals independently from the final passability mask. + passableCounts.fill(0); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (strategicallyPassableSurface(waterRegime[cell]!) && barrier[cell] === 0) { + passableCounts[regionId[cell]!] += 1; + } + } + const largestSharesBasisPoints = passableCounts.map((count, region) => ( + count === 0 ? 0 : Math.round((largestCounts[region]! * 10_000) / count) + )); + const proof = largestSharesBasisPoints.every((share, region) => ( + region < TIER_I_REGION_COUNT ? share >= (region === 4 ? 5_500 : 8_000) + : region < TIER_III_REGION_INDEX ? share >= 8_500 + : share >= 9_000 + )); + return Object.freeze({ + componentId, + componentSizes: Object.freeze(componentSizes), + largestComponentByRegion: Object.freeze(largestComponentByRegion), + passableCounts: Object.freeze(passableCounts), + largestCounts: Object.freeze(largestCounts), + largestSharesBasisPoints: Object.freeze(largestSharesBasisPoints), + proof, + }); +} + +/** + * Derive disjoint, vertex-biconnected regional cores from the final + * passability graph. Tarjan blocks retain a genuinely articulation-free core + * even when a harmless spur is attached to it. Because articulation vertices + * can belong to several blocks, each is owned by its largest incident block + * (stable block id breaks ties) and every conflicting block is discarded. + * The remaining component ids therefore describe complete, non-overlapping + * biconnected subgraphs rather than the fragments produced by deleting the + * original graph's articulation vertices only once. + */ +function robustRegionTopology( + grid: IndexedAxialGrid, + regionId: Uint8Array, + waterRegime: Uint8Array, + barrier: Uint8Array, +): GreaterRealmRobustRegionTopology { + const discovery = new Int32Array(grid.cellCount); + const low = new Int32Array(grid.cellCount); + const parent = new Int32Array(grid.cellCount); + const nextNeighbor = new Uint8Array(grid.cellCount); + const childCount = new Uint8Array(grid.cellCount); + const articulation = new Uint8Array(grid.cellCount); + const edgeFirst: number[] = []; + const edgeSecond: number[] = []; + const blocks: number[][] = []; + const blockMark = new Uint32Array(grid.cellCount); + let blockGeneration = 0; + discovery.fill(-1); + parent.fill(-1); + let clock = 0; + const passable = (cell: number) => ( + strategicallyPassableSurface(waterRegime[cell]!) + && barrier[cell] === 0 + ); + for (let root = 0; root < grid.cellCount; root += 1) { + if (!passable(root) || discovery[root] >= 0) continue; + const stack: number[] = [root]; + discovery[root] = clock; + low[root] = clock; + clock += 1; + while (stack.length > 0) { + const cell = stack[stack.length - 1]!; + const direction = nextNeighbor[cell]!; + if (direction < HEX_NEIGHBOR_COUNT) { + nextNeighbor[cell] = direction + 1; + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || regionId[neighbor] !== regionId[cell] + || !passable(neighbor) + ) continue; + if (discovery[neighbor] < 0) { + parent[neighbor] = cell; + childCount[cell] += 1; + edgeFirst.push(cell); + edgeSecond.push(neighbor); + discovery[neighbor] = clock; + low[neighbor] = clock; + clock += 1; + stack.push(neighbor); + continue; + } + if (neighbor !== parent[cell] && discovery[neighbor]! < discovery[cell]!) { + edgeFirst.push(cell); + edgeSecond.push(neighbor); + low[cell] = Math.min(low[cell]!, discovery[neighbor]!); + } + continue; + } + stack.pop(); + const ancestor = parent[cell]!; + if (ancestor < 0) { + if (childCount[cell]! > 1) articulation[cell] = 1; + if (childCount[cell] === 0) blocks.push([cell]); + continue; + } + low[ancestor] = Math.min(low[ancestor]!, low[cell]!); + if (low[cell]! < discovery[ancestor]!) continue; + if (parent[ancestor]! >= 0) articulation[ancestor] = 1; + blockGeneration += 1; + if (blockGeneration === 0xffff_ffff) fail('GREATER_REALM_BICONNECTED_MARK_OVERFLOW'); + const vertices: number[] = []; + let foundTreeEdge = false; + while (edgeFirst.length > 0) { + const second = edgeSecond.pop()!; + const first = edgeFirst.pop()!; + if (blockMark[first] !== blockGeneration) { + blockMark[first] = blockGeneration; + vertices.push(first); + } + if (blockMark[second] !== blockGeneration) { + blockMark[second] = blockGeneration; + vertices.push(second); + } + if (first === ancestor && second === cell) { + foundTreeEdge = true; + break; + } + } + if (!foundTreeEdge) fail('GREATER_REALM_BICONNECTED_EDGE_STACK_CORRUPT'); + vertices.sort((first, second) => first - second); + blocks.push(vertices); + } + } + if (edgeFirst.length !== 0 || edgeSecond.length !== 0) { + fail('GREATER_REALM_BICONNECTED_EDGE_STACK_NOT_EMPTY'); + } + + // An articulation may participate in multiple valid blocks. Give it to one + // deterministic largest block, then discard every block that would be + // incomplete without that vertex. Kept cores are therefore complete + // biconnected induced subgraphs and can safely use a single component id. + const articulationOwner = new Int32Array(grid.cellCount); + articulationOwner.fill(-1); + for (let block = 0; block < blocks.length; block += 1) { + for (const cell of blocks[block]!) { + if (articulation[cell] !== 1) continue; + const current = articulationOwner[cell]!; + if ( + current < 0 + || blocks[block]!.length > blocks[current]!.length + || (blocks[block]!.length === blocks[current]!.length && block < current) + ) articulationOwner[cell] = block; + } + } + const componentId = new Int32Array(grid.cellCount); + componentId.fill(-1); + const componentSizes: number[] = []; + for (let block = 0; block < blocks.length; block += 1) { + const vertices = blocks[block]!; + if (vertices.some(cell => ( + articulation[cell] === 1 && articulationOwner[cell] !== block + ))) continue; + const id = componentSizes.length; + for (const cell of vertices) { + if (componentId[cell] >= 0) fail('GREATER_REALM_BICONNECTED_CORE_OVERLAP'); + componentId[cell] = id; + } + componentSizes.push(vertices.length); + } + return Object.freeze({ + articulation, + componentId, + componentSizes: Object.freeze(componentSizes), + }); +} + +type GreaterRealmStrategicShapeMetrics = Readonly<{ + minorFragmentSharesBasisPoints: readonly number[]; + boundaryDensityBasisPoints: readonly number[]; + tendrilSharesBasisPoints: readonly number[]; + tierRadialAgreementBasisPoints: number; + radialTierOneBoundaryShareBasisPoints: number; + fragmentationProof: boolean; + compactnessProof: boolean; + tendrilProof: boolean; + nonRadialProof: boolean; +}>; + +function strategicShapeMetrics( + grid: IndexedAxialGrid, + tierId: Uint8Array, + regionId: Uint8Array, + waterRegime: Uint8Array, + barrier: Uint8Array, + topology: GreaterRealmPassableRegionTopology, +): GreaterRealmStrategicShapeMetrics { + const minorCells = Array(REGION_COUNT).fill(0); + const componentRegion = Array(topology.componentSizes.length).fill(-1); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const component = topology.componentId[cell]!; + if (component >= 0 && componentRegion[component] === -1) { + componentRegion[component] = regionId[cell]!; + } + } + for (let component = 0; component < topology.componentSizes.length; component += 1) { + if (topology.componentSizes[component]! >= 64) continue; + const region = componentRegion[component]!; + if (region >= 0) minorCells[region] += topology.componentSizes[component]!; + } + const boundarySides = Array(REGION_COUNT).fill(0); + const tendrils = Array(REGION_COUNT).fill(0); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (!strategicallyPassableSurface(waterRegime[cell]!) || barrier[cell] !== 0) continue; + const region = regionId[cell]!; + let sameRegionNeighbors = 0; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && regionId[neighbor] === region + && strategicallyPassableSurface(waterRegime[neighbor]!) + && barrier[neighbor] === 0 + ) sameRegionNeighbors += 1; + else boundarySides[region] += 1; + } + if (sameRegionNeighbors <= 1) tendrils[region] += 1; + } + const minorFragmentSharesBasisPoints = topology.passableCounts.map((count, region) => ( + count === 0 ? 10_000 : Math.round((minorCells[region]! * 10_000) / count) + )); + const boundaryDensityBasisPoints = topology.passableCounts.map((count, region) => ( + count === 0 + ? 10_000 + : Math.round((boundarySides[region]! * 10_000) / (count * HEX_NEIGHBOR_COUNT)) + )); + const tendrilSharesBasisPoints = topology.passableCounts.map((count, region) => ( + count === 0 ? 10_000 : Math.round((tendrils[region]! * 10_000) / count) + )); + + let tierThreeQ = 0; + let tierThreeR = 0; + let tierThreeCells = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierId[cell] !== 3) continue; + tierThreeQ += grid.q[cell]!; + tierThreeR += grid.r[cell]!; + tierThreeCells += 1; + } + const roundedRatio = (numerator: number, denominator: number) => numerator >= 0 + ? Math.floor((numerator * 2 + denominator) / (denominator * 2)) + : -Math.floor((-numerator * 2 + denominator) / (denominator * 2)); + const centerQ = roundedRatio(tierThreeQ, tierThreeCells); + const centerR = roundedRatio(tierThreeR, tierThreeCells); + const radialTierCounts = new Map(); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const radius = axialDistance(grid.q[cell]!, grid.r[cell]!, centerQ, centerR); + const counts = radialTierCounts.get(radius) ?? [0, 0, 0]; + counts[tierId[cell]! - 1] += 1; + radialTierCounts.set(radius, counts); + } + let radialModalCells = 0; + for (const counts of radialTierCounts.values()) radialModalCells += Math.max(...counts); + const tierRadialAgreementBasisPoints = Math.round( + (radialModalCells * 10_000) / grid.cellCount, + ); + let tierOneBoundaryEdges = 0; + let radialTierOneBoundaryEdges = 0; + const centerX = 2 * centerQ + centerR; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierId[cell] !== 1) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor <= cell + || tierId[neighbor] !== 1 + || regionId[neighbor] === regionId[cell] + ) continue; + tierOneBoundaryEdges += 1; + const midpointX = (2 * grid.q[cell]! + grid.r[cell]!) + + (2 * grid.q[neighbor]! + grid.r[neighbor]!) + - 2 * centerX; + const midpointR = grid.r[cell]! + grid.r[neighbor]! - 2 * centerR; + const deltaQ = grid.q[neighbor]! - grid.q[cell]!; + const deltaR = grid.r[neighbor]! - grid.r[cell]!; + const edgeX = 2 * deltaQ + deltaR; + const dot = midpointX * edgeX + 3 * midpointR * deltaR; + const radialNorm = midpointX * midpointX + 3 * midpointR * midpointR; + const edgeNorm = edgeX * edgeX + 3 * deltaR * deltaR; + if ( + BigInt(dot) * BigInt(dot) * 100n + <= BigInt(radialNorm) * BigInt(edgeNorm) * 16n + ) radialTierOneBoundaryEdges += 1; + } + } + const radialTierOneBoundaryShareBasisPoints = tierOneBoundaryEdges === 0 + ? 10_000 + : Math.round((radialTierOneBoundaryEdges * 10_000) / tierOneBoundaryEdges); + return Object.freeze({ + minorFragmentSharesBasisPoints: Object.freeze(minorFragmentSharesBasisPoints), + boundaryDensityBasisPoints: Object.freeze(boundaryDensityBasisPoints), + tendrilSharesBasisPoints: Object.freeze(tendrilSharesBasisPoints), + tierRadialAgreementBasisPoints, + radialTierOneBoundaryShareBasisPoints, + fragmentationProof: minorFragmentSharesBasisPoints.every((share, region) => ( + share <= (region === 4 ? 500 : 300) + )), + compactnessProof: boundaryDensityBasisPoints.every(share => share <= 1_000), + tendrilProof: tendrilSharesBasisPoints.every(share => share <= 150), + nonRadialProof: tierRadialAgreementBasisPoints <= 9_200 + && radialTierOneBoundaryShareBasisPoints <= 4_500, + }); +} + +function waterAndBiomes( + grid: IndexedAxialGrid, + candidateSeed: GreaterRealmTerrainSeed, + elevation: Int32Array, + filledElevation: Int32Array, + flowReceiver: Int32Array, + accumulation: BigUint64Array, + tierId: Uint8Array, + legacyProtectedCell: Uint8Array, + temperatureField: Int32Array, + moistureField: Int32Array, +): Readonly<{ + waterRegime: Uint8Array; + biomeId: Uint8Array; + landformId: Uint8Array; + majorRiverCount: number; + minorStreamCount: number; + lakeCount: number; + lakeBasinCandidates: number; + riverMouthCandidates: number; + streamHeadCandidates: number; +}> { + const lakeCandidate = new Uint8Array(grid.cellCount); + const belowSea = new Uint8Array(grid.cellCount); + const majorRiverNetwork = new Uint8Array(grid.cellCount); + const minorStreamNetwork = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (elevation[cell]! <= SEA_LEVEL) belowSea[cell] = 1; + if ( + legacyProtectedCell[cell] !== 1 + && elevation[cell]! > SEA_LEVEL + && filledElevation[cell]! - elevation[cell]! >= 80 + ) { + lakeCandidate[cell] = 1; + } + if (legacyProtectedCell[cell] === 1 || elevation[cell]! <= SEA_LEVEL) continue; + const discharge = Number(accumulation[cell]!); + if (discharge >= MAJOR_RIVER_DISCHARGE) majorRiverNetwork[cell] = 1; + else if (discharge >= 96) minorStreamNetwork[cell] = 1; + } + const allLakeComponents = [...connectedComponentsAtEqualSurface( + grid, + lakeCandidate, + filledElevation, + )] + .sort((first, second) => second.length - first.length || first[0]! - second[0]!); + const majorRiverCount = connectedComponents(grid, majorRiverNetwork).length; + let minorStreamCount = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (minorStreamNetwork[cell] !== 1) continue; + let hasUpstreamStream = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && minorStreamNetwork[neighbor] === 1 + && flowReceiver[neighbor] === cell + ) { + hasUpstreamStream = true; + break; + } + } + if (!hasUpstreamStream) minorStreamCount += 1; + } + const waterRegime = new Uint8Array(grid.cellCount); + let existingMajorBodies = 0; + for (const component of connectedComponents(grid, belowSea)) { + const touchesActiveBoundary = component.some(cell => { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction] === -1) return true; + } + return false; + }); + const regime = touchesActiveBoundary + ? WATER_OCEAN + : component.length >= 48 ? WATER_SEA : WATER_LAKE; + if (regime === WATER_OCEAN || regime === WATER_SEA) existingMajorBodies += 1; + for (const cell of component) waterRegime[cell] = regime; + } + const targetMajorBodies = 4 + ( + greaterRealmCounterRandomU32( + candidateSeed, + greaterRealmTerrainChannelId('inland-sea-body-count'), + 0, + 0, + ) % 3 + ); + const inlandSeaComponents = allLakeComponents + .filter(component => component.length >= 48) + .slice(0, Math.max(0, targetMajorBodies - existingMajorBodies)); + const inlandSeaCells = new Set(inlandSeaComponents.flat()); + for (const component of inlandSeaComponents) { + for (const cell of component) waterRegime[cell] = WATER_SEA; + } + const selectedLakes: Array = []; + for (const component of allLakeComponents) { + if ( + selectedLakes.length >= 72 + || component.length < 2 + || component.length > 64 + || inlandSeaCells.has(component[0]!) + ) continue; + const level = filledElevation[component[0]!]!; + const conflicts = component.some(cell => { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && waterRegime[neighbor] === WATER_LAKE + && (elevation[neighbor]! <= SEA_LEVEL ? SEA_LEVEL : filledElevation[neighbor]!) !== level + ) return true; + } + return false; + }); + if (conflicts) continue; + selectedLakes.push(component); + for (const cell of component) waterRegime[cell] = WATER_LAKE; + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (waterRegime[cell] === WATER_DRY && majorRiverNetwork[cell] === 1) { + waterRegime[cell] = WATER_RIVER; + } else if (waterRegime[cell] === WATER_DRY && minorStreamNetwork[cell] === 1) { + waterRegime[cell] = WATER_STREAM; + } + } + + const biomeId = new Uint8Array(grid.cellCount); + const landformId = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (waterRegime[cell] !== WATER_DRY) { + const saltwater = waterRegime[cell] === WATER_OCEAN || waterRegime[cell] === WATER_SEA; + biomeId[cell] = saltwater ? 20 : waterRegime[cell] === WATER_LAKE ? 21 : 22; + landformId[cell] = saltwater ? 16 : waterRegime[cell] === WATER_LAKE ? 10 : 2; + continue; + } + let maximumDrop = 0; + for (let directionIndex = 0; directionIndex < HEX_NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + directionIndex]!; + if (neighbor < 0) continue; + maximumDrop = Math.max(maximumDrop, Math.abs(elevation[cell]! - elevation[neighbor]!)); + } + const temperature = temperatureField[cell]!; + const moisture = moistureField[cell]!; + if (maximumDrop > 2_500 || elevation[cell]! > 16_000) { + biomeId[cell] = temperature < 2_500 ? 7 : 19; + landformId[cell] = elevation[cell]! > 20_000 ? 7 : 6; + } else if (temperature < 1_800) { + biomeId[cell] = moisture > 0 ? 6 : 8; + landformId[cell] = 14; + } else if (moisture < -1_800) { + biomeId[cell] = temperature > 6_000 ? 12 : 11; + landformId[cell] = maximumDrop > 900 ? 9 : 13; + } else if (moisture > 2_000) { + biomeId[cell] = tierId[cell] === 1 ? 4 : 5; + landformId[cell] = maximumDrop > 1_200 ? 5 : 3; + } else { + biomeId[cell] = tierId[cell] === 3 ? 15 : 1; + landformId[cell] = maximumDrop > 1_000 ? 4 : 3; + } + } + return Object.freeze({ + waterRegime, + biomeId, + landformId, + majorRiverCount, + minorStreamCount, + lakeCount: selectedLakes.length, + lakeBasinCandidates: allLakeComponents.length, + riverMouthCandidates: majorRiverCount, + streamHeadCandidates: minorStreamCount, + }); +} + +/** + * The generated surface is never allowed to reinterpret the deployed patch. + * Clear all generated water inside its protected footprint, then project the + * exact active Water revision and the seven frozen gameplay terrain classes. + * Detailed legacy hydrology remains in the pinned private patch descriptor. + */ +function overlayLegacyLowlandsSurface( + grid: IndexedAxialGrid, + legacy: GreaterRealmLegacyPlacement, + waterRegime: Uint8Array, + biomeId: Uint8Array, + landformId: Uint8Array, +): boolean { + const patch = GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (legacy.protectedCell[cell] === 1) waterRegime[cell] = 0; + } + const tileByKey = new Map(patch.world.tiles.map(tile => [tile.key, tile] as const)); + const visualClass = Object.freeze({ + lowland: Object.freeze([1, 3] as const), + meadow: Object.freeze([2, 3] as const), + forest: Object.freeze([4, 3] as const), + heath: Object.freeze([9, 3] as const), + ridge: Object.freeze([19, 6] as const), + lake: Object.freeze([1, 3] as const), + 'ancient-stone': Object.freeze([19, 5] as const), + }); + for (const metadata of patch.world.metadata) { + const tile = tileByKey.get(metadata.tileKey); + if (!tile) return false; + const cell = grid.indexOf(transformLegacyLowlandsToGlobal(tile, legacy.transform)); + const classification = visualClass[metadata.terrainKind]; + if (cell < 0 || !classification) return false; + biomeId[cell] = classification[0]; + landformId[cell] = classification[1]; + } + let enabledWaterCount = 0; + for (const waterCell of patch.water.enabledCells) { + const cell = grid.indexOf(transformLegacyLowlandsToGlobal(waterCell, legacy.transform)); + if (cell < 0 || legacy.protectedCell[cell] !== 1) return false; + waterRegime[cell] = waterCell.regime === 'ocean' ? 1 : waterCell.regime === 'river' ? 3 : 2; + biomeId[cell] = waterCell.regime === 'ocean' ? 20 : waterCell.regime === 'river' ? 22 : 21; + landformId[cell] = waterCell.regime === 'ocean' ? 16 : waterCell.regime === 'river' ? 2 : 10; + enabledWaterCount += 1; + } + if (enabledWaterCount !== GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.waterEnabledCellCount) { + return false; + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (legacy.castleSlot[cell] === 1 && waterRegime[cell] !== 0) return false; + } + return true; +} + +function finalHydrologyMetrics( + grid: IndexedAxialGrid, + elevation: Int32Array, + filledElevation: Int32Array, + flowReceiver: Int32Array, + flowAccumulation: BigUint64Array, + waterRegime: Uint8Array, + legacyProtectedCell: Uint8Array, +): Readonly<{ + majorOceanSeaBodies: number; + majorRivers: number; + minorStreams: number; + lakes: number; + watersheds: number; + surfaceConsistencyProof: boolean; +}> { + const regimeComponents = (regime: number) => { + const included = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (waterRegime[cell] === regime) included[cell] = 1; + } + return connectedComponents(grid, included); + }; + const oceanComponents = regimeComponents(WATER_OCEAN); + const seaComponents = regimeComponents(WATER_SEA); + const lakeComponents = regimeComponents(WATER_LAKE); + const majorOceanSeaBodies = oceanComponents.length + seaComponents.length; + const lakes = lakeComponents.filter(component => component.length >= 2).length; + const generatedMajorRiver = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + legacyProtectedCell[cell] !== 1 + && elevation[cell]! > SEA_LEVEL + && flowAccumulation[cell]! >= BigInt(MAJOR_RIVER_DISCHARGE) + ) generatedMajorRiver[cell] = 1; + } + const majorRivers = connectedComponents(grid, generatedMajorRiver).length; + let minorStreams = 0; + let watersheds = 0; + let surfaceConsistencyProof = true; + for (const component of oceanComponents) { + if (!component.some(cell => { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction] === -1) return true; + } + return false; + })) surfaceConsistencyProof = false; + } + for (const components of [seaComponents, lakeComponents]) { + for (const component of components) { + const componentMask = new Uint8Array(grid.cellCount); + for (const cell of component) componentMask[cell] = 1; + const expectedSurface = elevation[component[0]!]! <= SEA_LEVEL + ? SEA_LEVEL + : filledElevation[component[0]!]!; + if (component.some(cell => ( + (elevation[cell]! <= SEA_LEVEL ? SEA_LEVEL : filledElevation[cell]!) + !== expectedSurface + ))) { + surfaceConsistencyProof = false; + continue; + } + const hasLegalSpillOrOutlet = component.some(cell => { + const receiver = flowReceiver[cell]!; + if (receiver < 0) return expectedSurface <= SEA_LEVEL; + if (componentMask[receiver] === 1) return false; + const receiverSurface = elevation[receiver]! <= SEA_LEVEL + ? SEA_LEVEL + : filledElevation[receiver]!; + return receiverSurface <= expectedSurface; + }); + if (!hasLegalSpillOrOutlet) surfaceConsistencyProof = false; + } + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const receiver = flowReceiver[cell]!; + if (receiver < 0) { + if (waterRegime[cell] === 0) surfaceConsistencyProof = false; + if (flowAccumulation[cell]! >= 64n) watersheds += 1; + } + if (legacyProtectedCell[cell] === 1) continue; + if (waterRegime[cell] === 4) { + let hasUpstreamStream = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && waterRegime[neighbor] === 4 + && flowReceiver[neighbor] === cell + ) { + hasUpstreamStream = true; + break; + } + } + if (!hasUpstreamStream) minorStreams += 1; + } + if ( + (waterRegime[cell] === 3 || waterRegime[cell] === 4) + && receiver >= 0 + && legacyProtectedCell[receiver] !== 1 + && waterRegime[receiver] === 0 + ) surfaceConsistencyProof = false; + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (legacyProtectedCell[cell] !== 1) continue; + if ((waterRegime[cell] === 0) !== (elevation[cell]! > SEA_LEVEL)) { + surfaceConsistencyProof = false; + } + } + return Object.freeze({ + majorOceanSeaBodies, + majorRivers, + minorStreams, + lakes, + watersheds, + surfaceConsistencyProof, + }); +} + +function barrierApproachPaths( + grid: IndexedAxialGrid, + endpoint: number, + region: number, + regionId: Uint8Array, + waterRegime: Uint8Array, + barrier: Uint8Array, + targetComponentId: Int32Array, + targetComponentSizes: readonly number[], +): readonly (readonly number[])[] | undefined { + const starts: number[] = []; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && regionId[neighbor] === region + && waterRegime[neighbor] === 0 + ) starts.push(neighbor); + } + starts.sort((first, second) => first - second); + const findPath = ( + start: number, + forbidden: ReadonlySet = new Set(), + ): number[] | undefined => { + if (forbidden.has(start)) return undefined; + const previous = new Int32Array(grid.cellCount); + const depth = new Uint8Array(grid.cellCount); + previous.fill(-2); + previous[start] = -1; + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + queue[tail++] = start; + let target = -1; + while (head < tail) { + const cell = queue[head++]!; + if ( + barrier[cell] === 0 + && targetComponentId[cell]! >= 0 + && targetComponentSizes[targetComponentId[cell]!]! >= 64 + ) { + target = cell; + break; + } + // Natural coastlines and highland shoulders can separate a valid dry + // saddle from its region's durable interior by more than twenty cells. + // This is still a bounded offline search; the path is accepted only if + // both sides remain sealed from every unintended cross-tier contact. + if (depth[cell]! >= 64) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || neighbor === endpoint + || forbidden.has(neighbor) + || previous[neighbor] !== -2 + || regionId[neighbor] !== region + || waterRegime[neighbor] !== 0 + ) continue; + previous[neighbor] = cell; + depth[neighbor] = depth[cell]! + 1; + queue[tail++] = neighbor; + } + } + if (target < 0) return undefined; + const path: number[] = []; + for (let cell = target; cell >= 0; cell = previous[cell]!) path.push(cell); + path.reverse(); + return path; + }; + const paths: number[][] = []; + const signatures = new Set(); + for (const start of starts) { + const path = findPath(start); + if (!path) continue; + signatures.add(path.join(',')); + paths.push(path); + } + // A fixed neighbour order can make two otherwise independent approaches + // converge on the same short crest route. Deterministically search an + // alternative for each second start while excluding the first route's + // non-terminal cells. This proves two real corridors instead of counting + // two labels on one bridge cell. + const primaryPaths = [...paths]; + for (const first of primaryPaths) { + const forbidden = new Set(first); + for (const start of starts) { + if (start === first[0]) continue; + const alternative = findPath(start, forbidden); + if (!alternative) continue; + const signature = alternative.join(','); + if (signatures.has(signature)) continue; + signatures.add(signature); + paths.push(alternative); + } + } + paths.sort((first, second) => first.length - second.length || first[0]! - second[0]!); + if (paths.length < 1) return undefined; + // Keep the owned coordinate arrays mutable so private-candidate retirement + // can overwrite their contents; only the collection shape is immutable. + return Object.freeze(paths); +} + +function gateApproachCorridorsDisjoint( + left: readonly number[], + right: readonly number[], +): boolean { + const occupied = new Set(left); + return right.every(cell => !occupied.has(cell)); +} + +function compatibleGateApproaches( + grid: IndexedAxialGrid, + tierId: Uint8Array, + waterRegime: Uint8Array, + barrier: Uint8Array, + targetComponentId: Int32Array, + targetComponentSizes: readonly number[], + first: readonly (readonly number[])[], + second: readonly (readonly number[])[], + optionOrdinal = 0, +): Readonly<{ + first: readonly number[]; + firstAlternate: readonly number[]; + second: readonly number[]; + secondAlternate: readonly number[]; + all: readonly (readonly number[])[]; +}> | undefined { + let compatibleOrdinal = 0; + for (let firstA = 0; firstA < first.length; firstA += 1) { + for (let secondA = 0; secondA < second.length; secondA += 1) { + const firstAlternatives = first.map((_, index) => index).filter(index => ( + index !== firstA + && gateApproachCorridorsDisjoint(first[firstA]!, first[index]!) + )); + const secondAlternatives = second.map((_, index) => index).filter(index => ( + index !== secondA + && gateApproachCorridorsDisjoint(second[secondA]!, second[index]!) + )); + for (const firstB of firstAlternatives) { + for (const secondB of secondAlternatives) { + const paths = [ + first[firstA]!, + second[secondA]!, + first[firstB]!, + second[secondB]!, + ]; + const firstTarget = targetComponentId[paths[0]![paths[0]!.length - 1]!]!; + const secondTarget = targetComponentId[paths[1]![paths[1]!.length - 1]!]!; + const firstAlternateTarget = targetComponentId[paths[2]![paths[2]!.length - 1]!]!; + const secondAlternateTarget = targetComponentId[paths[3]![paths[3]!.length - 1]!]!; + if ( + firstTarget < 0 + || secondTarget < 0 + || firstTarget !== firstAlternateTarget + || secondTarget !== secondAlternateTarget + || targetComponentSizes[firstTarget]! < 64 + || targetComponentSizes[secondTarget]! < 64 + ) continue; + const frozenPaths = Object.freeze(paths); + const carved = new Set(frozenPaths.flat()); + let bypass = false; + for (const cell of carved) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[cell] !== tierId[neighbor] + && strategicallyPassableSurface(waterRegime[cell]!) + && strategicallyPassableSurface(waterRegime[neighbor]!) + && (barrier[cell] === 0 || carved.has(cell)) + && (barrier[neighbor] === 0 || carved.has(neighbor)) + ) { + bypass = true; + break; + } + } + if (bypass) break; + } + if (!bypass) { + if (compatibleOrdinal < optionOrdinal) { + compatibleOrdinal += 1; + continue; + } + return Object.freeze({ + first: first[firstA]!, + firstAlternate: first[firstB]!, + second: second[secondA]!, + secondAlternate: second[secondB]!, + all: frozenPaths, + }); + } + } + } + } + } + return undefined; +} + +/** + * The geological shoulder is deliberately wider than the exact cross-tier + * cut. On a narrow peninsula, an interior shoulder cell can therefore split + * one political region without contributing to the tier seal. Reconnect only + * those same-region fragments through a bounded pass: every cross-tier + * contact and every sealed gate endpoint remains locked as barrier authority. + */ +function reconnectBarrierSplitRegionComponents( + grid: IndexedAxialGrid, + regionId: Uint8Array, + tierId: Uint8Array, + waterRegime: Uint8Array, + barrier: Uint8Array, + gates: readonly GreaterRealmPrivateGate[], +): void { + const lockedBarrier = new Uint8Array(grid.cellCount); + for (const gate of gates) { + lockedBarrier[gate.firstCell] = 1; + lockedBarrier[gate.secondCell] = 1; + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (barrier[cell] !== 1) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && tierId[neighbor] !== tierId[cell]) { + lockedBarrier[cell] = 1; + break; + } + } + } + + for (let region = 0; region < REGION_COUNT; region += 1) { + // A fixed bound prevents malformed geography from turning repair into an + // unbounded convergence process. Normal shoulder splits need 1-3 passes. + for (let repair = 0; repair < 32; repair += 1) { + const topology = passableRegionTopology(grid, regionId, waterRegime, barrier); + const targetComponent = topology.largestComponentByRegion[region]!; + if (targetComponent < 0) break; + const candidates = topology.componentSizes + .map((size, component) => Object.freeze({ component, size })) + .filter(({ component }) => { + if (component === targetComponent) return false; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (topology.componentId[cell] === component) return regionId[cell] === region; + } + return false; + }) + .sort((first, second) => second.size - first.size || first.component - second.component) + .slice(0, 16); + if (candidates.length === 0) break; + + let repaired = false; + for (const candidate of candidates) { + const previous = new Int32Array(grid.cellCount); + previous.fill(-2); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (topology.componentId[cell] !== candidate.component) continue; + previous[cell] = -1; + queue[tail++] = cell; + } + let target = -1; + while (head < tail && target < 0) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || previous[neighbor] !== -2 + || regionId[neighbor] !== region + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || (barrier[neighbor] === 1 && lockedBarrier[neighbor] === 1) + ) continue; + previous[neighbor] = cell; + if ( + barrier[neighbor] === 0 + && topology.componentId[neighbor] === targetComponent + ) { + target = neighbor; + break; + } + queue[tail++] = neighbor; + } + } + if (target < 0) continue; + const passCells: number[] = []; + for (let cell = target; cell >= 0; cell = previous[cell]!) { + if (barrier[cell] === 1) passCells.push(cell); + } + if (passCells.length === 0 || passCells.length > 16) continue; + for (const cell of passCells) barrier[cell] = 0; + repaired = true; + break; + } + if (!repaired) break; + } + } +} + +function reconcileBarrierMeasuredRegionCoherence( + grid: IndexedAxialGrid, + regionId: Uint8Array, + tierId: Uint8Array, + waterRegime: Uint8Array, + barrier: Uint8Array, + legacyProtectedCell: Uint8Array, + protectedApproachCells: ReadonlySet, +): void { + const initialCounts = Array(REGION_COUNT).fill(0); + for (const region of regionId) initialCounts[region] += 1; + const protectedCell = new Uint8Array(grid.cellCount); + for (const cell of protectedApproachCells) protectedCell[cell] = 1; + + const largestTarget = (region: number) => region < TIER_I_REGION_COUNT + ? region === 4 ? 6_250 : 8_500 + : region < TIER_III_REGION_INDEX ? 9_000 : 9_500; + const minorTarget = (region: number) => region === 4 ? 425 : 250; + + const detachableProtectedFoothold = (component: readonly number[]) => { + if (component.some(cell => legacyProtectedCell[cell] === 1)) return undefined; + const anchors = component.filter(cell => protectedCell[cell] === 1); + if (anchors.length === 0) return Object.freeze([...component]); + // A reviewed gate approach must remain assigned to its endpoint's region, + // but a large island or peninsula behind that approach need not become an + // immutable political exclave. Preserve one connected 128-cell foothold + // containing every protected approach cell, then allow only its hinterland + // to participate in the ordinary same-tier coherence repair. + if (component.length <= 128) return undefined; + const member = new Uint8Array(grid.cellCount); + for (const cell of component) member[cell] = 1; + const keep = new Uint8Array(grid.cellCount); + keep[anchors[0]!] = 1; + let keepCount = 1; + for (const anchor of anchors.slice(1)) { + if (keep[anchor] === 1) continue; + const previous = new Int32Array(grid.cellCount); + previous.fill(-2); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (const cell of component) { + if (keep[cell] !== 1) continue; + previous[cell] = -1; + queue[tail++] = cell; + } + while (head < tail && previous[anchor] === -2) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || member[neighbor] !== 1 || previous[neighbor] !== -2) continue; + previous[neighbor] = cell; + queue[tail++] = neighbor; + } + } + if (previous[anchor] === -2) { + fail('GREATER_REALM_GATE_FOOTHOLD_CONNECTIVITY_INVALID'); + } + for (let cell = anchor; cell >= 0 && keep[cell] !== 1; cell = previous[cell]!) { + keep[cell] = 1; + keepCount += 1; + } + } + for (const anchor of anchors) { + if (keep[anchor] === 0) fail('GREATER_REALM_GATE_FOOTHOLD_PROTECTION_INVALID'); + } + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (const cell of component) { + if (keep[cell] === 1) queue[tail++] = cell; + } + while (head < tail && keepCount < 128) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || member[neighbor] !== 1 || keep[neighbor] === 1) continue; + keep[neighbor] = 1; + keepCount += 1; + queue[tail++] = neighbor; + if (keepCount === 128) break; + } + } + const detachable = component.filter(cell => keep[cell] === 0); + return detachable.length === 0 ? undefined : Object.freeze(detachable); + }; + + // Four fixed sweeps let a recipient consolidate a component donated after + // its prior visit, without an unbounded convergence loop. + for (let sweep = 0; sweep < 4; sweep += 1) { + const sweepTopology = passableRegionTopology(grid, regionId, waterRegime, barrier); + const ordering = Array.from({ length: REGION_COUNT }, (_, region) => region) + .sort((first, second) => ( + sweepTopology.largestSharesBasisPoints[first]! + - sweepTopology.largestSharesBasisPoints[second]! + || first - second + )); + for (const sourceRegion of ordering) { + const included = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + regionId[cell] === sourceRegion + && strategicallyPassableSurface(waterRegime[cell]!) + && barrier[cell] === 0 + ) included[cell] = 1; + } + const components = [...connectedComponents(grid, included)] + .sort((first, second) => second.length - first.length || first[0]! - second[0]!); + if (components.length <= 1) continue; + const largest = components[0]!; + let remainingTotal = components.reduce((sum, component) => sum + component.length, 0); + let remainingMinor = components.slice(1).reduce( + (sum, component) => sum + (component.length < 64 ? component.length : 0), + 0, + ); + for (const component of components.slice(1)) { + const largestSatisfied = largest.length * 10_000 + >= remainingTotal * largestTarget(sourceRegion); + const minorSatisfied = remainingMinor * 10_000 + <= remainingTotal * minorTarget(sourceRegion); + if (largestSatisfied && minorSatisfied) break; + const transferableComponent = detachableProtectedFoothold(component); + if (!transferableComponent) continue; + const contacts = new Uint32Array(REGION_COUNT); + const contactedComponents = Array.from( + { length: REGION_COUNT }, + () => new Set(), + ); + const topology = passableRegionTopology(grid, regionId, waterRegime, barrier); + const availableSwapWater = new Uint32Array(REGION_COUNT); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + !strategicallyPassableSurface(waterRegime[cell]!) + && legacyProtectedCell[cell] === 0 + && protectedCell[cell] === 0 + ) availableSwapWater[regionId[cell]!] += 1; + } + for (const cell of transferableComponent) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || barrier[neighbor] !== 0 + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || tierId[neighbor] !== tierId[cell] + || regionId[neighbor] === sourceRegion + ) continue; + contacts[regionId[neighbor]!] += 1; + const neighborComponent = topology.componentId[neighbor]!; + if (neighborComponent >= 0) { + contactedComponents[regionId[neighbor]!]!.add(neighborComponent); + } + } + } + let targetRegion = -1; + let bestContacts = -1; + let bestProjectedShare = -1; + for (let region = 0; region < REGION_COUNT; region += 1) { + if ( + region === sourceRegion + || GREATER_REALM_REGION_SPECS[region]!.tier + !== GREATER_REALM_REGION_SPECS[sourceRegion]!.tier + || availableSwapWater[region]! < transferableComponent.length + ) continue; + const projectedTotal = topology.passableCounts[region]! + + transferableComponent.length; + const joinedSize = transferableComponent.length + + [...contactedComponents[region]!] + .reduce((sum, componentId) => sum + topology.componentSizes[componentId]!, 0); + const projectedLargest = Math.max(topology.largestCounts[region]!, joinedSize); + const projectedShare = projectedTotal === 0 + ? 0 + : Math.floor((projectedLargest * 10_000) / projectedTotal); + if ( + projectedShare > bestProjectedShare + || ( + projectedShare === bestProjectedShare + && contacts[region]! > bestContacts + ) + || ( + projectedShare === bestProjectedShare + && contacts[region] === bestContacts + && region < targetRegion + ) + ) { + targetRegion = region; + bestContacts = contacts[region]!; + bestProjectedShare = projectedShare; + } + } + if (targetRegion < 0) continue; + const waterSwapCandidates: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + regionId[cell] === targetRegion + && !strategicallyPassableSurface(waterRegime[cell]!) + && legacyProtectedCell[cell] === 0 + && protectedCell[cell] === 0 + ) waterSwapCandidates.push(cell); + } + if (waterSwapCandidates.length < transferableComponent.length) continue; + waterSwapCandidates.sort((first, second) => { + const sourceContacts = (cell: number) => { + let count = 0; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && regionId[neighbor] === sourceRegion) count += 1; + } + return count; + }; + return sourceContacts(second) - sourceContacts(first) || first - second; + }); + for (const cell of transferableComponent) regionId[cell] = targetRegion; + for (let index = 0; index < transferableComponent.length; index += 1) { + regionId[waterSwapCandidates[index]!] = sourceRegion; + } + remainingTotal -= transferableComponent.length; + if (component.length < 64) remainingMinor -= transferableComponent.length; + } + } + } + const finalCounts = Array(REGION_COUNT).fill(0); + for (const region of regionId) finalCounts[region] += 1; + if (finalCounts.some((count, region) => count !== initialCounts[region])) { + fail('GREATER_REALM_BARRIER_REGION_RECONCILIATION_BALANCE_FAILED'); + } +} + +function barriersAndGates( + grid: IndexedAxialGrid, + candidateSeed: GreaterRealmTerrainSeed, + elevation: Int32Array, + uplift: Int32Array, + waterRegime: Uint8Array, + tierId: Uint8Array, + regionId: Uint8Array, + gateGraph: readonly GreaterRealmGateGraphEdge[], + legacyProtectedCell: Uint8Array, +): Readonly<{ + barrier: Uint8Array; + geologicalBarrierBand: Uint8Array; + barrierCrossSections: readonly GreaterRealmPrivateBarrierCrossSection[]; + gates: readonly GreaterRealmPrivateGate[]; + barrierProof: boolean; + gateApproachProof: boolean; + gateRouteRedundancyProof: boolean; + gateGraphProof: boolean; + passableRegionProof: boolean; + regionGraphProof: boolean; + passableTopology: GreaterRealmPassableRegionTopology; + highlandBarrierShareBasisPoints: number; + barrierMeanElevationAdvantage: number; + barrierMeanUpliftAdvantage: number; + measuredMinimumBarrierWidth: number; + measuredMaximumBarrierWidth: number; + geologicalHighlandProof: boolean; +}> { + const boundary = new Uint8Array(grid.cellCount); + const outerBoundary = new Uint8Array(grid.cellCount); + const innerBoundary = new Uint8Array(grid.cellCount); + const edgesByPair = new Map>(); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let directionIndex = 0; directionIndex < HEX_NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + directionIndex]!; + if (neighbor <= cell || tierId[cell] === tierId[neighbor]) continue; + boundary[cell] = 1; + boundary[neighbor] = 1; + const systemBoundary = Math.min(tierId[cell]!, tierId[neighbor]!) === 1 + ? outerBoundary + : innerBoundary; + systemBoundary[cell] = 1; + systemBoundary[neighbor] = 1; + const firstRegion = Math.min(regionId[cell]!, regionId[neighbor]!); + const secondRegion = Math.max(regionId[cell]!, regionId[neighbor]!); + const key = `${firstRegion}:${secondRegion}`; + const edges = edgesByPair.get(key) ?? []; + const firstCell = regionId[cell] === firstRegion ? cell : neighbor; + const secondCell = firstCell === cell ? neighbor : cell; + edges.push(Object.freeze([firstCell, secondCell] as const)); + edgesByPair.set(key, edges); + } + } + const distanceWithinTier = (systemBoundary: Uint8Array): Uint16Array => { + const distance = new Uint16Array(grid.cellCount); + distance.fill(0xffff); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (systemBoundary[cell] !== 1) continue; + distance[cell] = 0; + queue[tail++] = cell; + } + while (head < tail) { + const cell = queue[head++]!; + const nextDistance = distance[cell]! + 1; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || tierId[neighbor] !== tierId[cell] + || distance[neighbor] !== 0xffff + ) continue; + distance[neighbor] = nextDistance; + queue[tail++] = neighbor; + } + } + return distance; + }; + const distanceToOuterBoundary = distanceWithinTier(outerBoundary); + const distanceToInnerBoundary = distanceWithinTier(innerBoundary); + const barrier = new Uint8Array(grid.cellCount); + const geologicalBarrierBand = new Uint8Array(grid.cellCount); + const landElevations = Array.from({ length: grid.cellCount }, (_, cell) => cell) + .filter(cell => ( + strategicallyPassableSurface(waterRegime[cell]!) + && elevation[cell]! > SEA_LEVEL + && legacyProtectedCell[cell] !== 1 + )) + .map(cell => elevation[cell]!) + .sort((first, second) => first - second); + const landUplift = Array.from({ length: grid.cellCount }, (_, cell) => cell) + .filter(cell => ( + strategicallyPassableSurface(waterRegime[cell]!) + && elevation[cell]! > SEA_LEVEL + && legacyProtectedCell[cell] !== 1 + )) + .map(cell => uplift[cell]!) + .sort((first, second) => first - second); + if (landElevations.length === 0 || landUplift.length === 0) { + rejectGreaterRealmCandidate('GREATER_REALM_STRATEGIC_HIGHLAND_REFERENCE_MISSING'); + } + const highlandElevation = landElevations[Math.floor(landElevations.length * 0.58)]!; + const highlandUplift = landUplift[Math.floor(landUplift.length * 0.58)]!; + // Materialize the final band from nearest-boundary distance inside each + // tier. Outer Crown arms use two cells per side (four total); the stronger + // Inner Throne arms use three per side (six total). Width is therefore a + // property of the completed band, not a configured number pasted into the + // report. Water cells remain explicit, counted natural barrier assistance. + const OUTER_BARRIER_SIDE_LAYERS = 2; + const INNER_BARRIER_SIDE_LAYERS = 3; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (legacyProtectedCell[cell] === 1) continue; + if (distanceToOuterBoundary[cell]! < OUTER_BARRIER_SIDE_LAYERS) { + geologicalBarrierBand[cell] |= 1; + } + if (distanceToInnerBoundary[cell]! < INNER_BARRIER_SIDE_LAYERS) { + geologicalBarrierBand[cell] |= 2; + } + if ( + geologicalBarrierBand[cell] !== 0 + && strategicallyPassableSurface(waterRegime[cell]!) + && ( + boundary[cell] === 1 + || elevation[cell]! >= highlandElevation + || uplift[cell]! >= highlandUplift + ) + ) barrier[cell] = 1; + } + const sidePath = ( + endpoint: number, + system: 1 | 2, + ): number[] => { + if ( + legacyProtectedCell[endpoint] === 1 + || (geologicalBarrierBand[endpoint]! & system) === 0 + ) return []; + const previous = new Int32Array(grid.cellCount); + previous.fill(-2); + previous[endpoint] = -1; + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + let target = -1; + queue[tail++] = endpoint; + while (head < tail) { + const cell = queue[head++]!; + let exitsBand = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[neighbor] === tierId[endpoint] + && legacyProtectedCell[neighbor] !== 1 + && (geologicalBarrierBand[neighbor]! & system) === 0 + ) { + exitsBand = true; + break; + } + } + if (exitsBand) { + target = cell; + break; + } + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || previous[neighbor] !== -2 + || tierId[neighbor] !== tierId[endpoint] + || legacyProtectedCell[neighbor] === 1 + || (geologicalBarrierBand[neighbor]! & system) === 0 + ) continue; + previous[neighbor] = cell; + queue[tail++] = neighbor; + } + } + if (target < 0) return []; + const path: number[] = []; + for (let cell = target; cell >= 0; cell = previous[cell]!) path.push(cell); + path.reverse(); + return path; + }; + const barrierCrossSections: GreaterRealmPrivateBarrierCrossSection[] = []; + for (const edges of edgesByPair.values()) { + for (const [first, second] of edges) { + if ( + !strategicallyPassableSurface(waterRegime[first]!) + || !strategicallyPassableSurface(waterRegime[second]!) + ) continue; + const system = (Math.min(tierId[first]!, tierId[second]!) === 1 ? 1 : 2) as 1 | 2; + const firstCells = sidePath(first, system); + const secondCells = sidePath(second, system); + const firstSideCellCount = firstCells.length; + if ( + firstSideCellCount < 2 + || firstSideCellCount > 4 + || secondCells.length < 2 + || secondCells.length > 4 + ) continue; + const cells = [...firstCells.reverse(), ...secondCells]; + // Concave corners can make a longitudinal walk much longer than the + // band's normal distance. Store only bounded local normal witnesses; + // the complete band itself is independently proven from the distance + // fields below. + if (cells.length < 4 || cells.length > 8) continue; + barrierCrossSections.push(Object.freeze({ + firstCell: first, + secondCell: second, + system, + firstSideCellCount, + waterAssistedCellCount: cells.filter(cell => ( + !strategicallyPassableSurface(waterRegime[cell]!) + )).length, + // Keep private coordinate buffers mutable so package retirement can + // overwrite them in place. + cells, + })); + } + } + const gates: GreaterRealmPrivateGate[] = []; + const baseRobustTopology = robustRegionTopology(grid, regionId, waterRegime, barrier); + const usedGateCells = new Set(); + const protectedApproachCells = new Set(); + const requiredApproachCells = new Set(); + const gateChannel = greaterRealmTerrainChannelId('sealed-gate-saddle'); + for (const [firstRegion, secondRegion] of gateGraph) { + const key = `${Math.min(firstRegion, secondRegion)}:${Math.max(firstRegion, secondRegion)}`; + const candidates = [...(edgesByPair.get(key) ?? [])].filter(([first, second]) => ( + waterRegime[first] === 0 + && waterRegime[second] === 0 + && barrier[first] === 1 + && barrier[second] === 1 + )); + candidates.sort(([firstA, secondA], [firstB, secondB]) => { + const score = (first: number, second: number) => elevation[first]! + elevation[second]! + + (greaterRealmCounterRandomU32( + candidateSeed, + gateChannel, + grid.q[first]!, + grid.r[first]!, + ) % 4_001); + return score(firstA, secondA) - score(firstB, secondB) + || firstA - firstB + || secondA - secondB; + }); + type GateSelection = Readonly<{ + edge: readonly [number, number]; + approaches: Readonly<{ + first: readonly number[]; + firstAlternate: readonly number[]; + second: readonly number[]; + secondAlternate: readonly number[]; + all: readonly (readonly number[])[]; + }>; + }>; + type GateApproachCandidate = Readonly<{ + edge: readonly [number, number]; + first: readonly (readonly number[])[]; + second: readonly (readonly number[])[]; + }>; + const approachCandidates: GateApproachCandidate[] = []; + for (const edge of candidates) { + if ( + usedGateCells.has(edge[0]) + || usedGateCells.has(edge[1]) + || protectedApproachCells.has(edge[0]) + || protectedApproachCells.has(edge[1]) + ) continue; + // Endpoints must be distinct, but a narrow geological saddle may carry + // both sealed crossings. Requiring an arbitrary six-cell separation + // rejected otherwise valid natural passes on island/coastal frontiers. + const firstApproaches = barrierApproachPaths( + grid, + edge[0], + firstRegion, + regionId, + waterRegime, + barrier, + baseRobustTopology.componentId, + baseRobustTopology.componentSizes, + ); + const secondApproaches = barrierApproachPaths( + grid, + edge[1], + secondRegion, + regionId, + waterRegime, + barrier, + baseRobustTopology.componentId, + baseRobustTopology.componentSizes, + ); + if (!firstApproaches || !secondApproaches) continue; + approachCandidates.push(Object.freeze({ + edge, + first: firstApproaches, + second: secondApproaches, + })); + } + // Choose both gates as one deterministic pair. A greedy first selection + // can consume the only independent corridor on a narrow frontier even + // when a later pair is valid; pairwise selection provides real route + // redundancy without making ordinary candidate yield order-dependent. + let selected: readonly GateSelection[] = Object.freeze([]); + for (let firstIndex = 0; firstIndex < approachCandidates.length; firstIndex += 1) { + const firstCandidate = approachCandidates[firstIndex]!; + for (let firstOptionOrdinal = 0; firstOptionOrdinal < 32; firstOptionOrdinal += 1) { + const firstApproaches = compatibleGateApproaches( + grid, + tierId, + waterRegime, + barrier, + baseRobustTopology.componentId, + baseRobustTopology.componentSizes, + firstCandidate.first, + firstCandidate.second, + firstOptionOrdinal, + ); + if (!firstApproaches) break; + if (firstApproaches.all.some(path => path.some(cell => usedGateCells.has(cell)))) continue; + for (let secondIndex = 0; secondIndex < approachCandidates.length; secondIndex += 1) { + if (secondIndex === firstIndex) continue; + const secondCandidate = approachCandidates[secondIndex]!; + const endpointSet = new Set([ + ...firstCandidate.edge, + ...secondCandidate.edge, + ]); + if (endpointSet.size !== 4) continue; + for (let secondOptionOrdinal = 0; secondOptionOrdinal < 32; secondOptionOrdinal += 1) { + const secondApproaches = compatibleGateApproaches( + grid, + tierId, + waterRegime, + barrier, + baseRobustTopology.componentId, + baseRobustTopology.componentSizes, + secondCandidate.first, + secondCandidate.second, + secondOptionOrdinal, + ); + if (!secondApproaches) break; + const allPaths: readonly (readonly number[])[] = Object.freeze([ + ...firstApproaches.all, + ...secondApproaches.all, + ]); + if (allPaths.some((path: readonly number[]) => path.some((cell: number) => ( + usedGateCells.has(cell) || endpointSet.has(cell) + )))) continue; + const combinedCarved: Set = new Set( + allPaths.flatMap((path: readonly number[]) => [...path]), + ); + const tentativeGatePairs = [ + ...gates.map(gate => Object.freeze([ + gate.firstCell, + gate.secondCell, + ] as const)), + firstCandidate.edge, + secondCandidate.edge, + ] as const; + const tentativeGateMate = new Map(); + for (const [firstEndpoint, secondEndpoint] of tentativeGatePairs) { + tentativeGateMate.set(firstEndpoint, secondEndpoint); + tentativeGateMate.set(secondEndpoint, firstEndpoint); + } + const unavoidableOpenApproaches = new Set([ + ...requiredApproachCells, + ...firstApproaches.first, + ...firstApproaches.firstAlternate, + ...firstApproaches.second, + ...firstApproaches.secondAlternate, + ...secondApproaches.first, + ...secondApproaches.firstAlternate, + ...secondApproaches.second, + ...secondApproaches.secondAlternate, + ]); + // Opening a reviewed gate pair must expose exactly that one physical + // cross-tier edge. Reject endpoints whose neighbouring gate mouths + // or required approaches would become an unrecorded side entrance. + // Non-required carved shoulders may still be re-sealed after the + // same-tier coherence repair below. + let isolatedGateMouths = true; + for (const [endpoint, mate] of tentativeGateMate) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || tierId[endpoint] === tierId[neighbor] + || !strategicallyPassableSurface(waterRegime[endpoint]!) + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || neighbor === mate + ) continue; + if ( + tentativeGateMate.has(neighbor) + || unavoidableOpenApproaches.has(neighbor) + ) { + isolatedGateMouths = false; + break; + } + } + if (!isolatedGateMouths) break; + } + if (!isolatedGateMouths) continue; + let combinedBypass = false; + for (const cell of combinedCarved) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[cell] !== tierId[neighbor] + && strategicallyPassableSurface(waterRegime[cell]!) + && strategicallyPassableSurface(waterRegime[neighbor]!) + && (barrier[cell] === 0 || combinedCarved.has(cell)) + && (barrier[neighbor] === 0 || combinedCarved.has(neighbor)) + ) { + combinedBypass = true; + break; + } + } + if (combinedBypass) break; + } + if (combinedBypass) continue; + selected = Object.freeze([ + Object.freeze({ edge: firstCandidate.edge, approaches: firstApproaches }), + Object.freeze({ edge: secondCandidate.edge, approaches: secondApproaches }), + ]); + break; + } + if (selected.length === 2) break; + } + if (selected.length === 2) break; + } + if (selected.length === 2) break; + } + for (const selection of selected) { + usedGateCells.add(selection.edge[0]); + usedGateCells.add(selection.edge[1]); + for (const cell of selection.approaches.first) requiredApproachCells.add(cell); + for (const cell of selection.approaches.firstAlternate) requiredApproachCells.add(cell); + for (const cell of selection.approaches.second) requiredApproachCells.add(cell); + for (const cell of selection.approaches.secondAlternate) requiredApproachCells.add(cell); + for (const path of selection.approaches.all) { + for (const cell of path) { + barrier[cell] = 0; + protectedApproachCells.add(cell); + } + } + } + for (const { edge: [firstCell, secondCell], approaches } of selected) { + gates.push(Object.freeze({ + gateIndex: gates.length, + firstRegion, + secondRegion, + firstCell, + secondCell, + firstApproachPath: approaches.first, + firstAlternateApproachPath: approaches.firstAlternate, + secondApproachPath: approaches.second, + secondAlternateApproachPath: approaches.secondAlternate, + })); + } + } + // Approach carving for a later pass may cross the shoulder of an earlier + // pass in the same region. Gate endpoints themselves are immutable sealed + // barrier cells; restore that invariant before graph/topology proofs. + const lockedGateEndpoint = new Uint8Array(grid.cellCount); + for (const gate of gates) { + barrier[gate.firstCell] = 1; + barrier[gate.secondCell] = 1; + lockedGateEndpoint[gate.firstCell] = 1; + lockedGateEndpoint[gate.secondCell] = 1; + } + // A cross-tier edge only needs one sealed side. Keeping both sides of every + // edge turned natural passes into broad lowland walls and split otherwise + // coherent regions. Retain the geologically stronger side of each ordinary + // boundary edge, while leaving the two reviewed cells of every gate sealed. + const retainedBoundaryBarrier = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor <= cell + || tierId[cell] === tierId[neighbor] + || !strategicallyPassableSurface(waterRegime[cell]!) + || !strategicallyPassableSurface(waterRegime[neighbor]!) + ) continue; + const selectable = (candidate: number) => ( + legacyProtectedCell[candidate] !== 1 + && !protectedApproachCells.has(candidate) + ); + let selected = -1; + if (lockedGateEndpoint[cell] === 1) selected = cell; + else if (lockedGateEndpoint[neighbor] === 1) selected = neighbor; + else if (!selectable(cell)) selected = selectable(neighbor) ? neighbor : -1; + else if (!selectable(neighbor)) selected = cell; + else { + const cellHighland = elevation[cell]! >= highlandElevation + || uplift[cell]! >= highlandUplift; + const neighborHighland = elevation[neighbor]! >= highlandElevation + || uplift[neighbor]! >= highlandUplift; + if (cellHighland !== neighborHighland) selected = cellHighland ? cell : neighbor; + else { + const cellScore = elevation[cell]! * 4 + uplift[cell]!; + const neighborScore = elevation[neighbor]! * 4 + uplift[neighbor]!; + selected = cellScore > neighborScore + ? cell + : neighborScore > cellScore ? neighbor : Math.min(cell, neighbor); + } + } + if (selected >= 0) retainedBoundaryBarrier[selected] = 1; + } + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (boundary[cell] !== 1) continue; + if (lockedGateEndpoint[cell] === 1) barrier[cell] = 1; + else if (protectedApproachCells.has(cell)) barrier[cell] = 0; + else barrier[cell] = retainedBoundaryBarrier[cell]!; + } + reconnectBarrierSplitRegionComponents( + grid, + regionId, + tierId, + waterRegime, + barrier, + gates, + ); + reconcileBarrierMeasuredRegionCoherence( + grid, + regionId, + tierId, + waterRegime, + barrier, + legacyProtectedCell, + protectedApproachCells, + ); + // Same-tier coherence repair must never become the last writer on a Crown + // boundary. Re-seal any newly exposed cross-tier edge on an unreviewed, + // non-approach side; a contact protected on both sides remains an explicit + // proof failure instead of silently becoming an extra gate. + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor <= cell + || tierId[cell] === tierId[neighbor] + || !strategicallyPassableSurface(waterRegime[cell]!) + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || barrier[cell] === 1 + || barrier[neighbor] === 1 + ) continue; + const cellProtected = protectedApproachCells.has(cell); + const neighborProtected = protectedApproachCells.has(neighbor); + if (cellProtected && neighborProtected) { + const cellRequired = requiredApproachCells.has(cell); + const neighborRequired = requiredApproachCells.has(neighbor); + if (cellRequired && neighborRequired) continue; + if (cellRequired) barrier[neighbor] = 1; + else if (neighborRequired) barrier[cell] = 1; + else { + const cellScore = elevation[cell]! * 4 + uplift[cell]!; + const neighborScore = elevation[neighbor]! * 4 + uplift[neighbor]!; + barrier[cellScore >= neighborScore ? cell : neighbor] = 1; + } + continue; + } + if (cellProtected) barrier[neighbor] = 1; + else if (neighborProtected) barrier[cell] = 1; + else { + const cellScore = elevation[cell]! * 4 + uplift[cell]!; + const neighborScore = elevation[neighbor]! * 4 + uplift[neighbor]!; + barrier[cellScore >= neighborScore ? cell : neighbor] = 1; + } + } + } + // Reconcile the final impassable crest to the measured geological band after + // every coherence mutation. The broader band intentionally includes + // traversable foothills; every blocking cell must nevertheless be part of + // that band, while the independent cross-tier cut proof guarantees that no + // foothill creates an ungated route through the system. + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (legacyProtectedCell[cell] === 1) continue; + if (geologicalBarrierBand[cell] === 0) barrier[cell] = 0; + } + // A gate is a single reviewed physical edge, not merely a label placed on a + // broad pass. Model the future open state without mutating the sealed gate + // endpoints: every other passable cross-tier neighbour must remain sealed. + // Required primary approaches are never sacrificed to manufacture this + // proof; a collision with one makes the candidate ineligible instead. + const gateMate = new Int32Array(grid.cellCount); + gateMate.fill(-1); + let gatePhysicalCorridorProof = true; + for (const gate of gates) { + if (gateMate[gate.firstCell] >= 0 || gateMate[gate.secondCell] >= 0) { + gatePhysicalCorridorProof = false; + continue; + } + gateMate[gate.firstCell] = gate.secondCell; + gateMate[gate.secondCell] = gate.firstCell; + } + for (let endpoint = 0; endpoint < grid.cellCount; endpoint += 1) { + const mate = gateMate[endpoint]!; + if (mate < 0) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || neighbor === mate + || tierId[endpoint] === tierId[neighbor] + || !strategicallyPassableSurface(waterRegime[endpoint]!) + || !strategicallyPassableSurface(waterRegime[neighbor]!) + || barrier[neighbor] === 1 + ) continue; + if (gateMate[neighbor]! >= 0 || requiredApproachCells.has(neighbor)) { + gatePhysicalCorridorProof = false; + } else { + barrier[neighbor] = 1; + } + } + } + let futureOpenCrossTierEdges = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor <= cell + || tierId[cell] === tierId[neighbor] + || !strategicallyPassableSurface(waterRegime[cell]!) + || !strategicallyPassableSurface(waterRegime[neighbor]!) + ) continue; + const cellWouldBeOpen = barrier[cell] === 0 || gateMate[cell]! >= 0; + const neighborWouldBeOpen = barrier[neighbor] === 0 || gateMate[neighbor]! >= 0; + if (!cellWouldBeOpen || !neighborWouldBeOpen) continue; + futureOpenCrossTierEdges += 1; + if (gateMate[cell] !== neighbor || gateMate[neighbor] !== cell) { + gatePhysicalCorridorProof = false; + } + } + } + if (futureOpenCrossTierEdges !== gates.length) gatePhysicalCorridorProof = false; + let barrierProof = true; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let directionIndex = 0; directionIndex < HEX_NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + directionIndex]!; + if ( + neighbor >= 0 + && tierId[cell] !== tierId[neighbor] + && strategicallyPassableSurface(waterRegime[cell]!) + && strategicallyPassableSurface(waterRegime[neighbor]!) + && barrier[cell] !== 1 + && barrier[neighbor] !== 1 + ) barrierProof = false; + } + } + const gateGraphProof = gatePhysicalCorridorProof + && gates.length === GREATER_REALM_REQUIRED_GATE_COUNT + && gateGraph.length === 9 + && gateGraph.every(([firstRegion, secondRegion]) => { + const pairGates = gates.filter(gate => ( + gate.firstRegion === firstRegion && gate.secondRegion === secondRegion + )); + return pairGates.length === 2 && pairGates.every(gate => { + if ( + regionId[gate.firstCell] !== firstRegion + || regionId[gate.secondCell] !== secondRegion + || waterRegime[gate.firstCell] !== 0 + || waterRegime[gate.secondCell] !== 0 + ) return false; + if (barrier[gate.firstCell] !== 1 || barrier[gate.secondCell] !== 1) return false; + const neighborOffset = gate.firstCell * HEX_NEIGHBOR_COUNT; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[neighborOffset + direction] === gate.secondCell) return true; + } + return false; + }); + }); + const passableTopology = passableRegionTopology(grid, regionId, waterRegime, barrier); + const robustTopology = robustRegionTopology(grid, regionId, waterRegime, barrier); + const adjacentGateApproachProof = gates.length === GREATER_REALM_REQUIRED_GATE_COUNT + && gates.every(gate => ([ + [gate.firstCell, gate.firstRegion], + [gate.secondCell, gate.secondRegion], + ] as const).every(([endpoint, region]) => { + const approachCells: number[] = []; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && regionId[neighbor] === region + && waterRegime[neighbor] === 0 + && barrier[neighbor] === 0 + && passableTopology.componentId[neighbor]! >= 0 + && passableTopology.componentSizes[passableTopology.componentId[neighbor]!]! >= 64 + ) approachCells.push(neighbor); + } + return approachCells.length >= 1; + })); + const routeFinalRobustComponent = ( + endpoint: number, + region: number, + path: readonly number[], + ): number => { + if (path.length === 0 || new Set(path).size !== path.length) return -1; + let startsAtEndpoint = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction] === path[0]) { + startsAtEndpoint = true; + break; + } + } + if (!startsAtEndpoint) return -1; + for (let index = 0; index < path.length; index += 1) { + const cell = path[index]!; + if ( + regionId[cell] !== region + || waterRegime[cell] !== 0 + || barrier[cell] !== 0 + ) return -1; + if (index > 0) { + let adjacent = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[path[index - 1]! * HEX_NEIGHBOR_COUNT + direction] === cell) { + adjacent = true; + break; + } + } + if (!adjacent) return -1; + } + } + const target = path[path.length - 1]!; + const component = robustTopology.componentId[target]!; + return component >= 0 && robustTopology.componentSizes[component]! >= 64 + ? component + : -1; + }; + const routePairIsVertexDisjoint = ( + endpoint: number, + region: number, + primary: readonly number[], + alternate: readonly number[], + ): boolean => { + if ( + primary.length === 0 + || alternate.length === 0 + || primary[0] === alternate[0] + || !gateApproachCorridorsDisjoint(primary, alternate) + ) return false; + const primaryComponent = routeFinalRobustComponent(endpoint, region, primary); + const alternateComponent = routeFinalRobustComponent(endpoint, region, alternate); + return primaryComponent >= 0 && primaryComponent === alternateComponent; + }; + const gateRouteRedundancyProof = gates.length === GREATER_REALM_REQUIRED_GATE_COUNT + && gates.every(gate => ( + routePairIsVertexDisjoint( + gate.firstCell, + gate.firstRegion, + gate.firstApproachPath, + gate.firstAlternateApproachPath, + ) + && routePairIsVertexDisjoint( + gate.secondCell, + gate.secondRegion, + gate.secondApproachPath, + gate.secondAlternateApproachPath, + ) + )); + const gateApproachProof = adjacentGateApproachProof && gateRouteRedundancyProof; + // All ordinary cross-tier land contacts are sealed by the mountain cut. + // Opening only the reviewed gate edges therefore derives (rather than + // assumes) the future traversable region graph from the cell topology. + const traversablePairs = new Set(gates.map(gate => `${gate.firstRegion}:${gate.secondRegion}`)); + const expectedPairs = new Set(gateGraph.map( + ([firstRegion, secondRegion]) => `${firstRegion}:${secondRegion}`, + )); + const regionGraphProof = barrierProof + && gateGraphProof + && gateApproachProof + && passableTopology.proof + && traversablePairs.size === expectedPairs.size + && [...traversablePairs].every(key => expectedPairs.has(key)); + let barrierCells = 0; + let highlandBarrierCells = 0; + let barrierElevationTotal = 0; + let barrierUpliftTotal = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (barrier[cell] !== 1) continue; + barrierCells += 1; + barrierElevationTotal += elevation[cell]!; + barrierUpliftTotal += uplift[cell]!; + if (elevation[cell]! >= highlandElevation || uplift[cell]! >= highlandUplift) { + highlandBarrierCells += 1; + } + } + const landElevationTotal = landElevations.reduce((total, value) => total + value, 0); + const landUpliftTotal = landUplift.reduce((total, value) => total + value, 0); + const highlandBarrierShareBasisPoints = barrierCells === 0 + ? 0 + : Math.round((highlandBarrierCells * 10_000) / barrierCells); + const barrierMeanElevationAdvantage = barrierCells === 0 + ? -INT32_MAX + : Math.floor(barrierElevationTotal / barrierCells) + - Math.floor(landElevationTotal / landElevations.length); + const barrierMeanUpliftAdvantage = barrierCells === 0 + ? -INT32_MAX + : Math.floor(barrierUpliftTotal / barrierCells) + - Math.floor(landUpliftTotal / landUplift.length); + let measuredMinimumBarrierWidth = INT32_MAX; + let measuredMaximumBarrierWidth = 0; + let barrierWidthProof = barrierCrossSections.length > 0; + const measureBandSideThickness = (endpoint: number, system: 1 | 2): number => { + if ((geologicalBarrierBand[endpoint]! & system) === 0) return 0; + const distance = new Uint16Array(grid.cellCount); + distance.fill(0xffff); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + distance[endpoint] = 0; + queue[tail++] = endpoint; + while (head < tail) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[neighbor] === tierId[endpoint] + && legacyProtectedCell[neighbor] !== 1 + && (geologicalBarrierBand[neighbor]! & system) === 0 + ) return distance[cell]! + 1; + } + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || distance[neighbor] !== 0xffff + || tierId[neighbor] !== tierId[endpoint] + || legacyProtectedCell[neighbor] === 1 + || (geologicalBarrierBand[neighbor]! & system) === 0 + ) continue; + distance[neighbor] = distance[cell]! + 1; + queue[tail++] = neighbor; + } + } + return 0; + }; + const expectedStrategicBoundaryPairs = new Set(gateGraph.map( + ([firstRegion, secondRegion]) => `${firstRegion}:${secondRegion}`, + )); + let distanceFieldBandProof = true; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const expectedBand = legacyProtectedCell[cell] === 1 + ? 0 + : (distanceToOuterBoundary[cell]! < OUTER_BARRIER_SIDE_LAYERS ? 1 : 0) + | (distanceToInnerBoundary[cell]! < INNER_BARRIER_SIDE_LAYERS ? 2 : 0); + if (geologicalBarrierBand[cell] !== expectedBand) distanceFieldBandProof = false; + if (barrier[cell] === 1 && geologicalBarrierBand[cell] === 0) { + barrierWidthProof = false; + } + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor <= cell || tierId[cell] === tierId[neighbor]) continue; + } + } + const crossSectionEdges = new Set(); + const witnessedStrategicBoundaryPairs = new Set(); + for (const crossSection of barrierCrossSections) { + const edgeKey = crossSection.firstCell < crossSection.secondCell + ? `${crossSection.firstCell}:${crossSection.secondCell}` + : `${crossSection.secondCell}:${crossSection.firstCell}`; + if (crossSectionEdges.has(edgeKey)) barrierWidthProof = false; + crossSectionEdges.add(edgeKey); + const witnessedPair = [ + Math.min(regionId[crossSection.firstCell]!, regionId[crossSection.secondCell]!), + Math.max(regionId[crossSection.firstCell]!, regionId[crossSection.secondCell]!), + ].join(':'); + if (expectedStrategicBoundaryPairs.has(witnessedPair)) { + witnessedStrategicBoundaryPairs.add(witnessedPair); + } + const firstThickness = measureBandSideThickness( + crossSection.firstCell, + crossSection.system, + ); + const secondThickness = measureBandSideThickness( + crossSection.secondCell, + crossSection.system, + ); + const measuredWidth = firstThickness + secondThickness; + measuredMinimumBarrierWidth = Math.min(measuredMinimumBarrierWidth, measuredWidth); + measuredMaximumBarrierWidth = Math.max(measuredMaximumBarrierWidth, measuredWidth); + if ( + measuredWidth < 4 + || measuredWidth > 8 + || crossSection.cells.length !== measuredWidth + || crossSection.firstSideCellCount !== firstThickness + || crossSection.firstSideCellCount < 1 + || crossSection.firstSideCellCount >= crossSection.cells.length + || crossSection.cells[crossSection.firstSideCellCount - 1] !== crossSection.firstCell + || crossSection.cells[crossSection.firstSideCellCount] !== crossSection.secondCell + || crossSection.waterAssistedCellCount !== crossSection.cells.filter(cell => ( + !strategicallyPassableSurface(waterRegime[cell]!) + )).length + ) barrierWidthProof = false; + for (let index = 0; index < crossSection.cells.length; index += 1) { + const cell = crossSection.cells[index]!; + const expectedEndpoint = index < crossSection.firstSideCellCount + ? crossSection.firstCell + : crossSection.secondCell; + if ( + legacyProtectedCell[cell] === 1 + || (geologicalBarrierBand[cell]! & crossSection.system) === 0 + || tierId[cell] !== tierId[expectedEndpoint] + ) barrierWidthProof = false; + if (index === 0) continue; + const previous = crossSection.cells[index - 1]!; + let adjacent = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[previous * HEX_NEIGHBOR_COUNT + direction] === cell) { + adjacent = true; + break; + } + } + if (!adjacent) barrierWidthProof = false; + } + } + if (barrierCrossSections.length === 0) measuredMinimumBarrierWidth = 0; + barrierWidthProof = barrierWidthProof + && distanceFieldBandProof + && barrierProof + && witnessedStrategicBoundaryPairs.size === expectedStrategicBoundaryPairs.size + && [...witnessedStrategicBoundaryPairs].every( + pair => expectedStrategicBoundaryPairs.has(pair), + ) + && measuredMinimumBarrierWidth >= 4 + && measuredMaximumBarrierWidth <= 8; + const geologicalHighlandProof = barrierWidthProof + && highlandBarrierShareBasisPoints >= 6_500 + && ( + barrierMeanElevationAdvantage >= 300 + || barrierMeanUpliftAdvantage >= 100 + ); + return Object.freeze({ + barrier, + geologicalBarrierBand, + barrierCrossSections: Object.freeze(barrierCrossSections), + gates: Object.freeze(gates), + barrierProof, + gateApproachProof, + gateRouteRedundancyProof, + gateGraphProof, + passableRegionProof: passableTopology.proof, + regionGraphProof, + passableTopology, + highlandBarrierShareBasisPoints, + barrierMeanElevationAdvantage, + barrierMeanUpliftAdvantage, + measuredMinimumBarrierWidth, + measuredMaximumBarrierWidth, + geologicalHighlandProof, + }); +} + +function castleAndPotentialSites( + grid: IndexedAxialGrid, + candidateSeed: GreaterRealmTerrainSeed, + elevation: Int32Array, + waterRegime: Uint8Array, + tierId: Uint8Array, + regionId: Uint8Array, + barrier: Uint8Array, + gates: readonly GreaterRealmPrivateGate[], + legacyProtectedCell: Uint8Array, + legacyCastleSlot: Uint8Array, +): Readonly<{ + castleSlot: Uint8Array; + resourcePotential: Uint8Array; + corePotential: Uint8Array; + castleCount: number; + placementProof: boolean; + resourcePotentialCount: number; + corePotentialCount: number; +}> { + const gateCells = new Uint8Array(grid.cellCount); + for (const gate of gates) { + gateCells[gate.firstCell] = 1; + gateCells[gate.secondCell] = 1; + } + const gateDistance = gates.length > 0 + ? distanceFromMask(grid, gateCells) + : new Uint16Array(grid.cellCount).fill(0xffff); + const topology = passableRegionTopology(grid, regionId, waterRegime, barrier); + const candidateChannel = greaterRealmTerrainChannelId('castle-suitability-order'); + const castleSlot = new Uint8Array(legacyCastleSlot); + const selectedAll: number[] = []; + let castleCount = 0; + for (let cell = 0; cell < castleSlot.length; cell += 1) { + if (castleSlot[cell] !== 1) continue; + castleCount += 1; + selectedAll.push(cell); + } + if (castleCount !== GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.castleSlotCount) { + fail('GREATER_REALM_LEGACY_CASTLE_SLOT_COUNT_INVALID'); + } + for (let region = 0; region < TIER_I_REGION_COUNT; region += 1) { + // Region zero already contains the exact 100 deployed castle slots. The + // other five regions receive dormant candidate slots only. + if (region === 0) continue; + const candidates: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + regionId[cell] !== region + || tierId[cell] !== 1 + || waterRegime[cell] !== 0 + || barrier[cell] === 1 + || gateCells[cell] === 1 + || gateDistance[cell]! < 3 + || topology.componentId[cell]! < 0 + || topology.componentSizes[topology.componentId[cell]!]! < 200 + ) continue; + let passableNeighbors = 0; + let maximumDrop = 0; + const neighborPassable = Array(HEX_NEIGHBOR_COUNT).fill(false); + for (let directionIndex = 0; directionIndex < HEX_NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + directionIndex]!; + if (neighbor < 0) continue; + maximumDrop = Math.max(maximumDrop, Math.abs(elevation[cell]! - elevation[neighbor]!)); + if ( + regionId[neighbor] === region + && waterRegime[neighbor] === 0 + && barrier[neighbor] === 0 + && topology.componentId[neighbor] === topology.componentId[cell] + ) { + neighborPassable[directionIndex] = true; + passableNeighbors += 1; + } + } + const hasLocalAlternateRoute = neighborPassable.some((passable, direction) => ( + passable && neighborPassable[(direction + 1) % HEX_NEIGHBOR_COUNT] + )); + if (passableNeighbors >= 4 && hasLocalAlternateRoute && maximumDrop <= 6_000) { + candidates.push(cell); + } + } + let selected: number[] = []; + for (let attempt = 0; attempt < 16 && selected.length < 100; attempt += 1) { + const ordered = [...candidates].sort((first, second) => { + const sample = region * 32 + attempt; + const firstScore = greaterRealmCounterRandomU32( + candidateSeed, + candidateChannel, + grid.q[first]!, + grid.r[first]!, + sample, + ); + const secondScore = greaterRealmCounterRandomU32( + candidateSeed, + candidateChannel, + grid.q[second]!, + grid.r[second]!, + sample, + ); + return firstScore - secondScore || first - second; + }); + const trial: number[] = []; + for (const cell of ordered) { + if ([...selectedAll, ...trial].some(existing => axialDistance( + grid.q[cell]!, + grid.r[cell]!, + grid.q[existing]!, + grid.r[existing]!, + ) < 5)) continue; + trial.push(cell); + if (trial.length === 100) break; + } + if (trial.length > selected.length) selected = trial; + } + for (const cell of selected) { + selectedAll.push(cell); + castleSlot[cell] = 1; + } + castleCount += selected.length; + } + let placementProof = castleCount === GREATER_REALM_REQUIRED_CASTLE_SLOT_COUNT; + for (let ordinal = 0; ordinal < selectedAll.length; ordinal += 1) { + const cell = selectedAll[ordinal]!; + if (legacyCastleSlot[cell] === 1) continue; + const region = regionId[cell]!; + let independentlyVerifiedPassableNeighbors = 0; + for (let directionIndex = 0; directionIndex < HEX_NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + directionIndex]!; + if ( + neighbor >= 0 + && regionId[neighbor] === region + && waterRegime[neighbor] === 0 + && barrier[neighbor] === 0 + && topology.componentId[neighbor] === topology.componentId[cell] + ) independentlyVerifiedPassableNeighbors += 1; + } + if ( + region < 1 + || region >= TIER_I_REGION_COUNT + || tierId[cell] !== 1 + || waterRegime[cell] !== 0 + || barrier[cell] !== 0 + || gateDistance[cell]! < 3 + || topology.componentId[cell]! < 0 + || topology.componentSizes[topology.componentId[cell]!]! < 200 + || independentlyVerifiedPassableNeighbors < 4 + ) placementProof = false; + for (let previous = 0; previous < ordinal; previous += 1) { + const other = selectedAll[previous]!; + if (axialDistance( + grid.q[cell]!, + grid.r[cell]!, + grid.q[other]!, + grid.r[other]!, + ) < 5) placementProof = false; + } + } + const resourcePotential = new Uint8Array(grid.cellCount); + const corePotential = new Uint8Array(grid.cellCount); + let resourcePotentialCount = 0; + let corePotentialCount = 0; + const resourceChannel = greaterRealmTerrainChannelId('resource-potential'); + const coreChannel = greaterRealmTerrainChannelId('core-potential'); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + waterRegime[cell] !== 0 + || barrier[cell] === 1 + || castleSlot[cell] === 1 + || legacyProtectedCell[cell] === 1 + ) continue; + const resourceThreshold = tierId[cell] === 1 ? 2_000 : tierId[cell] === 2 ? 2_800 : 4_000; + const coreThreshold = tierId[cell] === 1 ? 1_400 : tierId[cell] === 2 ? 2_000 : 2_800; + if ((greaterRealmCounterRandomU32( + candidateSeed, + resourceChannel, + grid.q[cell]!, + grid.r[cell]!, + ) % 10_000) < resourceThreshold) { + resourcePotential[cell] = tierId[cell]!; + resourcePotentialCount += 1; + } + if ((greaterRealmCounterRandomU32( + candidateSeed, + coreChannel, + grid.q[cell]!, + grid.r[cell]!, + ) % 10_000) < coreThreshold) { + corePotential[cell] = tierId[cell]!; + corePotentialCount += 1; + } + } + return Object.freeze({ + castleSlot, + resourcePotential, + corePotential, + castleCount, + placementProof, + resourcePotentialCount, + corePotentialCount, + }); +} + +function dormantThroneAnchor( + grid: IndexedAxialGrid, + elevation: Int32Array, + waterRegime: Uint8Array, + regionId: Uint8Array, + barrier: Uint8Array, + castleSlot: Uint8Array, + resourcePotential: Uint8Array, + corePotential: Uint8Array, + gates: readonly GreaterRealmPrivateGate[], +): Readonly<{ + mask: Uint8Array; + proof: boolean; + barrierClearance: number; +}> { + const distanceToBarrier = distanceFromMask(grid, barrier); + const gateCells = new Uint8Array(grid.cellCount); + for (const gate of gates) { + gateCells[gate.firstCell] = 1; + gateCells[gate.secondCell] = 1; + } + let centerQTotal = 0; + let centerRTotal = 0; + let centerCells = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (regionId[cell] !== TIER_III_REGION_INDEX) continue; + centerQTotal += grid.q[cell]!; + centerRTotal += grid.r[cell]!; + centerCells += 1; + } + if (centerCells === 0) fail('GREATER_REALM_THRONE_REGION_INVARIANT'); + const roundedRatio = (numerator: number) => numerator >= 0 + ? Math.floor((numerator * 2 + centerCells) / (centerCells * 2)) + : -Math.floor((-numerator * 2 + centerCells) / (centerCells * 2)); + const centerQ = roundedRatio(centerQTotal); + const centerR = roundedRatio(centerRTotal); + let selected = -1; + let selectedScore = Number.NEGATIVE_INFINITY; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + regionId[cell] !== TIER_III_REGION_INDEX + || waterRegime[cell] !== 0 + || barrier[cell] !== 0 + || elevation[cell]! <= SEA_LEVEL + || distanceToBarrier[cell]! < 4 + || castleSlot[cell] !== 0 + || resourcePotential[cell] !== 0 + || corePotential[cell] !== 0 + || gateCells[cell] !== 0 + ) continue; + let passableNeighbors = 0; + let footprintClear = true; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && regionId[neighbor] === TIER_III_REGION_INDEX + && waterRegime[neighbor] === 0 + && barrier[neighbor] === 0 + ) passableNeighbors += 1; + if ( + neighbor < 0 + || castleSlot[neighbor] !== 0 + || resourcePotential[neighbor] !== 0 + || corePotential[neighbor] !== 0 + || gateCells[neighbor] !== 0 + ) footprintClear = false; + } + if (passableNeighbors < 5 || !footprintClear) continue; + const score = distanceToBarrier[cell]! * 4_000 + + Math.floor(Math.max(0, elevation[cell]!) / 4) + - axialDistance(grid.q[cell]!, grid.r[cell]!, centerQ, centerR) * 120; + if (score > selectedScore || (score === selectedScore && cell < selected)) { + selected = cell; + selectedScore = score; + } + } + const mask = new Uint8Array(grid.cellCount); + if (selected >= 0) mask[selected] = 1; + let selectedPassableNeighbors = 0; + let selectedFootprintClear = selected >= 0; + if (selected >= 0) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[selected * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && regionId[neighbor] === TIER_III_REGION_INDEX + && waterRegime[neighbor] === 0 + && barrier[neighbor] === 0 + ) selectedPassableNeighbors += 1; + if ( + neighbor < 0 + || castleSlot[neighbor] !== 0 + || resourcePotential[neighbor] !== 0 + || corePotential[neighbor] !== 0 + || gateCells[neighbor] !== 0 + ) selectedFootprintClear = false; + } + } + return Object.freeze({ + mask, + proof: selected >= 0 + && mask.reduce((total, value) => total + value, 0) === 1 + && regionId[selected] === TIER_III_REGION_INDEX + && waterRegime[selected] === 0 + && barrier[selected] === 0 + && castleSlot[selected] === 0 + && resourcePotential[selected] === 0 + && corePotential[selected] === 0 + && gateCells[selected] === 0 + && selectedPassableNeighbors >= 5 + && selectedFootprintClear + && distanceToBarrier[selected]! >= 4, + barrierClearance: selected < 0 ? 0 : distanceToBarrier[selected]!, + }); +} + +function boundaryMetrics( + grid: IndexedAxialGrid, + waterRegime: Uint8Array, +): Readonly<{ + boundaryCells: number; + maximumRadiusShareBasisPoints: number; + rotationalSimilarityBasisPoints: number; + maximumAlignedBoundaryRun: number; + minimumBoundaryLandDistance: number; + saltwaterBoundaryBasisPoints: number; + naturalBoundary: boolean; + deepOceanBoundary: boolean; +}> { + const boundary: number[] = []; + const boundaryByMissingDirection = Array.from( + { length: HEX_NEIGHBOR_COUNT }, + () => new Uint8Array(grid.cellCount), + ); + const land = new Uint8Array(grid.cellCount); + const radiusCounts = new Map(); + const keys = new Set(); + for (let index = 0; index < grid.cellCount; index += 1) { + keys.add(`${grid.q[index]},${grid.r[index]}`); + if (waterRegime[index] === 0) land[index] = 1; + let isBoundary = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[index * HEX_NEIGHBOR_COUNT + direction] !== -1) continue; + boundaryByMissingDirection[direction]![index] = 1; + isBoundary = true; + } + if (isBoundary) { + boundary.push(index); + const radius = axialDistance(grid.q[index]!, grid.r[index]!); + radiusCounts.set(radius, (radiusCounts.get(radius) ?? 0) + 1); + } + } + const maximumRadiusCount = Math.max(0, ...radiusCounts.values()); + const maximumRadiusShareBasisPoints = boundary.length === 0 + ? 10_000 + : Math.round((maximumRadiusCount * 10_000) / boundary.length); + let rotatedIntersection = 0; + for (let index = 0; index < grid.cellCount; index += 1) { + const rotatedQ = -grid.r[index]!; + const rotatedR = grid.q[index]! + grid.r[index]!; + if (keys.has(`${rotatedQ},${rotatedR}`)) rotatedIntersection += 1; + } + const rotationalSimilarityBasisPoints = Math.round( + (rotatedIntersection * 10_000) / (grid.cellCount * 2 - rotatedIntersection), + ); + const maximumRadius = Math.max(...boundary.map(index => axialDistance(grid.q[index]!, grid.r[index]!))); + const distanceToLand = distanceFromMask(grid, land); + const minimumBoundaryLandDistance = Math.min(...boundary.map(index => distanceToLand[index]!)); + const saltwaterBoundaryCells = boundary.filter(index => waterRegime[index] === 1).length; + const saltwaterBoundaryBasisPoints = boundary.length === 0 + ? 0 + : Math.round((saltwaterBoundaryCells * 10_000) / boundary.length); + let maximumAlignedBoundaryRun = 0; + for (const directionBoundary of boundaryByMissingDirection) { + for (const component of connectedComponents(grid, directionBoundary)) { + maximumAlignedBoundaryRun = Math.max(maximumAlignedBoundaryRun, component.length); + } + } + return Object.freeze({ + boundaryCells: boundary.length, + maximumRadiusShareBasisPoints, + rotationalSimilarityBasisPoints, + maximumAlignedBoundaryRun, + minimumBoundaryLandDistance, + saltwaterBoundaryBasisPoints, + naturalBoundary: boundary.length > 0 + && maximumRadiusShareBasisPoints < 1_800 + && rotationalSimilarityBasisPoints < 9_300 + && maximumAlignedBoundaryRun <= 96, + deepOceanBoundary: maximumRadius <= PRIVATE_CANVAS_RADIUS - 8 + && minimumBoundaryLandDistance >= 8 + && saltwaterBoundaryBasisPoints === 10_000, + }); +} + +function topographicLandmassMetrics( + grid: IndexedAxialGrid, + elevation: Int32Array, +): Readonly<{ + majorLandmassCount: number; + largeIslandCount: number; + smallIslandCount: number; + proof: boolean; +}> { + const land = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (elevation[cell]! > SEA_LEVEL) land[cell] = 1; + } + const componentSizes = connectedComponents(grid, land).map(component => component.length); + const majorLandmassCount = componentSizes.filter(size => size >= 5_000).length; + const largeIslandCount = componentSizes.filter(size => size >= 96 && size < 5_000).length; + const smallIslandCount = componentSizes.filter(size => size > 0 && size < 96).length; + return Object.freeze({ + majorLandmassCount, + largeIslandCount, + smallIslandCount, + proof: majorLandmassCount >= 2 + && majorLandmassCount <= 4 + && largeIslandCount >= 3 + && largeIslandCount <= 8, + }); +} + +function chunkMetrics(grid: IndexedAxialGrid): Readonly<{ + count: number; + minimum: number; + median: number; + p95: number; + maximum: number; +}> { + const populations = new Map(); + for (let index = 0; index < grid.cellCount; index += 1) { + const chunkQ = Math.floor((grid.q[index]! + PRIVATE_CANVAS_RADIUS) / 15); + const chunkR = Math.floor((grid.r[index]! + PRIVATE_CANVAS_RADIUS) / 15); + const key = `${chunkQ}:${chunkR}`; + populations.set(key, (populations.get(key) ?? 0) + 1); + } + const sorted = [...populations.values()].sort((first, second) => first - second); + if (sorted.length === 0) fail('GREATER_REALM_CHUNKS_EMPTY'); + return Object.freeze({ + count: sorted.length, + minimum: sorted[0]!, + median: sorted[Math.floor((sorted.length - 1) * 0.5)]!, + p95: sorted[Math.floor((sorted.length - 1) * 0.95)]!, + maximum: sorted[sorted.length - 1]!, + }); +} + +function regionRanges(regionCounts: readonly number[]) { + const tierI = regionCounts.slice(0, 6); + const tierII = regionCounts.slice(6, 9); + const tierIII = regionCounts.slice(9, 10); + const range = (values: readonly number[]) => Object.freeze({ + minimum: Math.min(...values), + maximum: Math.max(...values), + }); + return Object.freeze({ tierI: range(tierI), tierII: range(tierII), tierIII: range(tierIII) }); +} + +function regionBalance(values: readonly number[]): boolean { + const sorted = [...values].sort((first, second) => first - second); + const median = sorted[Math.floor(sorted.length / 2)]!; + return values.every(value => value * 100 >= median * 75 && value * 100 <= median * 135); +} + +function allActiveConnected(grid: IndexedAxialGrid): boolean { + const all = new Uint8Array(grid.cellCount); + all.fill(1); + return connectedComponents(grid, all).length === 1; +} + +function activeMaskHasNoEnclosedVoids( + canvas: IndexedAxialGrid, + mask: Uint8Array, +): boolean { + const inactive = new Uint8Array(canvas.cellCount); + for (let cell = 0; cell < canvas.cellCount; cell += 1) { + if (mask[cell] === 0) inactive[cell] = 1; + } + return connectedComponents(canvas, inactive).every(component => component.some(cell => { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (canvas.neighbors[cell * HEX_NEIGHBOR_COUNT + direction] === -1) return true; + } + return false; + })); +} + +function hydrologyIsAcyclic( + grid: IndexedAxialGrid, + receiver: Int32Array, + filledElevation: Int32Array, +): boolean { + if (receiver.length !== grid.cellCount || filledElevation.length !== grid.cellCount) return false; + const incoming = new Uint32Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const downstream = receiver[cell]!; + if (downstream === -1) continue; + if ( + downstream < 0 + || downstream >= grid.cellCount + || downstream === cell + || filledElevation[downstream]! > filledElevation[cell]! + ) return false; + let isNeighbor = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction] === downstream) { + isNeighbor = true; + break; + } + } + if (!isNeighbor) return false; + incoming[downstream] += 1; + } + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (incoming[cell] === 0) queue[tail++] = cell; + } + let visited = 0; + while (head < tail) { + const cell = queue[head++]!; + visited += 1; + const downstream = receiver[cell]!; + if (downstream < 0) continue; + incoming[downstream] -= 1; + if (incoming[downstream] === 0) queue[tail++] = downstream; + } + return visited === grid.cellCount; +} + +function candidateStageDigests( + grid: IndexedAxialGrid, + fields: Readonly>, +): Readonly> { + const groups = [ + ['geology', ['bedrockElevation', 'domainId', 'geologyId']], + ['geomorphology', [ + 'geomorphologyElevation', + 'geomorphologyTemperature', + 'geomorphologyMoisture', + 'geomorphologyTotalDelta', + 'geomorphologyGlacialDelta', + 'geomorphologyAridDelta', + 'geomorphologyVolcanicDelta', + 'geomorphologyCoastalDelta', + 'geomorphologyGlacialMask', + 'geomorphologyAridMask', + 'geomorphologyVolcanicMask', + 'geomorphologyVolcanicAnchorMask', + 'geomorphologyCoastalMask', + 'geomorphologyCoastalClass', + ]], + ['hydrology', ['elevation', 'filledElevation', 'flowReceiver', 'waterRegime']], + ['topography', [ + 'slope', 'aspect', 'profileCurvature', 'planCurvature', 'wetnessIndex', + 'exposure', 'distanceToCoast', 'distanceToFreshwater', 'watershedId', + 'ridgeId', 'temperature', 'moisture', 'biomeId', 'landformId', + ]], + ['strategy', ['regionId', 'tierId', 'barrier', 'castleSlot', 'throneAnchor']], + ['final', Object.keys(fields)], + ] as const; + return Object.freeze(Object.fromEntries(groups.map(([stage, names]) => [ + stage, + digestGreaterRealmTerrainStage( + stage, + grid, + Object.fromEntries(names.map(name => [name, fields[name]!])) as Record, + ), + ]))); +} + +export function generateGreaterRealmCandidate(input: Readonly<{ + rootSeed: Uint8Array; + candidateOrdinal: number; +}>): GreaterRealmPrivateCandidate { + const seedMaterial = deriveCandidateSeedMaterial(input.rootSeed, input.candidateOrdinal); + const candidateSeed = deriveCandidateSeed(seedMaterial); + try { + const canvas = greaterRealmPrivateCanvasAuthority(); + const domains = separatedDomains(candidateSeed); + const geology = macroGeology(canvas, candidateSeed, domains); + const thermallyShaped = erodeGreaterRealmThermally(canvas, geology.bedrock, { + iterations: 3, + talus: geology.resistance.map(value => 700 + Math.floor(value / 8)), + transferNumerator: 1, + transferDenominator: 24, + }).elevation; + const maskResult = activeMask(canvas, candidateSeed, thermallyShaped); + const active = activeGridFromCanvas(canvas, maskResult.mask); + const grid = active.grid; + const projectedBedrock = projectInt32(geology.bedrock, active.sourceIndexes); + const initialElevation = projectInt32(thermallyShaped, active.sourceIndexes); + const resistance = projectInt32(geology.resistance, active.sourceIndexes); + const uplift = projectInt32(geology.uplift, active.sourceIndexes); + const domainId = projectUint8(geology.domainId, active.sourceIndexes); + const geologyId = projectUint8(geology.geologyId, active.sourceIndexes); + const legacy = placeLegacyLowlands(grid, candidateSeed, initialElevation); + const volcanicPotential = Int32Array.from( + domainId, + value => domains[value]!.volcanicPotential, + ); + const geomorphology = shapeGreaterRealmGeomorphology({ + grid, + candidateSeed, + elevation: initialElevation, + tectonicUplift: uplift, + rockResistance: resistance, + volcanicPotential, + legacyReserveCell: legacy.reserveCell, + seaLevel: SEA_LEVEL, + }); + const fluvial = fluvialPass( + grid, + geomorphology.elevation, + resistance, + legacy.protectedCell, + ); + const reconciled = reconcileLegacyLowlandsTopography( + grid, + legacy, + projectedBedrock, + fluvial.elevation, + ); + const provisionalStrategy = assignTiersAndRegions( + grid, + candidateSeed, + reconciled.elevation, + uplift, + domainId, + reconciled.flowAccumulation, + legacy.reserveCell, + ); + const surface = waterAndBiomes( + grid, + candidateSeed, + reconciled.elevation, + reconciled.filledElevation, + reconciled.flowReceiver, + reconciled.flowAccumulation, + provisionalStrategy.tierId, + legacy.protectedCell, + geomorphology.temperature, + geomorphology.moisture, + ); + const legacySurfaceProof = overlayLegacyLowlandsSurface( + grid, + legacy, + surface.waterRegime, + surface.biomeId, + surface.landformId, + ); + const semanticStrategy = remapTierOneNaturalBasinsByCharacter( + grid, + provisionalStrategy, + reconciled.elevation, + uplift, + reconciled.flowAccumulation, + domainId, + domains, + surface.waterRegime, + geomorphology.temperature, + geomorphology.moisture, + geomorphology.glacialMask, + geomorphology.aridMask, + geomorphology.volcanicMask, + geomorphology.coastalMask, + geomorphology.coastalClass, + ); + const strategy = repairNaturalRegionLandCoherence( + grid, + semanticStrategy, + surface.waterRegime, + legacy.protectedCell, + ); + const topography = deriveGreaterRealmTopography({ + grid, + elevation: reconciled.elevation, + flowReceiver: reconciled.flowReceiver, + flowAccumulation: reconciled.flowAccumulation, + waterRegime: surface.waterRegime, + geologyId, + tectonicUplift: uplift, + rockResistance: resistance, + regionId: strategy.regionId, + tierId: strategy.tierId, + legacyProtectedCell: legacy.protectedCell, + protectedBiomeId: surface.biomeId, + protectedLandformId: surface.landformId, + geomorphicTemperature: geomorphology.temperature, + geomorphicMoisture: geomorphology.moisture, + geomorphicGlacialMask: geomorphology.glacialMask, + geomorphicAridMask: geomorphology.aridMask, + geomorphicVolcanicMask: geomorphology.volcanicMask, + geomorphicCoastalClass: geomorphology.coastalClass, + }); + const finalHydrology = finalHydrologyMetrics( + grid, + reconciled.elevation, + reconciled.filledElevation, + reconciled.flowReceiver, + reconciled.flowAccumulation, + surface.waterRegime, + legacy.protectedCell, + ); + const strategicBarrier = barriersAndGates( + grid, + candidateSeed, + reconciled.elevation, + uplift, + surface.waterRegime, + strategy.tierId, + strategy.regionId, + strategy.gateGraph, + legacy.protectedCell, + ); + const sites = castleAndPotentialSites( + grid, + candidateSeed, + reconciled.elevation, + surface.waterRegime, + strategy.tierId, + strategy.regionId, + strategicBarrier.barrier, + strategicBarrier.gates, + legacy.protectedCell, + legacy.castleSlot, + ); + const throne = dormantThroneAnchor( + grid, + reconciled.elevation, + surface.waterRegime, + strategy.regionId, + strategicBarrier.barrier, + sites.castleSlot, + sites.resourcePotential, + sites.corePotential, + strategicBarrier.gates, + ); + const boundary = boundaryMetrics(grid, surface.waterRegime); + const landmasses = topographicLandmassMetrics(grid, reconciled.elevation); + const chunks = chunkMetrics(grid); + let landCellCount = 0; + let waterCellCount = 0; + let mountainBarrierCells = 0; + for (let index = 0; index < grid.cellCount; index += 1) { + // Public land/water shares describe the continent's topographic footprint; + // rivers and enclosed surface-water overlays remain features of that land. + if (reconciled.elevation[index]! > SEA_LEVEL) landCellCount += 1; + else waterCellCount += 1; + if (strategicBarrier.geologicalBarrierBand[index] !== 0) mountainBarrierCells += 1; + } + const geologicalBarrierMask = Uint8Array.from( + strategicBarrier.geologicalBarrierBand, + value => value === 0 ? 0 : 1, + ); + const barrierComponents = connectedComponents( + grid, + geologicalBarrierMask, + ); + const mountainSystems = barrierComponents.filter(component => component.length >= 64); + const continuousBarrierCells = mountainSystems.reduce( + (total, component) => total + component.length, + 0, + ); + const tierICounts = strategy.regionCounts.slice(0, 6); + const tierIICounts = strategy.regionCounts.slice(6, 9); + const approvedRange = grid.cellCount >= 100_000 && grid.cellCount <= 150_000; + const tierIBasisPoints = Math.round((strategy.tierCounts[0] * 10_000) / grid.cellCount); + const tierIIBasisPoints = Math.round((strategy.tierCounts[1] * 10_000) / grid.cellCount); + const tierIIIBasisPoints = 10_000 - tierIBasisPoints - tierIIBasisPoints; + const topology = strategicBarrier.passableTopology; + const strategicShape = strategicShapeMetrics( + grid, + strategy.tierId, + strategy.regionId, + surface.waterRegime, + strategicBarrier.barrier, + topology, + ); + const tierThreePassableLandCells = topology.passableCounts[TIER_III_REGION_INDEX]!; + const smallestOtherRegionPassableLandCells = Math.min( + ...topology.passableCounts.slice(0, TIER_III_REGION_INDEX), + ); + const hydrologyAcyclic = hydrologyIsAcyclic( + grid, + reconciled.flowReceiver, + reconciled.filledElevation, + ); + const proofs = Object.freeze({ + activeMaskConnected: allActiveConnected(grid) + && activeMaskHasNoEnclosedVoids(canvas, maskResult.mask), + approvedCellRange: approvedRange, + barriersHaveNoBypass: strategicBarrier.barrierProof, + biomeCoherence: + topography.biomeMetrics.incompatibleVisualBiomeAdjacencyCount === 0 + && topography.biomeMetrics.incompatibleBiomeLandformPairCount === 0, + biomeDiversity: topography.biomeMetrics.visualBiomeClassCount >= 8 + && topography.biomeMetrics.minimumRegionVisualBiomeClassCount >= 3 + && topography.biomeMetrics.minimumTierIVisualBiomeClassCount >= 6 + && topography.biomeMetrics.minimumTierIIVisualBiomeClassCount >= 5 + && topography.biomeMetrics.tierIIIVisualBiomeClassCount >= 3 + && topography.biomeMetrics.minimumTierIMajorVisualBiomeClassCount >= 4 + && topography.biomeMetrics.minimumTierITransitionVisualBiomeClassCount >= 2 + && topography.biomeMetrics.minimumTierIIMajorVisualBiomeClassCount >= 5 + && topography.biomeMetrics.tierIIIMajorVisualBiomeClassCount >= 3 + && topography.biomeMetrics.maximumTierISingleBiomeShareBasisPoints <= 5_500, + castleCapacity: sites.placementProof, + deepOceanBoundary: boundary.deepOceanBoundary, + dormantThroneAnchor: throne.proof, + gateApproaches: strategicBarrier.gateApproachProof, + gateGraph: strategicBarrier.gateGraphProof, + geologicalHighlandBarriers: strategicBarrier.geologicalHighlandProof, + advancedGeomorphology: geomorphology.metrics.changedCellCount > 0 + && geomorphology.metrics.maximumAbsoluteCellDelta <= 8_192 + && geomorphology.metrics.protectedChangedCellCount === 0 + && geomorphology.metrics.erodedMaterialUnits + === geomorphology.metrics.depositedMaterialUnits + + geomorphology.metrics.exportedMaterialUnits + && geomorphology.metrics.glacialClimateCompatibilityBasisPoints === 10_000 + && geomorphology.metrics.aridClimateCompatibilityBasisPoints === 10_000 + && geomorphology.metrics.volcanicTectonicCompatibilityBasisPoints === 10_000 + && geomorphology.metrics.coastalProximityCompatibilityBasisPoints === 10_000 + && geomorphology.metrics.glacial.systemCount > 0 + && geomorphology.metrics.glacial.minimumSystemCellCount >= 6 + && geomorphology.metrics.arid.systemCount > 0 + && geomorphology.metrics.arid.minimumSystemCellCount >= 8 + && geomorphology.metrics.volcanicAnchorCount >= 2 + && geomorphology.metrics.volcanicAnchorCount <= 8 + && geomorphology.metrics.coastalClassCount >= 3 + && geomorphology.metrics.ridgeUpliftAlignmentBasisPoints >= 8_500 + && geomorphology.metrics.riverValleyAlignmentBasisPoints >= 8_500, + hydrologyAcyclic, + hydrologySurfaceConsistency: finalHydrology.surfaceConsistencyProof, + legacyLowlandsPreserved: legacy.proof && legacySurfaceProof, + naturalLandmassTopology: landmasses.proof, + naturalStrategicRegions: strategicShape.nonRadialProof, + naturalOuterBoundary: boundary.naturalBoundary, + regionLandCoherence: strategicBarrier.passableRegionProof + && strategicShape.fragmentationProof + && strategicShape.compactnessProof + && strategicShape.tendrilProof, + regionPassableLand: strategicBarrier.passableRegionProof, + regionGraph: strategicBarrier.regionGraphProof, + }); + const hardGates = Object.freeze({ + ...Object.fromEntries(Object.entries(proofs).map(([key, value]) => [ + `PROOF_${key.replace(/([a-z])([A-Z])/gu, '$1_$2').toUpperCase()}`, + value, + ])), + LAND_SHARE_6200_7200: landCellCount * 10_000 >= grid.cellCount * 6_200 + && landCellCount * 10_000 <= grid.cellCount * 7_200, + TIER_I_SHARE_6800_7400: tierIBasisPoints >= 6_800 && tierIBasisPoints <= 7_400, + TIER_II_SHARE_2200_2700: tierIIBasisPoints >= 2_200 && tierIIBasisPoints <= 2_700, + TIER_III_SHARE_0300_0600: tierIIIBasisPoints >= 300 && tierIIIBasisPoints <= 600, + TIER_I_REGION_BALANCE: regionBalance(tierICounts), + TIER_II_REGION_BALANCE: regionBalance(tierIICounts), + TIER_III_SMALLEST_TOTAL_CELLS: strategy.regionCounts[TIER_III_REGION_INDEX]! + < Math.min(...strategy.regionCounts.slice(0, TIER_III_REGION_INDEX)), + TIER_III_SMALLEST_PASSABLE_LAND: tierThreePassableLandCells + < smallestOtherRegionPassableLandCells, + HYDROLOGY_MAJOR_OCEAN_SEA_BODIES_4_6: finalHydrology.majorOceanSeaBodies >= 4 + && finalHydrology.majorOceanSeaBodies <= 6, + HYDROLOGY_MAJOR_RIVERS_48_72: finalHydrology.majorRivers >= 48 + && finalHydrology.majorRivers <= 72, + HYDROLOGY_MINOR_STREAMS_120_240: finalHydrology.minorStreams >= 120 + && finalHydrology.minorStreams <= 240, + HYDROLOGY_LAKES_48_96: finalHydrology.lakes >= 48 + && finalHydrology.lakes <= 96, + SEDIMENT_MATERIAL_BUDGET: fluvial.erodedMaterialUnits + === fluvial.depositedMaterialUnits + fluvial.exportedSedimentUnits, + }); + const eligibilityFailureCodes = Object.freeze(Object.entries(hardGates) + .filter(([, passed]) => !passed) + .map(([code]) => code)); + const eligible = eligibilityFailureCodes.length === 0; + const axialArtifactBasisPoints = clamp( + boundary.maximumRadiusShareBasisPoints + + Math.floor(boundary.rotationalSimilarityBasisPoints / 3) + + boundary.maximumAlignedBoundaryRun * 8, + 0, + 10_000, + ); + const scoreRange = (value: number, minimum: number, maximum: number) => { + if (value >= minimum && value <= maximum) return 10_000; + const miss = value < minimum ? minimum - value : value - maximum; + return clamp(10_000 - Math.round((miss * 10_000) / Math.max(1, maximum - minimum)), 0, 10_000); + }; + const hydrologyCoherenceBasisPoints = Math.round(( + scoreRange(finalHydrology.majorOceanSeaBodies, 4, 6) + + scoreRange(finalHydrology.majorRivers, 48, 72) + + scoreRange(finalHydrology.minorStreams, 120, 240) + + scoreRange(finalHydrology.lakes, 48, 96) + + (finalHydrology.surfaceConsistencyProof ? 10_000 : 0) + + (hydrologyAcyclic ? 10_000 : 0) + ) / 6); + const ridgeContinuityBasisPoints = mountainBarrierCells === 0 + ? 0 + : Math.round((continuousBarrierCells * 10_000) / mountainBarrierCells); + const aggregate = Object.freeze({ + eligible, + activeCellCount: grid.cellCount, + landCellCount, + waterCellCount, + tierCellCounts: Object.freeze({ + tierI: strategy.tierCounts[0], + tierII: strategy.tierCounts[1], + tierIII: strategy.tierCounts[2], + }), + regionSizeRanges: regionRanges(strategy.regionCounts), + hydrology: Object.freeze({ + majorOceanSeaBodies: finalHydrology.majorOceanSeaBodies, + majorRivers: finalHydrology.majorRivers, + minorStreams: finalHydrology.minorStreams, + lakes: finalHydrology.lakes, + }), + geology: Object.freeze({ + pseudoTectonicDomains: domains.length, + mountainSystems: mountainSystems.length, + watersheds: finalHydrology.watersheds, + }), + topography: Object.freeze({ + signedElevationMinimum: topography.topographyMetrics.elevationMinimum, + signedElevationMaximum: topography.topographyMetrics.elevationMaximum, + slopeP50: topography.topographyMetrics.slopeP50, + slopeP95: topography.topographyMetrics.slopeP95, + ridgeCellCount: topography.topographyMetrics.ridgeCells, + plateauCellCount: topography.topographyMetrics.plateauCells, + basinCellCount: topography.topographyMetrics.basinCells, + coastCellCount: topography.topographyMetrics.coastCells, + }), + biomes: Object.freeze({ + visualClassCount: topography.biomeMetrics.visualBiomeClassCount, + minimumPerRegionVisualClassCount: + topography.biomeMetrics.minimumRegionVisualBiomeClassCount, + minimumTierIVisualClassCount: + topography.biomeMetrics.minimumTierIVisualBiomeClassCount, + minimumTierIIVisualClassCount: + topography.biomeMetrics.minimumTierIIVisualBiomeClassCount, + tierIIIVisualClassCount: topography.biomeMetrics.tierIIIVisualBiomeClassCount, + minimumTierIMajorVisualClassCount: + topography.biomeMetrics.minimumTierIMajorVisualBiomeClassCount, + minimumTierITransitionVisualClassCount: + topography.biomeMetrics.minimumTierITransitionVisualBiomeClassCount, + minimumTierIIMajorVisualClassCount: + topography.biomeMetrics.minimumTierIIMajorVisualBiomeClassCount, + tierIIIMajorVisualClassCount: + topography.biomeMetrics.tierIIIMajorVisualBiomeClassCount, + maximumTierISingleBiomeShareBasisPoints: + topography.biomeMetrics.maximumTierISingleBiomeShareBasisPoints, + incompatibleVisualAdjacencyCount: + topography.biomeMetrics.incompatibleVisualBiomeAdjacencyCount, + incompatibleBiomeLandformPairCount: + topography.biomeMetrics.incompatibleBiomeLandformPairCount, + }), + quality: Object.freeze({ + naturalnessBasisPoints: clamp(10_000 - axialArtifactBasisPoints, 0, 10_000), + axialArtifactBasisPoints, + ridgeContinuityBasisPoints, + hydrologyCoherenceBasisPoints, + }), + gateCount: strategicBarrier.gates.length, + castleSlotCount: sites.castleCount, + proofs, + }); + const fields: Readonly> = Object.freeze({ + bedrockElevation: reconciled.bedrockElevation, + elevation: reconciled.elevation, + filledElevation: reconciled.filledElevation, + sedimentDepth: fluvial.sedimentDepth, + flowReceiver: reconciled.flowReceiver, + flowAccumulation: reconciled.flowAccumulation, + domainId, + geologyId, + tectonicUplift: uplift, + rockResistance: resistance, + geomorphologyElevation: geomorphology.elevation, + geomorphologyTemperature: geomorphology.temperature, + geomorphologyMoisture: geomorphology.moisture, + geomorphologyTotalDelta: geomorphology.totalDelta, + geomorphologyGlacialDelta: geomorphology.glacialDelta, + geomorphologyAridDelta: geomorphology.aridDelta, + geomorphologyVolcanicDelta: geomorphology.volcanicDelta, + geomorphologyCoastalDelta: geomorphology.coastalDelta, + geomorphologyGlacialMask: geomorphology.glacialMask, + geomorphologyAridMask: geomorphology.aridMask, + geomorphologyVolcanicMask: geomorphology.volcanicMask, + geomorphologyVolcanicAnchorMask: geomorphology.volcanicAnchorMask, + geomorphologyCoastalMask: geomorphology.coastalMask, + geomorphologyCoastalClass: geomorphology.coastalClass, + regionId: strategy.regionId, + tierId: strategy.tierId, + waterRegime: surface.waterRegime, + biomeId: topography.biomeId, + landformId: topography.landformId, + slope: topography.slope, + aspect: topography.aspect, + profileCurvature: topography.profileCurvature, + planCurvature: topography.planCurvature, + wetnessIndex: topography.wetnessIndex, + exposure: topography.exposure, + distanceToCoast: topography.distanceToCoast, + distanceToFreshwater: topography.distanceToFreshwater, + watershedId: topography.watershedId, + ridgeId: topography.ridgeId, + temperature: topography.temperature, + moisture: topography.moisture, + barrier: strategicBarrier.barrier, + geologicalBarrierBand: strategicBarrier.geologicalBarrierBand, + castleSlot: sites.castleSlot, + resourcePotential: sites.resourcePotential, + corePotential: sites.corePotential, + throneAnchor: throne.mask, + legacyLowlandsCell: legacy.worldCell, + legacyLowlandsProtectedCell: legacy.protectedCell, + legacyLowlandsReserveCell: legacy.reserveCell, + legacyLowlandsCastleSlot: legacy.castleSlot, + }); + const stageDigests = candidateStageDigests(grid, fields); + return Object.freeze({ + candidateOrdinal: input.candidateOrdinal, + seedMaterial, + candidateSeed, + domains, + grid, + legacyLowlandsTransform: legacy.transform, + legacyLowlandsCell: legacy.worldCell, + legacyLowlandsProtectedCell: legacy.protectedCell, + legacyLowlandsReserveCell: legacy.reserveCell, + legacyLowlandsCastleSlot: legacy.castleSlot, + bedrockElevation: reconciled.bedrockElevation, + elevation: reconciled.elevation, + filledElevation: reconciled.filledElevation, + sedimentDepth: fluvial.sedimentDepth, + flowReceiver: reconciled.flowReceiver, + flowAccumulation: reconciled.flowAccumulation, + domainId, + geologyId, + tectonicUplift: uplift, + rockResistance: resistance, + geomorphologyElevation: geomorphology.elevation, + geomorphologyTemperature: geomorphology.temperature, + geomorphologyMoisture: geomorphology.moisture, + geomorphologyTotalDelta: geomorphology.totalDelta, + geomorphologyGlacialDelta: geomorphology.glacialDelta, + geomorphologyAridDelta: geomorphology.aridDelta, + geomorphologyVolcanicDelta: geomorphology.volcanicDelta, + geomorphologyCoastalDelta: geomorphology.coastalDelta, + geomorphologyGlacialMask: geomorphology.glacialMask, + geomorphologyAridMask: geomorphology.aridMask, + geomorphologyVolcanicMask: geomorphology.volcanicMask, + geomorphologyVolcanicAnchorMask: geomorphology.volcanicAnchorMask, + geomorphologyCoastalMask: geomorphology.coastalMask, + geomorphologyCoastalClass: geomorphology.coastalClass, + regionId: strategy.regionId, + tierId: strategy.tierId, + waterRegime: surface.waterRegime, + biomeId: topography.biomeId, + landformId: topography.landformId, + slope: topography.slope, + aspect: topography.aspect, + profileCurvature: topography.profileCurvature, + planCurvature: topography.planCurvature, + wetnessIndex: topography.wetnessIndex, + exposure: topography.exposure, + distanceToCoast: topography.distanceToCoast, + distanceToFreshwater: topography.distanceToFreshwater, + watershedId: topography.watershedId, + ridgeId: topography.ridgeId, + temperature: topography.temperature, + moisture: topography.moisture, + barrier: strategicBarrier.barrier, + geologicalBarrierBand: strategicBarrier.geologicalBarrierBand, + castleSlot: sites.castleSlot, + resourcePotential: sites.resourcePotential, + corePotential: sites.corePotential, + throneAnchor: throne.mask, + tierOneSemanticPermutation: strategy.semanticPermutation, + gateGraph: strategy.gateGraph, + gates: strategicBarrier.gates, + barrierCrossSections: strategicBarrier.barrierCrossSections, + stageDigests, + aggregate, + privateMetrics: Object.freeze({ + activeBoundaryCells: boundary.boundaryCells, + maximumBoundaryRadiusShareBasisPoints: boundary.maximumRadiusShareBasisPoints, + rotationalSimilarityBasisPoints: boundary.rotationalSimilarityBasisPoints, + maximumAlignedBoundaryRun: boundary.maximumAlignedBoundaryRun, + minimumBoundaryLandDistance: boundary.minimumBoundaryLandDistance, + saltwaterBoundaryBasisPoints: boundary.saltwaterBoundaryBasisPoints, + majorLandmassCount: landmasses.majorLandmassCount, + largeIslandCount: landmasses.largeIslandCount, + smallIslandCount: landmasses.smallIslandCount, + mountainBarrierCells, + measuredMinimumBarrierWidth: strategicBarrier.measuredMinimumBarrierWidth, + measuredMaximumBarrierWidth: strategicBarrier.measuredMaximumBarrierWidth, + gateRouteRedundancyProof: strategicBarrier.gateRouteRedundancyProof, + lakeBasinCandidates: surface.lakeBasinCandidates, + riverMouthCandidates: surface.riverMouthCandidates, + streamHeadCandidates: surface.streamHeadCandidates, + resourcePotentialSites: sites.resourcePotentialCount, + corePotentialSites: sites.corePotentialCount, + chunkCount: chunks.count, + chunkPopulationMinimum: chunks.minimum, + chunkPopulationMedian: chunks.median, + chunkPopulationP95: chunks.p95, + chunkPopulationMaximum: chunks.maximum, + erodedMaterialUnits: fluvial.erodedMaterialUnits, + depositedMaterialUnits: fluvial.depositedMaterialUnits, + exportedSedimentUnits: fluvial.exportedSedimentUnits, + minimumLargestPassableRegionShareBasisPoints: Math.min( + ...topology.largestSharesBasisPoints, + ), + largestPassableRegionSharesBasisPoints: topology.largestSharesBasisPoints, + minorPassableFragmentSharesBasisPoints: + strategicShape.minorFragmentSharesBasisPoints, + passableBoundaryDensityBasisPoints: strategicShape.boundaryDensityBasisPoints, + passableTendrilSharesBasisPoints: strategicShape.tendrilSharesBasisPoints, + tierRadialAgreementBasisPoints: strategicShape.tierRadialAgreementBasisPoints, + radialTierOneBoundaryShareBasisPoints: + strategicShape.radialTierOneBoundaryShareBasisPoints, + highlandBarrierShareBasisPoints: + strategicBarrier.highlandBarrierShareBasisPoints, + barrierMeanElevationAdvantage: strategicBarrier.barrierMeanElevationAdvantage, + barrierMeanUpliftAdvantage: strategicBarrier.barrierMeanUpliftAdvantage, + geomorphology: geomorphology.metrics, + throneAnchorBarrierClearance: throne.barrierClearance, + tierThreePassableLandCells, + smallestOtherRegionPassableLandCells, + eligibilityFailureCodes, + }), + }); + } catch (error) { + seedMaterial.fill(0); + candidateSeed.fill(0); + throw error; + } +} + +export function clearGreaterRealmCandidateSecret(candidate: GreaterRealmPrivateCandidate): void { + candidate.seedMaterial.fill(0); + candidate.candidateSeed.fill(0); +} diff --git a/scripts/atlas/greater-realm-candidate-package.ts b/scripts/atlas/greater-realm-candidate-package.ts new file mode 100644 index 00000000..42cd04de --- /dev/null +++ b/scripts/atlas/greater-realm-candidate-package.ts @@ -0,0 +1,1995 @@ +import { createHash, createHmac, timingSafeEqual } from 'node:crypto'; +import { lstatSync, realpathSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { basename, dirname, relative, resolve, sep } from 'node:path'; + +import { + GREATER_REALM_GENERATOR_VERSION, + GREATER_REALM_PRIVATE_MANIFEST_KIND, + GREATER_REALM_PRIVATE_PACKAGE_MAGIC, + GREATER_REALM_REGION_SPECS, + clearGreaterRealmCandidateSecret, + generateGreaterRealmCandidate, + type GreaterRealmPrivateCandidate, +} from './greater-realm-candidate-generator'; +import type { GreaterRealmPrivateWorkspace } from './greater-realm-private-workspace'; +import type { IntegerTerrainArray } from './greater-realm-terrain'; +import { GREATER_REALM_GEOMORPHOLOGY_VERSION } from './greater-realm-geomorphology'; +import { + GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1, + GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_SCHEMA, +} from './greater-realm-legacy-lowlands'; +import { + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + decodeGreaterRealmPrivateSeed, + encodeGreaterRealmPrivateSeed, +} from './greater-realm-private-seed'; +import { + inspectGreaterRealmTrustedGit, + sha256GreaterRealmAttestedFile, +} from './greater-realm-git'; + +const PRIVATE_ATLAS_FORMAT_VERSION = 4; +const PRIVATE_ATLAS_MAXIMUM_BYTES = 128 * 1024 * 1024; +const PRIVATE_PREVIEW_MAXIMUM_BYTES = 16 * 1024 * 1024; +const PRIVATE_MANIFEST_MAXIMUM_BYTES = 4 * 1024 * 1024; +const PRIVATE_CANDIDATE_MAXIMUM_BYTES = PRIVATE_ATLAS_MAXIMUM_BYTES + + PRIVATE_MANIFEST_MAXIMUM_BYTES + + PRIVATE_PREVIEW_MAXIMUM_BYTES * 6 + + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES; +const SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const SOURCE_COMMIT_PATTERN = /^[0-9a-f]{40}$/u; +const BATCH_HANDLE_PATTERN = /^GR-B-[A-Z2-7]{16}$/u; +const CANDIDATE_HANDLE_PATTERN = /^GR-A-[A-Z2-7]{16}$/u; +const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); +export const GREATER_REALM_PRIVATE_PREVIEW_MARKER = + 'WKGR-PRIVATE-PREVIEW-V1' as const; +const PRIVATE_PREVIEW_TEXT_KEY = 'WarpkeepPrivate'; +const PRIVATE_PREVIEW_MODES = [ + 'silhouette', 'hillshade', 'biome', 'hydrology', 'regions', 'mountain-gates', +] as const; +const PRIVATE_CANVAS_RADIUS = 270; +const PRIVATE_CHUNK_AXIS_SPAN = 15; +const PRIVATE_CHUNK_PARTITION_VERSION = 'axial-bin-15-v1' as const; +const PRIVATE_CHUNK_SCHEMA = 'warpkeep.greater-realm.private-chunk-manifest.v1' as const; +const PRIVATE_TOPOGRAPHY_PATCH_SCHEMA = + 'warpkeep.greater-realm.private-topography-patch.v1' as const; +const PRIVATE_TOPOGRAPHY_VERSION = 'greater-realm-advanced-topography-v1' as const; +const PRIVATE_TOPOGRAPHY_ENCODING_VERSION = 'wkgr-topography-fields-v1' as const; + +const PRIVATE_PINNED_TOOLCHAIN = Object.freeze({ + configuredNodeEngine: '>=22.13 <23', + configuredPackageManager: 'npm@10.9.8', + libvips: '8.18.3', + sharp: '0.35.3', + tsx: '4.23.0', + typescript: '7.0.2', +}); +const PRIVATE_TOOLCHAIN_VERSION_KEYS = Object.freeze([ + 'architecture', 'configuredNodeEngine', 'configuredPackageManager', 'git', + 'installLayout', 'libvips', 'nodeExecutable', 'platform', 'runtimeNode', + 'preflight', 'sharp', 'tsx', 'typescript', +] as const); +const require = createRequire(import.meta.url); +const REPOSITORY_ROOT = resolve(import.meta.dirname, '..', '..'); +const NODE_MODULES_ROOT = resolve(REPOSITORY_ROOT, 'node_modules'); +const TOOLCHAIN_LOCK_PATH = resolve( + REPOSITORY_ROOT, + 'scripts', + 'atlas', + 'greater-realm-toolchain-lock.json', +); + +type GreaterRealmPrivatePreviewMode = typeof PRIVATE_PREVIEW_MODES[number]; + +const PRIVATE_MANIFEST_KEYS = Object.freeze([ + 'aggregate', + 'atlasDigest', + 'barrierCrossSections', + 'batchHandle', + 'candidateHandle', + 'candidateOrdinal', + 'canvas', + 'chunkManifests', + 'domains', + 'exactActiveCellCount', + 'exactRegionCellCounts', + 'formatVersion', + 'gateGraph', + 'gates', + 'generatorVersion', + 'geomorphologyVersion', + 'kind', + 'legacyLowlands', + 'performance', + 'previewDigests', + 'privateMetrics', + 'provenanceDigest', + 'regionSpecs', + 'seedDigest', + 'sourceCommit', + 'stageDigests', + 'tierOneSemanticPermutation', + 'throneAnchor', + 'toolchainVersions', + 'topographyPatchManifests', +] as const); + +type EncodedFieldType = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; + +type EncodedField = Readonly<{ + name: string; + type: EncodedFieldType; + width: number; + array: IntegerTerrainArray; +}>; + +type PrivateChunk = Readonly<{ + chunkKey: string; + chunkQ: number; + chunkR: number; + cellIndices: readonly number[]; +}>; + +function fail(code: string): never { + throw new Error(code); +} + +function privateToolchainPath(path: string): string { + const canonical = realpathSync(path); + const withinRepository = relative(REPOSITORY_ROOT, canonical); + if ( + withinRepository === '' + || withinRepository === '..' + || withinRepository.startsWith(`..${sep}`) + || resolve(REPOSITORY_ROOT, withinRepository) !== canonical + || ( + withinRepository !== 'node_modules' + && !withinRepository.startsWith(`node_modules${sep}`) + ) + ) fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + return withinRepository.split(sep).join('/'); +} + +function privateToolchainFile(path: string) { + try { + const attestation = sha256GreaterRealmAttestedFile(path, NODE_MODULES_ROOT); + return Object.freeze({ + path: privateToolchainPath(attestation.canonicalPath), + sha256: attestation.sha256, + }); + } catch { + return fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + } +} + +function privateExternalToolchainFile(path: string) { + try { + const attestation = sha256GreaterRealmAttestedFile(path); + return Object.freeze({ + path: attestation.canonicalPath, + sha256: attestation.sha256, + }); + } catch { + return fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + } +} + +function privatePackageAlias(name: 'sharp' | 'tsx' | 'typescript', packageRoot: string) { + const alias = resolve(NODE_MODULES_ROOT, name); + let status: ReturnType; + try { + status = lstatSync(alias); + } catch { + return fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + } + if (!status.isDirectory() && !status.isSymbolicLink()) { + fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + } + const canonicalAlias = realpathSync(alias); + if (canonicalAlias !== realpathSync(packageRoot)) { + fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + } + return Object.freeze({ + kind: status.isSymbolicLink() ? 'symlink' as const : 'directory' as const, + target: privateToolchainPath(canonicalAlias), + }); +} + +function privateNativeArtifacts(paths: readonly string[], kind: 'sharp' | 'libvips') { + const unique = [...new Set(paths.map(path => realpathSync(path)))].sort(); + if (unique.length < 1 || unique.length > 16) { + fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + } + const artifacts = unique.map(privateToolchainFile); + if (artifacts.some(artifact => ( + kind === 'sharp' + ? !artifact.path.endsWith('.node') || !basename(artifact.path).includes('sharp') + : !basename(artifact.path).toLowerCase().includes('vips') + ))) fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + return Object.freeze(artifacts); +} + +function privateToolchainPreflightReceipt() { + let manifest: ReturnType; + try { + manifest = sha256GreaterRealmAttestedFile(TOOLCHAIN_LOCK_PATH, REPOSITORY_ROOT); + } catch { + return fail('GREATER_REALM_PRIVATE_TOOLCHAIN_PREFLIGHT_REQUIRED'); + } + const receipt = process.env.WKGR_TOOLCHAIN_PREFLIGHT_RECEIPT; + const profile = process.env.WKGR_TOOLCHAIN_PREFLIGHT_PROFILE; + const expectedProfile = `${process.platform}-${process.arch}`; + const expectedReceipt = `sha256:${manifest.sha256}`; + if ( + receipt !== expectedReceipt + || profile !== expectedProfile + || !/^sha256:[0-9a-f]{64}$/u.test(receipt) + || !/^(?:darwin-arm64|linux-x64)$/u.test(profile) + ) fail('GREATER_REALM_PRIVATE_TOOLCHAIN_PREFLIGHT_REQUIRED'); + return Object.freeze({ + kind: 'locked-package-tree-v1' as const, + manifestPath: relative(REPOSITORY_ROOT, manifest.canonicalPath).split(sep).join('/'), + manifestSha256: manifest.sha256, + profile, + }); +} + +async function privateToolchainVersions() { + const preflight = privateToolchainPreflightReceipt(); + const sharpModule = await import('sharp'); + const runtimeNode = process.versions.node; + const runtimeSharp = sharpModule.default.versions.sharp; + const runtimeLibvips = sharpModule.default.versions.vips; + const nodeParts = runtimeNode.split('.').map(value => Number.parseInt(value, 10)); + const installedTsx = require('tsx/package.json') as { version?: unknown }; + const installedTypeScript = require('typescript/package.json') as { version?: unknown }; + if ( + typeof runtimeNode !== 'string' + || !/^\d+\.\d+\.\d+$/u.test(runtimeNode) + || nodeParts[0] !== 22 + || nodeParts[1] === undefined + || nodeParts[1] < 13 + || runtimeSharp !== PRIVATE_PINNED_TOOLCHAIN.sharp + || runtimeLibvips !== PRIVATE_PINNED_TOOLCHAIN.libvips + || installedTsx.version !== PRIVATE_PINNED_TOOLCHAIN.tsx + || installedTypeScript.version !== PRIVATE_PINNED_TOOLCHAIN.typescript + ) fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + const sharpEntrypoint = require.resolve('sharp'); + const sharpRoot = resolve(dirname(sharpEntrypoint), '..'); + const tsxPackageJson = require.resolve('tsx/package.json'); + const tsxRoot = dirname(tsxPackageJson); + const tsxRequire = createRequire(tsxPackageJson); + const esbuildPackageJson = tsxRequire.resolve('esbuild/package.json'); + const esbuildPackage = tsxRequire('esbuild/package.json') as { version?: unknown }; + const esbuildRoot = dirname(esbuildPackageJson); + const esbuildRequire = createRequire(esbuildPackageJson); + let esbuildNativePackageJson: string; + try { + esbuildNativePackageJson = esbuildRequire.resolve( + `@esbuild/${process.platform}-${process.arch}/package.json`, + ); + } catch { + return fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + } + const esbuildNativeRoot = dirname(esbuildNativePackageJson); + const esbuildNativeCli = resolve( + esbuildNativeRoot, + 'bin', + process.platform === 'win32' ? 'esbuild.exe' : 'esbuild', + ); + const typescriptPackageJson = require.resolve('typescript/package.json'); + const typescriptRoot = dirname(typescriptPackageJson); + const typescriptRequire = createRequire(typescriptPackageJson); + let typescriptNativePackageJson: string; + try { + typescriptNativePackageJson = typescriptRequire.resolve( + `@typescript/typescript-${process.platform}-${process.arch}/package.json`, + ); + } catch { + return fail('GREATER_REALM_PRIVATE_TOOLCHAIN_INVALID'); + } + const typescriptNativeRoot = dirname(typescriptNativePackageJson); + const typescriptNativeCli = resolve( + typescriptNativeRoot, + 'lib', + process.platform === 'win32' ? 'tsc.exe' : 'tsc', + ); + const report = process.report?.getReport(); + const reportedSharedObjects = report !== undefined && typeof report === 'object' + ? (report as { sharedObjects?: unknown }).sharedObjects + : undefined; + const sharedObjects = report !== undefined + && Array.isArray(reportedSharedObjects) + ? reportedSharedObjects as string[] + : []; + const sharpNativePaths = sharedObjects.filter(path => ( + typeof path === 'string' + && path.endsWith('.node') + && basename(path).toLowerCase().includes('sharp') + )); + const libvipsNativePaths = sharedObjects.filter(path => ( + typeof path === 'string' + && basename(path).toLowerCase().includes('vips') + )); + const aliases = Object.freeze({ + sharp: privatePackageAlias('sharp', sharpRoot), + tsx: privatePackageAlias('tsx', tsxRoot), + typescript: privatePackageAlias('typescript', typescriptRoot), + }); + const aliasKinds = Object.values(aliases).map(alias => alias.kind); + const installLayout = aliasKinds.every(kind => kind === 'directory') + ? 'direct-node-modules' + : aliasKinds.every(kind => kind === 'symlink') + ? 'bound-symlink-store' + : 'bound-mixed-node-modules'; + const git = inspectGreaterRealmTrustedGit(); + return Object.freeze({ + ...PRIVATE_PINNED_TOOLCHAIN, + architecture: process.arch, + git: Object.freeze({ + binaryPath: git.binaryPath, + binarySha256: git.binarySha256, + execPath: git.execPath, + version: git.version, + }), + installLayout: Object.freeze({ kind: installLayout, aliases }), + nodeExecutable: privateExternalToolchainFile(process.execPath), + platform: process.platform, + preflight, + runtimeNode, + sharp: Object.freeze({ + version: runtimeSharp, + packageJson: privateToolchainFile(resolve(sharpRoot, 'package.json')), + entrypoint: privateToolchainFile(sharpEntrypoint), + native: privateNativeArtifacts(sharpNativePaths, 'sharp'), + libvipsNative: privateNativeArtifacts(libvipsNativePaths, 'libvips'), + }), + tsx: Object.freeze({ + version: installedTsx.version, + packageJson: privateToolchainFile(tsxPackageJson), + cli: privateToolchainFile(require.resolve('tsx/cli')), + esbuildVersion: esbuildPackage.version, + esbuildPackageJson: privateToolchainFile(esbuildPackageJson), + esbuildEntrypoint: privateToolchainFile(resolve(esbuildRoot, 'lib', 'main.js')), + esbuildNativePackageJson: privateToolchainFile(esbuildNativePackageJson), + esbuildNativeCli: privateToolchainFile(esbuildNativeCli), + }), + typescript: Object.freeze({ + version: installedTypeScript.version, + packageJson: privateToolchainFile(typescriptPackageJson), + cli: privateToolchainFile(resolve(typescriptRoot, 'lib', 'tsc.js')), + nativePackageJson: privateToolchainFile(typescriptNativePackageJson), + nativeCli: privateToolchainFile(typescriptNativeCli), + }), + }); +} + +function exactRecord( + value: unknown, + expectedKeys: readonly string[], + code = 'GREATER_REALM_PRIVATE_MANIFEST_INVALID', +): Readonly> { + if ( + value === null + || typeof value !== 'object' + || Array.isArray(value) + || ![Object.prototype, null].includes(Object.getPrototypeOf(value)) + ) fail(code); + const ownKeys = Reflect.ownKeys(value); + if (ownKeys.some(key => typeof key !== 'string')) fail(code); + const actual = (ownKeys as string[]).sort(); + const expected = [...expectedKeys].sort(); + if ( + actual.length !== expected.length + || actual.some((key, index) => key !== expected[index]) + ) fail(code); + const descriptors = Object.getOwnPropertyDescriptors(value); + if (Object.values(descriptors).some(descriptor => !('value' in descriptor))) fail(code); + return value as Readonly>; +} + +function exactJsonEqual(left: unknown, right: unknown): boolean { + if (Object.is(left, right)) return true; + if (Array.isArray(left) || Array.isArray(right)) { + if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) { + return false; + } + return left.every((value, index) => exactJsonEqual(value, right[index])); + } + if ( + left === null + || right === null + || typeof left !== 'object' + || typeof right !== 'object' + ) return false; + const leftKeys = Object.keys(left).sort(); + const rightKeys = Object.keys(right).sort(); + if ( + leftKeys.length !== rightKeys.length + || leftKeys.some((key, index) => key !== rightKeys[index]) + ) return false; + return leftKeys.every(key => exactJsonEqual( + (left as Record)[key], + (right as Record)[key], + )); +} + +function canonicalPrivateManifestValue(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map(canonicalPrivateManifestValue); + } + if (value !== null && typeof value === 'object') { + return Object.fromEntries(Object.entries(value) + .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)) + .map(([key, entry]) => [key, canonicalPrivateManifestValue(entry)])); + } + return value; +} + +function serializeCanonicalPrivateManifest(value: unknown): Buffer { + return Buffer.from( + `${JSON.stringify(canonicalPrivateManifestValue(value), null, 2)}\n`, + 'utf8', + ); +} + +function safeBufferEqual(left: Uint8Array, right: Uint8Array): boolean { + return left.byteLength === right.byteLength && timingSafeEqual(left, right); +} + +function deriveCandidateSeedMaterial( + batchSeed: Uint8Array, + candidateOrdinal: number, +): Buffer { + if ( + batchSeed.byteLength !== 32 + || !Number.isSafeInteger(candidateOrdinal) + || candidateOrdinal < 0 + ) fail('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); + return createHmac('sha256', batchSeed) + .update(GREATER_REALM_GENERATOR_VERSION, 'utf8') + .update('\0candidate\0', 'utf8') + .update(String(candidateOrdinal), 'utf8') + .digest(); +} + +function privateProvenanceDigest(input: Readonly<{ + batchHandle: string; + sourceCommit: string; + candidateOrdinal: number; + seedMaterial: Uint8Array; +}>): string { + return createHash('sha256') + .update('warpkeep.greater-realm.private-provenance.v1\0', 'utf8') + .update(input.batchHandle, 'utf8') + .update('\0', 'utf8') + .update(input.sourceCommit, 'utf8') + .update('\0', 'utf8') + .update(GREATER_REALM_GENERATOR_VERSION, 'utf8') + .update('\0', 'utf8') + .update(String(input.candidateOrdinal), 'utf8') + .update('\0', 'utf8') + .update(input.seedMaterial) + .digest('hex'); +} + +function crc32(bytes: Uint8Array): number { + let crc = 0xffff_ffff; + for (const byte of bytes) { + crc ^= byte; + for (let bit = 0; bit < 8; bit += 1) { + crc = (crc >>> 1) ^ (crc & 1 ? 0xedb8_8320 : 0); + } + } + return (crc ^ 0xffff_ffff) >>> 0; +} + +function privatePreviewTextChunk(): Buffer { + const type = Buffer.from('tEXt', 'ascii'); + const data = Buffer.from( + `${PRIVATE_PREVIEW_TEXT_KEY}\0${GREATER_REALM_PRIVATE_PREVIEW_MARKER}`, + 'latin1', + ); + const crcInput = Buffer.concat([type, data]); + const chunk = Buffer.allocUnsafe(12 + data.length); + try { + chunk.writeUInt32BE(data.length, 0); + type.copy(chunk, 4); + data.copy(chunk, 8); + chunk.writeUInt32BE(crc32(crcInput), 8 + data.length); + return chunk; + } finally { + type.fill(0); + data.fill(0); + crcInput.fill(0); + } +} + +function markPrivatePreviewPng(png: Buffer): Buffer { + if (!png.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) { + fail('GREATER_REALM_PRIVATE_PREVIEW_INVALID'); + } + let offset = PNG_SIGNATURE.length; + while (offset + 12 <= png.length) { + const length = png.readUInt32BE(offset); + const end = offset + 12 + length; + if (end > png.length) fail('GREATER_REALM_PRIVATE_PREVIEW_INVALID'); + const type = png.subarray(offset + 4, offset + 8).toString('ascii'); + if (type === 'IEND') { + if (length !== 0 || end !== png.length) fail('GREATER_REALM_PRIVATE_PREVIEW_INVALID'); + const marker = privatePreviewTextChunk(); + try { + return Buffer.concat([png.subarray(0, offset), marker, png.subarray(offset)]); + } finally { + marker.fill(0); + } + } + offset = end; + } + return fail('GREATER_REALM_PRIVATE_PREVIEW_INVALID'); +} + +function hasValidPrivatePreviewMarker(png: Buffer): boolean { + if (!png.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) return false; + const expected = Buffer.from( + `${PRIVATE_PREVIEW_TEXT_KEY}\0${GREATER_REALM_PRIVATE_PREVIEW_MARKER}`, + 'latin1', + ); + let matches = 0; + let offset = PNG_SIGNATURE.length; + try { + while (offset + 12 <= png.length) { + const length = png.readUInt32BE(offset); + const end = offset + 12 + length; + if (end > png.length) return false; + const type = png.subarray(offset + 4, offset + 8); + const data = png.subarray(offset + 8, offset + 8 + length); + const expectedCrc = png.readUInt32BE(offset + 8 + length); + const crcInput = png.subarray(offset + 4, offset + 8 + length); + if (crc32(crcInput) !== expectedCrc) return false; + const typeName = type.toString('ascii'); + if (typeName === 'tEXt' && safeBufferEqual(data, expected)) matches += 1; + if (typeName === 'IEND') return length === 0 && end === png.length && matches === 1; + offset = end; + } + return false; + } finally { + expected.fill(0); + } +} + +function privateFields(candidate: GreaterRealmPrivateCandidate): readonly EncodedField[] { + return Object.freeze([ + { name: 'q', type: 5, width: 4, array: candidate.grid.q }, + { name: 'r', type: 5, width: 4, array: candidate.grid.r }, + { name: 'bedrock-elevation', type: 5, width: 4, array: candidate.bedrockElevation }, + { name: 'erosion-elevation', type: 5, width: 4, array: candidate.elevation }, + { name: 'filled-elevation', type: 5, width: 4, array: candidate.filledElevation }, + { name: 'sediment-depth', type: 4, width: 2, array: candidate.sedimentDepth }, + { name: 'flow-receiver', type: 5, width: 4, array: candidate.flowReceiver }, + { name: 'flow-accumulation', type: 8, width: 8, array: candidate.flowAccumulation }, + { name: 'domain-id', type: 2, width: 1, array: candidate.domainId }, + { name: 'geology-id', type: 2, width: 1, array: candidate.geologyId }, + { name: 'region-id', type: 2, width: 1, array: candidate.regionId }, + { name: 'tier-id', type: 2, width: 1, array: candidate.tierId }, + { name: 'water-regime', type: 2, width: 1, array: candidate.waterRegime }, + { name: 'biome-id', type: 2, width: 1, array: candidate.biomeId }, + { name: 'landform-id', type: 2, width: 1, array: candidate.landformId }, + { name: 'barrier', type: 2, width: 1, array: candidate.barrier }, + { + name: 'geological-barrier-band', + type: 2, + width: 1, + array: candidate.geologicalBarrierBand, + }, + { name: 'castle-slot', type: 2, width: 1, array: candidate.castleSlot }, + { name: 'resource-potential', type: 2, width: 1, array: candidate.resourcePotential }, + { name: 'core-potential', type: 2, width: 1, array: candidate.corePotential }, + { name: 'throne-anchor', type: 2, width: 1, array: candidate.throneAnchor }, + { name: 'legacy-lowlands-cell', type: 2, width: 1, array: candidate.legacyLowlandsCell }, + { name: 'legacy-lowlands-protected-cell', type: 2, width: 1, array: candidate.legacyLowlandsProtectedCell }, + { name: 'legacy-lowlands-reserve-cell', type: 2, width: 1, array: candidate.legacyLowlandsReserveCell }, + { name: 'legacy-lowlands-castle-slot', type: 2, width: 1, array: candidate.legacyLowlandsCastleSlot }, + { name: 'tectonic-uplift', type: 5, width: 4, array: candidate.tectonicUplift }, + { name: 'rock-resistance', type: 5, width: 4, array: candidate.rockResistance }, + { + name: 'geomorphology-elevation', + type: 5, + width: 4, + array: candidate.geomorphologyElevation, + }, + { + name: 'geomorphology-total-delta', + type: 5, + width: 4, + array: candidate.geomorphologyTotalDelta, + }, + { + name: 'geomorphology-glacial-delta', + type: 5, + width: 4, + array: candidate.geomorphologyGlacialDelta, + }, + { + name: 'geomorphology-arid-delta', + type: 5, + width: 4, + array: candidate.geomorphologyAridDelta, + }, + { + name: 'geomorphology-volcanic-delta', + type: 5, + width: 4, + array: candidate.geomorphologyVolcanicDelta, + }, + { + name: 'geomorphology-coastal-delta', + type: 5, + width: 4, + array: candidate.geomorphologyCoastalDelta, + }, + { + name: 'geomorphology-glacial-mask', + type: 2, + width: 1, + array: candidate.geomorphologyGlacialMask, + }, + { + name: 'geomorphology-arid-mask', + type: 2, + width: 1, + array: candidate.geomorphologyAridMask, + }, + { + name: 'geomorphology-volcanic-mask', + type: 2, + width: 1, + array: candidate.geomorphologyVolcanicMask, + }, + { + name: 'geomorphology-volcanic-anchor-mask', + type: 2, + width: 1, + array: candidate.geomorphologyVolcanicAnchorMask, + }, + { + name: 'geomorphology-coastal-mask', + type: 2, + width: 1, + array: candidate.geomorphologyCoastalMask, + }, + { + name: 'geomorphology-coastal-class', + type: 2, + width: 1, + array: candidate.geomorphologyCoastalClass, + }, + { + name: 'geomorphology-temperature', + type: 5, + width: 4, + array: candidate.geomorphologyTemperature, + }, + { + name: 'geomorphology-moisture', + type: 5, + width: 4, + array: candidate.geomorphologyMoisture, + }, + { name: 'slope', type: 4, width: 2, array: candidate.slope }, + { name: 'aspect', type: 2, width: 1, array: candidate.aspect }, + { name: 'profile-curvature', type: 5, width: 4, array: candidate.profileCurvature }, + { name: 'plan-curvature', type: 5, width: 4, array: candidate.planCurvature }, + { name: 'wetness-index', type: 4, width: 2, array: candidate.wetnessIndex }, + { name: 'exposure', type: 5, width: 4, array: candidate.exposure }, + { name: 'distance-to-coast', type: 4, width: 2, array: candidate.distanceToCoast }, + { name: 'distance-to-freshwater', type: 4, width: 2, array: candidate.distanceToFreshwater }, + { name: 'watershed-id', type: 5, width: 4, array: candidate.watershedId }, + { name: 'ridge-id', type: 5, width: 4, array: candidate.ridgeId }, + { name: 'temperature', type: 5, width: 4, array: candidate.temperature }, + { name: 'moisture', type: 5, width: 4, array: candidate.moisture }, + ] as const); +} + +function encodedFieldHasExactType(field: EncodedField): boolean { + if (field.type === 1) return field.width === 1 && field.array instanceof Int8Array; + if (field.type === 2) { + return field.width === 1 + && field.array instanceof Uint8Array + && !(field.array instanceof Uint8ClampedArray); + } + if (field.type === 3) return field.width === 2 && field.array instanceof Int16Array; + if (field.type === 4) return field.width === 2 && field.array instanceof Uint16Array; + if (field.type === 5) return field.width === 4 && field.array instanceof Int32Array; + if (field.type === 6) return field.width === 4 && field.array instanceof Uint32Array; + if (field.type === 7) return field.width === 8 && field.array instanceof BigInt64Array; + return field.type === 8 && field.width === 8 && field.array instanceof BigUint64Array; +} + +function writeFieldValue( + buffer: Buffer, + offset: number, + field: EncodedField, + index: number, +): number { + const value = field.array[index]!; + if (field.type === 1) buffer.writeInt8(Number(value), offset); + else if (field.type === 2) buffer.writeUInt8(Number(value), offset); + else if (field.type === 3) buffer.writeInt16LE(Number(value), offset); + else if (field.type === 4) buffer.writeUInt16LE(Number(value), offset); + else if (field.type === 5) buffer.writeInt32LE(Number(value), offset); + else if (field.type === 6) buffer.writeUInt32LE(Number(value), offset); + else if (field.type === 7) buffer.writeBigInt64LE(BigInt(value), offset); + else if (field.type === 8) buffer.writeBigUInt64LE(BigInt(value), offset); + else fail('GREATER_REALM_PRIVATE_FIELD_TYPE_INVALID'); + return offset + field.width; +} + +function writeArray(buffer: Buffer, offset: number, field: EncodedField): number { + for (let index = 0; index < field.array.length; index += 1) { + offset = writeFieldValue(buffer, offset, field, index); + } + return offset; +} + +/** Canonical private binary; never write this buffer inside the repository. */ +export function serializeGreaterRealmPrivateAtlas( + candidate: GreaterRealmPrivateCandidate, +): Buffer { + const fields = privateFields(candidate); + const magic = Buffer.from(GREATER_REALM_PRIVATE_PACKAGE_MAGIC, 'ascii'); + let buffer: Buffer | undefined; + let completed = false; + try { + const headerBytes = 2 + magic.length + 2 + 4 + 2; + const fieldHeaderBytes = fields.reduce((total, field) => { + const nameBytes = Buffer.byteLength(field.name, 'utf8'); + if ( + nameBytes === 0 + || nameBytes > 255 + || field.array.length !== candidate.grid.cellCount + || !encodedFieldHasExactType(field) + ) { + fail('GREATER_REALM_PRIVATE_FIELD_INVALID'); + } + return total + 1 + nameBytes + 1 + 4 + 8; + }, 0); + const payloadBytes = fields.reduce((total, field) => ( + total + field.array.length * field.width + ), 0); + const totalBytes = headerBytes + fieldHeaderBytes + payloadBytes; + if (!Number.isSafeInteger(totalBytes) || totalBytes > PRIVATE_ATLAS_MAXIMUM_BYTES) { + fail('GREATER_REALM_PRIVATE_ATLAS_TOO_LARGE'); + } + buffer = Buffer.allocUnsafe(totalBytes); + let offset = 0; + buffer.writeUInt16LE(magic.length, offset); + offset += 2; + magic.copy(buffer, offset); + offset += magic.length; + buffer.writeUInt16LE(PRIVATE_ATLAS_FORMAT_VERSION, offset); + offset += 2; + buffer.writeUInt32LE(candidate.grid.cellCount, offset); + offset += 4; + buffer.writeUInt16LE(fields.length, offset); + offset += 2; + for (const field of fields) { + const name = Buffer.from(field.name, 'utf8'); + try { + buffer.writeUInt8(name.length, offset); + offset += 1; + name.copy(buffer, offset); + offset += name.length; + buffer.writeUInt8(field.type, offset); + offset += 1; + buffer.writeUInt32LE(field.array.length, offset); + offset += 4; + buffer.writeBigUInt64LE(BigInt(field.array.length * field.width), offset); + offset += 8; + offset = writeArray(buffer, offset, field); + } finally { + name.fill(0); + } + } + if (offset !== buffer.length) fail('GREATER_REALM_PRIVATE_ATLAS_LENGTH_MISMATCH'); + completed = true; + return buffer; + } finally { + magic.fill(0); + if (!completed) buffer?.fill(0); + } +} + +function candidateRegionCounts(candidate: GreaterRealmPrivateCandidate): readonly number[] { + const counts = Array(GREATER_REALM_REGION_SPECS.length).fill(0); + for (const region of candidate.regionId) { + if (region >= counts.length) fail('GREATER_REALM_PRIVATE_REGION_INVALID'); + counts[region] = counts[region]! + 1; + } + return Object.freeze(counts); +} + +const PRIVATE_TOPOGRAPHY_FIELD_NAMES = Object.freeze([ + 'geological-barrier-band', + 'tectonic-uplift', + 'rock-resistance', + 'geomorphology-elevation', + 'geomorphology-total-delta', + 'geomorphology-glacial-delta', + 'geomorphology-arid-delta', + 'geomorphology-volcanic-delta', + 'geomorphology-coastal-delta', + 'geomorphology-glacial-mask', + 'geomorphology-arid-mask', + 'geomorphology-volcanic-mask', + 'geomorphology-volcanic-anchor-mask', + 'geomorphology-coastal-mask', + 'geomorphology-coastal-class', + 'geomorphology-temperature', + 'geomorphology-moisture', + 'slope', + 'aspect', + 'profile-curvature', + 'plan-curvature', + 'wetness-index', + 'exposure', + 'distance-to-coast', + 'distance-to-freshwater', + 'watershed-id', + 'ridge-id', + 'temperature', + 'moisture', +] as const); + +const PRIVATE_CHUNK_MANIFEST_KEYS = Object.freeze([ + 'schema', + 'chunkKey', + 'chunkQ', + 'chunkR', + 'partitionVersion', + 'cellCount', + 'minimumElevationMilli', + 'maximumElevationMilli', + 'regionCellCounts', + 'tierCellCounts', + 'cellIndexDigest', + 'fieldCount', + 'payloadByteCount', + 'payloadDigest', + 'topographyPatchId', + 'topographyPatchDigest', + 'generationVersion', + 'geomorphologyVersion', + 'topographyVersion', + 'active', +] as const); + +const PRIVATE_TOPOGRAPHY_PATCH_KEYS = Object.freeze([ + 'schema', + 'topographyPatchId', + 'chunkKey', + 'chunkQ', + 'chunkR', + 'partitionVersion', + 'levelOfDetail', + 'sampleWidth', + 'sampleHeight', + 'sampleCount', + 'encodingVersion', + 'minimumElevationMilli', + 'maximumElevationMilli', + 'fieldCount', + 'fieldInventoryDigest', + 'payloadByteCount', + 'payloadDigest', + 'cellIndexDigest', + 'generationVersion', + 'geomorphologyVersion', + 'topographyVersion', + 'active', + 'manifestDigest', +] as const); + +function privateTopographyFields( + candidate: GreaterRealmPrivateCandidate, +): readonly EncodedField[] { + const byName = new Map(privateFields(candidate).map(field => [field.name, field] as const)); + return Object.freeze(PRIVATE_TOPOGRAPHY_FIELD_NAMES.map(name => { + const field = byName.get(name); + if (!field) fail('GREATER_REALM_PRIVATE_TOPOGRAPHY_FIELD_MISSING'); + return field; + })); +} + +function privateChunkPartition(candidate: GreaterRealmPrivateCandidate): readonly PrivateChunk[] { + const chunks = new Map(); + for (let index = 0; index < candidate.grid.cellCount; index += 1) { + const q = candidate.grid.q[index]!; + const r = candidate.grid.r[index]!; + if ( + q < -PRIVATE_CANVAS_RADIUS + || q > PRIVATE_CANVAS_RADIUS + || r < -PRIVATE_CANVAS_RADIUS + || r > PRIVATE_CANVAS_RADIUS + ) fail('GREATER_REALM_PRIVATE_CHUNK_COORDINATE_INVALID'); + const chunkQ = Math.floor((q + PRIVATE_CANVAS_RADIUS) / PRIVATE_CHUNK_AXIS_SPAN); + const chunkR = Math.floor((r + PRIVATE_CANVAS_RADIUS) / PRIVATE_CHUNK_AXIS_SPAN); + const chunkKey = `${chunkQ}:${chunkR}`; + const chunk = chunks.get(chunkKey) ?? { chunkQ, chunkR, cellIndices: [] }; + chunk.cellIndices.push(index); + chunks.set(chunkKey, chunk); + } + return Object.freeze([...chunks.entries()] + .sort(([, left], [, right]) => left.chunkQ - right.chunkQ || left.chunkR - right.chunkR) + .map(([chunkKey, chunk]) => Object.freeze({ + chunkKey, + chunkQ: chunk.chunkQ, + chunkR: chunk.chunkR, + cellIndices: Object.freeze(chunk.cellIndices), + }))); +} + +function fieldInventoryDigest(fields: readonly EncodedField[]): string { + const digest = createHash('sha256').update( + 'warpkeep.greater-realm.private-field-inventory.v1\0', + 'utf8', + ); + for (const field of fields) { + if (!encodedFieldHasExactType(field)) fail('GREATER_REALM_PRIVATE_FIELD_INVALID'); + digest.update(field.name, 'utf8'); + digest.update(`\0${field.type}:${field.width}\0`, 'utf8'); + } + return digest.digest('hex'); +} + +function cellIndexDigest( + candidate: GreaterRealmPrivateCandidate, + cellIndices: readonly number[], +): string { + const payload = Buffer.allocUnsafe(cellIndices.length * 12); + try { + for (let offset = 0; offset < cellIndices.length; offset += 1) { + const index = cellIndices[offset]!; + if (index < 0 || index >= candidate.grid.cellCount) { + fail('GREATER_REALM_PRIVATE_CHUNK_CELL_INVALID'); + } + payload.writeUInt32LE(index, offset * 12); + payload.writeInt32LE(candidate.grid.q[index]!, offset * 12 + 4); + payload.writeInt32LE(candidate.grid.r[index]!, offset * 12 + 8); + } + return createHash('sha256') + .update('warpkeep.greater-realm.private-chunk-cells.v1\0', 'utf8') + .update(payload) + .digest('hex'); + } finally { + payload.fill(0); + } +} + +function fieldPayloadDigest( + fields: readonly EncodedField[], + cellIndices: readonly number[], + domain: string, +): Readonly<{ digest: string; byteCount: number }> { + const digest = createHash('sha256').update(domain, 'utf8').update('\0', 'utf8'); + let byteCount = 0; + for (const field of fields) { + if (!encodedFieldHasExactType(field)) fail('GREATER_REALM_PRIVATE_FIELD_INVALID'); + const header = Buffer.allocUnsafe(10); + const name = Buffer.from(field.name, 'utf8'); + const payload = Buffer.allocUnsafe(cellIndices.length * field.width); + try { + header.writeUInt16LE(name.length, 0); + header.writeUInt8(field.type, 2); + header.writeUInt8(field.width, 3); + header.writeUInt32LE(cellIndices.length, 4); + header.writeUInt16LE(0, 8); + digest.update(header); + digest.update(name); + let offset = 0; + for (const index of cellIndices) { + if (index < 0 || index >= field.array.length) { + fail('GREATER_REALM_PRIVATE_CHUNK_CELL_INVALID'); + } + offset = writeFieldValue(payload, offset, field, index); + } + if (offset !== payload.length) fail('GREATER_REALM_PRIVATE_FIELD_LENGTH_INVALID'); + digest.update(payload); + byteCount += payload.length; + if (!Number.isSafeInteger(byteCount)) fail('GREATER_REALM_PRIVATE_FIELD_LENGTH_INVALID'); + } finally { + header.fill(0); + name.fill(0); + payload.fill(0); + } + } + return Object.freeze({ digest: digest.digest('hex'), byteCount }); +} + +function digestPrivateManifestEntry(domain: string, value: unknown): string { + const bytes = Buffer.from(JSON.stringify(canonicalPrivateManifestValue(value)), 'utf8'); + try { + return createHash('sha256').update(domain, 'utf8').update('\0', 'utf8').update(bytes) + .digest('hex'); + } finally { + bytes.fill(0); + } +} + +function candidatePrivateChunkManifests(candidate: GreaterRealmPrivateCandidate) { + const chunks = privateChunkPartition(candidate); + const allFields = privateFields(candidate); + const topographyFields = privateTopographyFields(candidate); + const topographyInventoryDigest = fieldInventoryDigest(topographyFields); + const chunkManifests: unknown[] = []; + const topographyPatchManifests: unknown[] = []; + for (const chunk of chunks) { + const regionCellCounts = Array(GREATER_REALM_REGION_SPECS.length).fill(0); + const tierCellCounts = [0, 0, 0]; + let minimumElevationMilli = Number.POSITIVE_INFINITY; + let maximumElevationMilli = Number.NEGATIVE_INFINITY; + let minimumQ = Number.POSITIVE_INFINITY; + let maximumQ = Number.NEGATIVE_INFINITY; + let minimumR = Number.POSITIVE_INFINITY; + let maximumR = Number.NEGATIVE_INFINITY; + for (const index of chunk.cellIndices) { + const region = candidate.regionId[index]!; + const tier = candidate.tierId[index]!; + if (region >= regionCellCounts.length || tier < 1 || tier > 3) { + fail('GREATER_REALM_PRIVATE_CHUNK_CLASSIFICATION_INVALID'); + } + regionCellCounts[region] = regionCellCounts[region]! + 1; + tierCellCounts[tier - 1] = tierCellCounts[tier - 1]! + 1; + minimumElevationMilli = Math.min(minimumElevationMilli, candidate.elevation[index]!); + maximumElevationMilli = Math.max(maximumElevationMilli, candidate.elevation[index]!); + minimumQ = Math.min(minimumQ, candidate.grid.q[index]!); + maximumQ = Math.max(maximumQ, candidate.grid.q[index]!); + minimumR = Math.min(minimumR, candidate.grid.r[index]!); + maximumR = Math.max(maximumR, candidate.grid.r[index]!); + } + if (chunk.cellIndices.length === 0) fail('GREATER_REALM_PRIVATE_CHUNK_EMPTY'); + const cellsDigest = cellIndexDigest(candidate, chunk.cellIndices); + const topographyPayload = fieldPayloadDigest( + topographyFields, + chunk.cellIndices, + 'warpkeep.greater-realm.private-topography-payload.v1', + ); + const topographyPatchId = `topography:${chunk.chunkKey}`; + const patchBody = Object.freeze({ + schema: PRIVATE_TOPOGRAPHY_PATCH_SCHEMA, + topographyPatchId, + chunkKey: chunk.chunkKey, + chunkQ: chunk.chunkQ, + chunkR: chunk.chunkR, + partitionVersion: PRIVATE_CHUNK_PARTITION_VERSION, + levelOfDetail: 0, + sampleWidth: maximumQ - minimumQ + 1, + sampleHeight: maximumR - minimumR + 1, + sampleCount: chunk.cellIndices.length, + encodingVersion: PRIVATE_TOPOGRAPHY_ENCODING_VERSION, + minimumElevationMilli, + maximumElevationMilli, + fieldCount: topographyFields.length, + fieldInventoryDigest: topographyInventoryDigest, + payloadByteCount: topographyPayload.byteCount, + payloadDigest: topographyPayload.digest, + cellIndexDigest: cellsDigest, + generationVersion: GREATER_REALM_GENERATOR_VERSION, + geomorphologyVersion: GREATER_REALM_GEOMORPHOLOGY_VERSION, + topographyVersion: PRIVATE_TOPOGRAPHY_VERSION, + active: false, + }); + const patch = Object.freeze({ + ...patchBody, + manifestDigest: digestPrivateManifestEntry( + 'warpkeep.greater-realm.private-topography-manifest.v1', + patchBody, + ), + }); + const chunkPayload = fieldPayloadDigest( + allFields, + chunk.cellIndices, + 'warpkeep.greater-realm.private-chunk-payload.v1', + ); + topographyPatchManifests.push(patch); + chunkManifests.push(Object.freeze({ + schema: PRIVATE_CHUNK_SCHEMA, + chunkKey: chunk.chunkKey, + chunkQ: chunk.chunkQ, + chunkR: chunk.chunkR, + partitionVersion: PRIVATE_CHUNK_PARTITION_VERSION, + cellCount: chunk.cellIndices.length, + minimumElevationMilli, + maximumElevationMilli, + regionCellCounts: Object.freeze(regionCellCounts), + tierCellCounts: Object.freeze(tierCellCounts), + cellIndexDigest: cellsDigest, + fieldCount: allFields.length, + payloadByteCount: chunkPayload.byteCount, + payloadDigest: chunkPayload.digest, + topographyPatchId, + topographyPatchDigest: patch.manifestDigest, + generationVersion: GREATER_REALM_GENERATOR_VERSION, + geomorphologyVersion: GREATER_REALM_GEOMORPHOLOGY_VERSION, + topographyVersion: PRIVATE_TOPOGRAPHY_VERSION, + active: false, + })); + } + if (chunkManifests.length !== candidate.privateMetrics.chunkCount) { + fail('GREATER_REALM_PRIVATE_CHUNK_COUNT_MISMATCH'); + } + return Object.freeze({ + chunkManifests: Object.freeze(chunkManifests), + topographyPatchManifests: Object.freeze(topographyPatchManifests), + }); +} + +export type GreaterRealmCandidatePerformance = Readonly<{ + generationMilliseconds: number; + processPeakMemoryMiB: number; +}>; + +/** + * Aggregate-only owner review vector captured from a fully verified private + * package. It deliberately contains no coordinates, seeds, transforms, paths, + * digests, region identities, or exact hidden-site locations. + */ +export type GreaterRealmVerifiedPrivateShortlistMetrics = Readonly<{ + candidateHandle: string; + maximumBoundaryRadiusShareBasisPoints: number; + rotationalSimilarityBasisPoints: number; + maximumAlignedBoundaryRun: number; + saltwaterBoundaryBasisPoints: number; + minimumLargestPassableRegionShareBasisPoints: number; + maximumMinorPassableFragmentShareBasisPoints: number; + maximumPassableBoundaryDensityBasisPoints: number; + maximumPassableTendrilShareBasisPoints: number; + throneAnchorBarrierClearance: number; + gateRouteRedundancyProof: boolean; + measuredMinimumBarrierWidth: number; + measuredMaximumBarrierWidth: number; + chunkCount: number; + chunkPopulationSpread: number; + chunkUpperTailSpread: number; + highlandBarrierShareBasisPoints: number; + barrierMeanElevationAdvantage: number; + barrierMeanUpliftAdvantage: number; + ridgeUpliftAlignmentBasisPoints: number; + riverValleyAlignmentBasisPoints: number; + landformClimateCompatibilityFloorBasisPoints: number; + coastalProximityCompatibilityBasisPoints: number; + coastalClassCount: number; +}>; + +function verifiedPrivateShortlistMetrics( + candidateHandle: string, + candidate: GreaterRealmPrivateCandidate, +): GreaterRealmVerifiedPrivateShortlistMetrics { + const privateMetrics = candidate.privateMetrics; + const geomorphology = privateMetrics.geomorphology; + return Object.freeze({ + candidateHandle, + maximumBoundaryRadiusShareBasisPoints: + privateMetrics.maximumBoundaryRadiusShareBasisPoints, + rotationalSimilarityBasisPoints: privateMetrics.rotationalSimilarityBasisPoints, + maximumAlignedBoundaryRun: privateMetrics.maximumAlignedBoundaryRun, + saltwaterBoundaryBasisPoints: privateMetrics.saltwaterBoundaryBasisPoints, + minimumLargestPassableRegionShareBasisPoints: + privateMetrics.minimumLargestPassableRegionShareBasisPoints, + maximumMinorPassableFragmentShareBasisPoints: Math.max( + ...privateMetrics.minorPassableFragmentSharesBasisPoints, + ), + maximumPassableBoundaryDensityBasisPoints: Math.max( + ...privateMetrics.passableBoundaryDensityBasisPoints, + ), + maximumPassableTendrilShareBasisPoints: Math.max( + ...privateMetrics.passableTendrilSharesBasisPoints, + ), + throneAnchorBarrierClearance: privateMetrics.throneAnchorBarrierClearance, + gateRouteRedundancyProof: privateMetrics.gateRouteRedundancyProof, + measuredMinimumBarrierWidth: privateMetrics.measuredMinimumBarrierWidth, + measuredMaximumBarrierWidth: privateMetrics.measuredMaximumBarrierWidth, + chunkCount: privateMetrics.chunkCount, + chunkPopulationSpread: + privateMetrics.chunkPopulationMaximum - privateMetrics.chunkPopulationMinimum, + chunkUpperTailSpread: + privateMetrics.chunkPopulationP95 - privateMetrics.chunkPopulationMedian, + highlandBarrierShareBasisPoints: privateMetrics.highlandBarrierShareBasisPoints, + barrierMeanElevationAdvantage: privateMetrics.barrierMeanElevationAdvantage, + barrierMeanUpliftAdvantage: privateMetrics.barrierMeanUpliftAdvantage, + ridgeUpliftAlignmentBasisPoints: geomorphology.ridgeUpliftAlignmentBasisPoints, + riverValleyAlignmentBasisPoints: geomorphology.riverValleyAlignmentBasisPoints, + landformClimateCompatibilityFloorBasisPoints: Math.min( + geomorphology.glacialClimateCompatibilityBasisPoints, + geomorphology.aridClimateCompatibilityBasisPoints, + geomorphology.volcanicTectonicCompatibilityBasisPoints, + geomorphology.coastalProximityCompatibilityBasisPoints, + ), + coastalProximityCompatibilityBasisPoints: + geomorphology.coastalProximityCompatibilityBasisPoints, + coastalClassCount: geomorphology.coastalClassCount, + }); +} + +export async function renderGreaterRealmPrivatePreview( + candidate: GreaterRealmPrivateCandidate, + mode: GreaterRealmPrivatePreviewMode, +): Promise { + if (!(PRIVATE_PREVIEW_MODES as readonly unknown[]).includes(mode)) { + fail('GREATER_REALM_PRIVATE_PREVIEW_MODE_INVALID'); + } + const width = 1_280; + const height = 1_024; + const pixels = Buffer.alloc(width * height * 4, 0); + let watermark: Buffer | undefined; + let encoded: Buffer | undefined; + try { + let minimumQ = Number.POSITIVE_INFINITY; + let maximumQ = Number.NEGATIVE_INFINITY; + let minimumR = Number.POSITIVE_INFINITY; + let maximumR = Number.NEGATIVE_INFINITY; + for (let index = 0; index < candidate.grid.cellCount; index += 1) { + minimumQ = Math.min(minimumQ, candidate.grid.q[index]!); + maximumQ = Math.max(maximumQ, candidate.grid.q[index]!); + minimumR = Math.min(minimumR, candidate.grid.r[index]!); + maximumR = Math.max(maximumR, candidate.grid.r[index]!); + } + const qSpan = maximumQ - minimumQ + 1; + const rSpan = maximumR - minimumR + 1; + const scale = Math.max(1, Math.floor(Math.min((width - 80) / (qSpan + rSpan / 2), (height - 120) / rSpan))); + const palette = [ + [74, 126, 72], [80, 141, 187], [74, 167, 205], [73, 129, 185], + [92, 159, 204], [119, 160, 83], [80, 116, 72], [178, 190, 204], + [188, 157, 92], [151, 104, 64], [99, 121, 82], [201, 184, 110], + [173, 133, 74], [143, 123, 88], [110, 119, 132], [100, 79, 109], + [65, 76, 88], [184, 194, 207], [129, 139, 98], [108, 108, 112], + [36, 74, 126], [59, 112, 161], [77, 145, 187], [124, 91, 142], + ] as const; + const regionPalette = [ + [87, 144, 84], [117, 159, 195], [205, 151, 76], [84, 147, 127], [103, 124, 164], + [151, 96, 80], [92, 112, 82], [116, 103, 126], [85, 121, 142], [116, 84, 135], + ] as const; + const gateCells = new Set(candidate.gates.flatMap(gate => [gate.firstCell, gate.secondCell])); + for (let index = 0; index < candidate.grid.cellCount; index += 1) { + const x = 40 + Math.round(((candidate.grid.q[index]! - minimumQ) + (candidate.grid.r[index]! - minimumR) / 2) * scale); + const y = 60 + Math.round((candidate.grid.r[index]! - minimumR) * scale * 0.86); + let color: readonly [number, number, number] = [44, 49, 64]; + if (mode === 'silhouette') { + color = candidate.waterRegime[index] === 0 ? [142, 164, 105] : [39, 76, 124]; + } else if (mode === 'hillshade') { + const shade = clampPreview(90 + Math.floor((candidate.elevation[index]! + 12_000) / 260)); + color = [shade, shade, Math.min(255, shade + 8)]; + } else if (mode === 'biome') { + color = palette[candidate.biomeId[index]! % palette.length]!; + } else if (mode === 'hydrology') { + color = candidate.waterRegime[index] === 0 ? [109, 118, 91] : [48, 132, 205]; + } else if (mode === 'regions') { + color = regionPalette[candidate.regionId[index]!]!; + } else { + color = gateCells.has(index) + ? [236, 194, 82] + : candidate.barrier[index] === 1 + ? [112, 104, 110] + : [73, 94, 78]; + } + for (let offsetY = 0; offsetY < Math.max(1, scale); offsetY += 1) { + for (let offsetX = 0; offsetX < Math.max(1, scale); offsetX += 1) { + const targetX = x + offsetX; + const targetY = y + offsetY; + if (targetX < 0 || targetX >= width || targetY < 0 || targetY >= height) continue; + const pixel = (targetY * width + targetX) * 4; + pixels[pixel] = color[0]; + pixels[pixel + 1] = color[1]; + pixels[pixel + 2] = color[2]; + pixels[pixel + 3] = 255; + } + } + } + const sharpModule = await import('sharp'); + watermark = Buffer.from( + `PRIVATE OWNER REVIEW — DO NOT DISTRIBUTE · ${mode.toUpperCase()}`, + 'utf8', + ); + encoded = await sharpModule.default(pixels, { raw: { width, height, channels: 4 } }) + .composite([{ input: watermark }]) + .png({ compressionLevel: 9, adaptiveFiltering: false, palette: true }) + .toBuffer(); + return markPrivatePreviewPng(encoded); + } finally { + pixels.fill(0); + watermark?.fill(0); + encoded?.fill(0); + } +} + +function clampPreview(value: number): number { + return Math.max(28, Math.min(236, value)); +} + +export async function writeGreaterRealmPrivateCandidate(input: Readonly<{ + workspace: GreaterRealmPrivateWorkspace; + batchHandle: string; + candidateHandle: string; + sourceCommit: string; + candidate: GreaterRealmPrivateCandidate; + performance: GreaterRealmCandidatePerformance; +}>): Promise> { + if ( + !BATCH_HANDLE_PATTERN.test(input.batchHandle) + || !CANDIDATE_HANDLE_PATTERN.test(input.candidateHandle) + || !SOURCE_COMMIT_PATTERN.test(input.sourceCommit) + || !Number.isSafeInteger(input.candidate.candidateOrdinal) + || input.candidate.candidateOrdinal < 0 + || input.candidate.candidateOrdinal > 255 + || !Number.isSafeInteger(input.candidate.grid.cellCount) + || input.candidate.grid.cellCount < 100_000 + || input.candidate.grid.cellCount > 150_000 + || input.candidate.aggregate.activeCellCount !== input.candidate.grid.cellCount + || input.candidate.seedMaterial.byteLength !== 32 + || input.candidate.aggregate.eligible !== true + || !Number.isSafeInteger(input.performance.generationMilliseconds) + || input.performance.generationMilliseconds < 100 + || input.performance.generationMilliseconds % 100 !== 0 + || input.performance.generationMilliseconds > 7 * 24 * 60 * 60 * 1_000 + || !Number.isSafeInteger(input.performance.processPeakMemoryMiB) + || input.performance.processPeakMemoryMiB < 1 + || input.performance.processPeakMemoryMiB > 1_048_576 + || ( + input.performance.processPeakMemoryMiB !== 1 + && input.performance.processPeakMemoryMiB % 8 !== 0 + ) + ) fail('GREATER_REALM_PRIVATE_PACKAGE_INPUT_INVALID'); + const base = `batches/${input.batchHandle}/candidates/${input.candidateHandle}`; + const throneAnchor = expectedThroneAnchorManifest(input.candidate); + const toolchainVersions = await privateToolchainVersions(); + const privateManifests = candidatePrivateChunkManifests(input.candidate); + input.workspace.ensureDirectory(base); + const atlas = serializeGreaterRealmPrivateAtlas(input.candidate); + let atlasDigest = ''; + try { + atlasDigest = createHash('sha256').update(atlas).digest('hex'); + const candidateSeedEnvelope = encodeGreaterRealmPrivateSeed( + input.candidate.seedMaterial, + 'candidate', + ); + try { + input.workspace.writeFileAtomic( + `${base}/seed.bin`, + candidateSeedEnvelope, + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + } finally { + candidateSeedEnvelope.fill(0); + } + input.workspace.writeFileAtomic( + `${base}/atlas.wkgr-atlas`, + atlas, + PRIVATE_ATLAS_MAXIMUM_BYTES, + ); + } finally { + atlas.fill(0); + } + const previewDigestEntries: Array = []; + for (const mode of PRIVATE_PREVIEW_MODES) { + const preview = await renderGreaterRealmPrivatePreview(input.candidate, mode); + try { + previewDigestEntries.push(Object.freeze([ + mode, + createHash('sha256').update(preview).digest('hex'), + ])); + input.workspace.writeFileAtomic( + `${base}/previews/private-preview-${mode}.png`, + preview, + PRIVATE_PREVIEW_MAXIMUM_BYTES, + ); + } finally { + preview.fill(0); + } + } + const manifest = Object.freeze({ + kind: GREATER_REALM_PRIVATE_MANIFEST_KIND, + formatVersion: PRIVATE_ATLAS_FORMAT_VERSION, + generatorVersion: GREATER_REALM_GENERATOR_VERSION, + geomorphologyVersion: GREATER_REALM_GEOMORPHOLOGY_VERSION, + sourceCommit: input.sourceCommit, + batchHandle: input.batchHandle, + candidateHandle: input.candidateHandle, + candidateOrdinal: input.candidate.candidateOrdinal, + seedDigest: createHash('sha256').update(input.candidate.seedMaterial).digest('hex'), + provenanceDigest: privateProvenanceDigest({ + batchHandle: input.batchHandle, + sourceCommit: input.sourceCommit, + candidateOrdinal: input.candidate.candidateOrdinal, + seedMaterial: input.candidate.seedMaterial, + }), + canvas: Object.freeze({ + kind: 'private-axial-disc', + radius: PRIVATE_CANVAS_RADIUS, + workingCells: 219_511, + }), + exactActiveCellCount: input.candidate.grid.cellCount, + regionSpecs: GREATER_REALM_REGION_SPECS, + exactRegionCellCounts: candidateRegionCounts(input.candidate), + chunkManifests: privateManifests.chunkManifests, + topographyPatchManifests: privateManifests.topographyPatchManifests, + toolchainVersions, + legacyLowlands: Object.freeze({ + schema: GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_SCHEMA, + transform: input.candidate.legacyLowlandsTransform, + lockPins: GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1, + }), + domains: input.candidate.domains, + tierOneSemanticPermutation: input.candidate.tierOneSemanticPermutation, + throneAnchor, + gateGraph: input.candidate.gateGraph, + barrierCrossSections: input.candidate.barrierCrossSections, + gates: input.candidate.gates.map(gate => Object.freeze({ + ...gate, + firstQ: input.candidate.grid.q[gate.firstCell], + firstR: input.candidate.grid.r[gate.firstCell], + secondQ: input.candidate.grid.q[gate.secondCell], + secondR: input.candidate.grid.r[gate.secondCell], + })), + stageDigests: input.candidate.stageDigests, + atlasDigest, + previewDigests: Object.freeze(Object.fromEntries(previewDigestEntries)), + aggregate: input.candidate.aggregate, + privateMetrics: input.candidate.privateMetrics, + performance: input.performance, + }); + const manifestBytes = serializeCanonicalPrivateManifest(manifest); + let manifestDigest = ''; + try { + if (manifestBytes.length > PRIVATE_MANIFEST_MAXIMUM_BYTES) { + fail('GREATER_REALM_PRIVATE_MANIFEST_TOO_LARGE'); + } + manifestDigest = createHash('sha256').update(manifestBytes).digest('hex'); + input.workspace.writeFileAtomic( + `${base}/manifest.private.json`, + manifestBytes, + PRIVATE_MANIFEST_MAXIMUM_BYTES, + ); + } finally { + manifestBytes.fill(0); + } + if (!SHA256_PATTERN.test(manifestDigest)) fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + return Object.freeze({ atlasDigest, manifestDigest }); +} + +function readUInt16(buffer: Buffer, offset: number): number { + if (offset < 0 || offset + 2 > buffer.length) fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + return buffer.readUInt16LE(offset); +} + +function readUInt32(buffer: Buffer, offset: number): number { + if (offset < 0 || offset + 4 > buffer.length) fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + return buffer.readUInt32LE(offset); +} + +function readUInt64(buffer: Buffer, offset: number): bigint { + if (offset < 0 || offset + 8 > buffer.length) fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + return buffer.readBigUInt64LE(offset); +} + +function verifyPrivateAtlasBinary(atlas: Buffer, expectedCellCount: number): void { + const fields = [ + ['q', 5, 4], + ['r', 5, 4], + ['bedrock-elevation', 5, 4], + ['erosion-elevation', 5, 4], + ['filled-elevation', 5, 4], + ['sediment-depth', 4, 2], + ['flow-receiver', 5, 4], + ['flow-accumulation', 8, 8], + ['domain-id', 2, 1], + ['geology-id', 2, 1], + ['region-id', 2, 1], + ['tier-id', 2, 1], + ['water-regime', 2, 1], + ['biome-id', 2, 1], + ['landform-id', 2, 1], + ['barrier', 2, 1], + ['geological-barrier-band', 2, 1], + ['castle-slot', 2, 1], + ['resource-potential', 2, 1], + ['core-potential', 2, 1], + ['throne-anchor', 2, 1], + ['legacy-lowlands-cell', 2, 1], + ['legacy-lowlands-protected-cell', 2, 1], + ['legacy-lowlands-reserve-cell', 2, 1], + ['legacy-lowlands-castle-slot', 2, 1], + ['tectonic-uplift', 5, 4], + ['rock-resistance', 5, 4], + ['geomorphology-elevation', 5, 4], + ['geomorphology-total-delta', 5, 4], + ['geomorphology-glacial-delta', 5, 4], + ['geomorphology-arid-delta', 5, 4], + ['geomorphology-volcanic-delta', 5, 4], + ['geomorphology-coastal-delta', 5, 4], + ['geomorphology-glacial-mask', 2, 1], + ['geomorphology-arid-mask', 2, 1], + ['geomorphology-volcanic-mask', 2, 1], + ['geomorphology-volcanic-anchor-mask', 2, 1], + ['geomorphology-coastal-mask', 2, 1], + ['geomorphology-coastal-class', 2, 1], + ['geomorphology-temperature', 5, 4], + ['geomorphology-moisture', 5, 4], + ['slope', 4, 2], + ['aspect', 2, 1], + ['profile-curvature', 5, 4], + ['plan-curvature', 5, 4], + ['wetness-index', 4, 2], + ['exposure', 5, 4], + ['distance-to-coast', 4, 2], + ['distance-to-freshwater', 4, 2], + ['watershed-id', 5, 4], + ['ridge-id', 5, 4], + ['temperature', 5, 4], + ['moisture', 5, 4], + ] as const; + let offset = 0; + const magicLength = readUInt16(atlas, offset); + offset += 2; + if (magicLength !== Buffer.byteLength(GREATER_REALM_PRIVATE_PACKAGE_MAGIC, 'ascii')) { + fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + } + if (offset + magicLength > atlas.length) fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + const magic = atlas.subarray(offset, offset + magicLength).toString('ascii'); + offset += magicLength; + const version = readUInt16(atlas, offset); + offset += 2; + const cellCount = readUInt32(atlas, offset); + offset += 4; + const fieldCount = readUInt16(atlas, offset); + offset += 2; + if ( + magic !== GREATER_REALM_PRIVATE_PACKAGE_MAGIC + || version !== PRIVATE_ATLAS_FORMAT_VERSION + || cellCount !== expectedCellCount + || fieldCount !== fields.length + ) fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + for (const [expectedName, expectedType, expectedWidth] of fields) { + if (offset + 1 > atlas.length) fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + const nameLength = atlas.readUInt8(offset); + offset += 1; + if (nameLength === 0 || offset + nameLength > atlas.length) { + fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + } + const name = atlas.subarray(offset, offset + nameLength).toString('utf8'); + offset += nameLength; + if (offset + 1 > atlas.length) fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + const type = atlas.readUInt8(offset); + offset += 1; + const length = readUInt32(atlas, offset); + offset += 4; + const byteLength = readUInt64(atlas, offset); + offset += 8; + const expectedBytes = BigInt(expectedCellCount * expectedWidth); + if ( + name !== expectedName + || type !== expectedType + || length !== expectedCellCount + || byteLength !== expectedBytes + || byteLength > BigInt(Number.MAX_SAFE_INTEGER) + || offset + Number(byteLength) > atlas.length + ) fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + offset += Number(byteLength); + } + if (offset !== atlas.length) fail('GREATER_REALM_PRIVATE_ATLAS_INVALID'); +} + +type UnknownPrivateManifest = Readonly>; + +function privateManifest(value: unknown): UnknownPrivateManifest { + return exactRecord(value, PRIVATE_MANIFEST_KEYS); +} + +function expectedGateManifest(candidate: GreaterRealmPrivateCandidate) { + return candidate.gates.map(gate => Object.freeze({ + ...gate, + firstQ: candidate.grid.q[gate.firstCell], + firstR: candidate.grid.r[gate.firstCell], + secondQ: candidate.grid.q[gate.secondCell], + secondR: candidate.grid.r[gate.secondCell], + })); +} + +function expectedThroneAnchorManifest(candidate: GreaterRealmPrivateCandidate) { + let anchorCell = -1; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const marker = candidate.throneAnchor[cell]!; + if (marker !== 0 && marker !== 1) fail('GREATER_REALM_PRIVATE_THRONE_ANCHOR_INVALID'); + if (marker !== 1) continue; + if (anchorCell !== -1) fail('GREATER_REALM_PRIVATE_THRONE_ANCHOR_INVALID'); + anchorCell = cell; + } + if ( + anchorCell < 0 + || candidate.tierId[anchorCell] !== 3 + || candidate.regionId[anchorCell] !== GREATER_REALM_REGION_SPECS.length - 1 + || candidate.waterRegime[anchorCell] !== 0 + || candidate.barrier[anchorCell] !== 0 + ) fail('GREATER_REALM_PRIVATE_THRONE_ANCHOR_INVALID'); + return Object.freeze({ + dormant: true as const, + cell: anchorCell, + q: candidate.grid.q[anchorCell], + r: candidate.grid.r[anchorCell], + regionId: candidate.regionId[anchorCell], + tierId: candidate.tierId[anchorCell], + }); +} + +export function clearGreaterRealmPrivateCandidateBuffers( + candidate: GreaterRealmPrivateCandidate, +): void { + clearGreaterRealmCandidateSecret(candidate); + for (const gate of candidate.gates) { + for (const path of [ + gate.firstApproachPath, + gate.firstAlternateApproachPath, + gate.secondApproachPath, + gate.secondAlternateApproachPath, + ]) { + (path as number[]).fill(0); + } + } + for (const crossSection of candidate.barrierCrossSections) { + (crossSection.cells as number[]).fill(0); + } + candidate.grid.clearIndex?.(); + new Uint8Array( + candidate.grid.neighbors.buffer, + candidate.grid.neighbors.byteOffset, + candidate.grid.neighbors.byteLength, + ).fill(0); + for (const field of privateFields(candidate)) { + new Uint8Array( + field.array.buffer, + field.array.byteOffset, + field.array.byteLength, + ).fill(0); + } +} + +function assertPrivateCandidateInventory( + workspace: GreaterRealmPrivateWorkspace, + base: string, +): void { + const attestation = workspace.attestTree(base); + if ( + attestation.fileCount !== 9 + || attestation.directoryCount !== 2 + || attestation.entryCount !== 11 + || attestation.byteCount < GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES + + PNG_SIGNATURE.length * PRIVATE_PREVIEW_MODES.length + || attestation.byteCount > PRIVATE_CANDIDATE_MAXIMUM_BYTES + ) fail('GREATER_REALM_PRIVATE_PACKAGE_INVENTORY_INVALID'); +} + +function assertManifestPerformance(value: unknown): void { + const performance = exactRecord(value, [ + 'generationMilliseconds', + 'processPeakMemoryMiB', + ]); + if ( + !Number.isSafeInteger(performance.generationMilliseconds) + || (performance.generationMilliseconds as number) < 100 + || (performance.generationMilliseconds as number) % 100 !== 0 + || (performance.generationMilliseconds as number) > 7 * 24 * 60 * 60 * 1_000 + || !Number.isSafeInteger(performance.processPeakMemoryMiB) + || (performance.processPeakMemoryMiB as number) < 1 + || (performance.processPeakMemoryMiB as number) > 1_048_576 + || ( + performance.processPeakMemoryMiB !== 1 + && (performance.processPeakMemoryMiB as number) % 8 !== 0 + ) + ) fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); +} + +function assertPrivateChunkManifestCollections( + manifest: UnknownPrivateManifest, + expectedCandidate: GreaterRealmPrivateCandidate, + expectedToolchainVersions: Awaited>, +): void { + if ( + !Array.isArray(manifest.chunkManifests) + || !Array.isArray(manifest.topographyPatchManifests) + || Object.getPrototypeOf(manifest.chunkManifests) !== Array.prototype + || Object.getPrototypeOf(manifest.topographyPatchManifests) !== Array.prototype + ) fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + const expected = candidatePrivateChunkManifests(expectedCandidate); + if ( + manifest.chunkManifests.length !== expected.chunkManifests.length + || manifest.topographyPatchManifests.length !== expected.topographyPatchManifests.length + || manifest.chunkManifests.length !== expectedCandidate.privateMetrics.chunkCount + ) fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + for (let index = 0; index < manifest.chunkManifests.length; index += 1) { + const chunk = exactRecord( + manifest.chunkManifests[index], + PRIVATE_CHUNK_MANIFEST_KEYS, + ); + if ( + chunk.schema !== PRIVATE_CHUNK_SCHEMA + || typeof chunk.payloadDigest !== 'string' + || !SHA256_PATTERN.test(chunk.payloadDigest) + || typeof chunk.cellIndexDigest !== 'string' + || !SHA256_PATTERN.test(chunk.cellIndexDigest) + || typeof chunk.topographyPatchDigest !== 'string' + || !SHA256_PATTERN.test(chunk.topographyPatchDigest) + || chunk.active !== false + || !exactJsonEqual(chunk, expected.chunkManifests[index]) + ) fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + } + for (let index = 0; index < manifest.topographyPatchManifests.length; index += 1) { + const patch = exactRecord( + manifest.topographyPatchManifests[index], + PRIVATE_TOPOGRAPHY_PATCH_KEYS, + ); + const patchBody = Object.fromEntries(Object.entries(patch) + .filter(([key]) => key !== 'manifestDigest')); + if ( + patch.schema !== PRIVATE_TOPOGRAPHY_PATCH_SCHEMA + || typeof patch.payloadDigest !== 'string' + || !SHA256_PATTERN.test(patch.payloadDigest) + || typeof patch.cellIndexDigest !== 'string' + || !SHA256_PATTERN.test(patch.cellIndexDigest) + || typeof patch.fieldInventoryDigest !== 'string' + || !SHA256_PATTERN.test(patch.fieldInventoryDigest) + || typeof patch.manifestDigest !== 'string' + || !SHA256_PATTERN.test(patch.manifestDigest) + || patch.manifestDigest !== digestPrivateManifestEntry( + 'warpkeep.greater-realm.private-topography-manifest.v1', + patchBody, + ) + || patch.active !== false + || !exactJsonEqual(patch, expected.topographyPatchManifests[index]) + ) fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + } + const toolchainVersions = exactRecord( + manifest.toolchainVersions, + PRIVATE_TOOLCHAIN_VERSION_KEYS, + ); + if (!exactJsonEqual(toolchainVersions, expectedToolchainVersions)) { + fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + } +} + +/** Re-hash and structurally verify one owner-only candidate package. */ +export async function verifyGreaterRealmPrivateCandidatePackage(input: Readonly<{ + workspace: GreaterRealmPrivateWorkspace; + batchHandle: string; + candidateHandle: string; + expectedCandidateOrdinal: number; + sourceCommit: string; + expectedBatchSeedDigest: string; + expectedActiveCellCount: number; + expectedAggregate: GreaterRealmPrivateCandidate['aggregate']; + expectedPerformance: GreaterRealmCandidatePerformance; + expectedAtlasDigest: string; + expectedManifestDigest: string; + onVerifiedPrivateShortlistMetrics?: ( + metrics: GreaterRealmVerifiedPrivateShortlistMetrics, + ) => void; +}>): Promise> { + if ( + !BATCH_HANDLE_PATTERN.test(input.batchHandle) + || !CANDIDATE_HANDLE_PATTERN.test(input.candidateHandle) + || !SHA256_PATTERN.test(input.expectedAtlasDigest) + || !SHA256_PATTERN.test(input.expectedManifestDigest) + || !SHA256_PATTERN.test(input.expectedBatchSeedDigest) + || !SOURCE_COMMIT_PATTERN.test(input.sourceCommit) + || !Number.isSafeInteger(input.expectedCandidateOrdinal) + || input.expectedCandidateOrdinal < 0 + || input.expectedCandidateOrdinal > 255 + || !Number.isSafeInteger(input.expectedActiveCellCount) + || input.expectedActiveCellCount < 100_000 + || input.expectedActiveCellCount > 150_000 + || ( + input.onVerifiedPrivateShortlistMetrics !== undefined + && typeof input.onVerifiedPrivateShortlistMetrics !== 'function' + ) + ) fail('GREATER_REALM_PRIVATE_PACKAGE_EXPECTATION_INVALID'); + const base = `batches/${input.batchHandle}/candidates/${input.candidateHandle}`; + let batchSeedEnvelope: Buffer | undefined; + let candidateSeedEnvelope: Buffer | undefined; + let batchSeed: Buffer | undefined; + let derivedSeed: Buffer | undefined; + let seed: Buffer | undefined; + let atlas: Buffer | undefined; + let expectedAtlas: Buffer | undefined; + let manifestBytes: Buffer | undefined; + let canonicalManifestBytes: Buffer | undefined; + let expectedCandidate: GreaterRealmPrivateCandidate | undefined; + try { + assertPrivateCandidateInventory(input.workspace, base); + batchSeedEnvelope = input.workspace.readFile( + `batches/${input.batchHandle}/batch-seed.bin`, + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + candidateSeedEnvelope = input.workspace.readFile( + `${base}/seed.bin`, + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + batchSeed = decodeGreaterRealmPrivateSeed(batchSeedEnvelope, 'batch'); + seed = decodeGreaterRealmPrivateSeed(candidateSeedEnvelope, 'candidate'); + atlas = input.workspace.readFile(`${base}/atlas.wkgr-atlas`, PRIVATE_ATLAS_MAXIMUM_BYTES); + manifestBytes = input.workspace.readFile( + `${base}/manifest.private.json`, + PRIVATE_MANIFEST_MAXIMUM_BYTES, + ); + if (batchSeed.length !== 32 || seed.length !== 32) { + fail('GREATER_REALM_PRIVATE_PACKAGE_INVALID'); + } + if ( + createHash('sha256').update(batchSeed).digest('hex') + !== input.expectedBatchSeedDigest + ) fail('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); + derivedSeed = deriveCandidateSeedMaterial(batchSeed, input.expectedCandidateOrdinal); + if (!safeBufferEqual(seed, derivedSeed)) fail('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); + expectedCandidate = generateGreaterRealmCandidate({ + rootSeed: batchSeed, + candidateOrdinal: input.expectedCandidateOrdinal, + }); + if ( + !safeBufferEqual(expectedCandidate.seedMaterial, derivedSeed) + || expectedCandidate.grid.cellCount !== input.expectedActiveCellCount + || expectedCandidate.aggregate.eligible !== true + || !exactJsonEqual(expectedCandidate.aggregate, input.expectedAggregate) + ) fail('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); + expectedAtlas = serializeGreaterRealmPrivateAtlas(expectedCandidate); + const atlasDigest = createHash('sha256').update(atlas).digest('hex'); + const manifestDigest = createHash('sha256').update(manifestBytes).digest('hex'); + if ( + atlasDigest !== input.expectedAtlasDigest + || manifestDigest !== input.expectedManifestDigest + ) fail('GREATER_REALM_PRIVATE_PACKAGE_DIGEST_MISMATCH'); + verifyPrivateAtlasBinary(atlas, input.expectedActiveCellCount); + if (!safeBufferEqual(atlas, expectedAtlas)) { + fail('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); + } + let parsed: unknown; + try { + parsed = JSON.parse(manifestBytes.toString('utf8')); + } catch { + fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + } + canonicalManifestBytes = serializeCanonicalPrivateManifest(parsed); + if (!safeBufferEqual(manifestBytes, canonicalManifestBytes)) { + fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + } + const manifest = privateManifest(parsed); + const expectedSeedDigest = createHash('sha256').update(derivedSeed).digest('hex'); + const expectedProvenanceDigest = privateProvenanceDigest({ + batchHandle: input.batchHandle, + sourceCommit: input.sourceCommit, + candidateOrdinal: input.expectedCandidateOrdinal, + seedMaterial: derivedSeed, + }); + if ( + manifest.kind !== GREATER_REALM_PRIVATE_MANIFEST_KIND + || manifest.formatVersion !== PRIVATE_ATLAS_FORMAT_VERSION + || manifest.generatorVersion !== GREATER_REALM_GENERATOR_VERSION + || manifest.geomorphologyVersion !== GREATER_REALM_GEOMORPHOLOGY_VERSION + || manifest.sourceCommit !== input.sourceCommit + || manifest.batchHandle !== input.batchHandle + || manifest.candidateHandle !== input.candidateHandle + || manifest.candidateOrdinal !== input.expectedCandidateOrdinal + || manifest.exactActiveCellCount !== input.expectedActiveCellCount + || manifest.atlasDigest !== atlasDigest + || manifest.seedDigest !== expectedSeedDigest + || manifest.provenanceDigest !== expectedProvenanceDigest + || !exactJsonEqual(manifest.canvas, { + kind: 'private-axial-disc', + radius: PRIVATE_CANVAS_RADIUS, + workingCells: 219_511, + }) + || !exactJsonEqual(manifest.regionSpecs, GREATER_REALM_REGION_SPECS) + || !exactJsonEqual(manifest.exactRegionCellCounts, candidateRegionCounts(expectedCandidate)) + || !exactJsonEqual(manifest.legacyLowlands, { + schema: GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_SCHEMA, + transform: expectedCandidate.legacyLowlandsTransform, + lockPins: GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1, + }) + || !exactJsonEqual(manifest.domains, expectedCandidate.domains) + || !exactJsonEqual( + manifest.tierOneSemanticPermutation, + expectedCandidate.tierOneSemanticPermutation, + ) + || !exactJsonEqual(manifest.throneAnchor, expectedThroneAnchorManifest(expectedCandidate)) + || !exactJsonEqual(manifest.gateGraph, expectedCandidate.gateGraph) + || !exactJsonEqual( + manifest.barrierCrossSections, + expectedCandidate.barrierCrossSections, + ) + || !exactJsonEqual(manifest.gates, expectedGateManifest(expectedCandidate)) + || !exactJsonEqual(manifest.stageDigests, expectedCandidate.stageDigests) + || !exactJsonEqual(manifest.aggregate, expectedCandidate.aggregate) + || !exactJsonEqual(manifest.privateMetrics, expectedCandidate.privateMetrics) + ) fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + assertPrivateChunkManifestCollections( + manifest, + expectedCandidate, + await privateToolchainVersions(), + ); + assertManifestPerformance(manifest.performance); + if (!exactJsonEqual(manifest.performance, input.expectedPerformance)) { + fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + } + const previewDigests = exactRecord(manifest.previewDigests, PRIVATE_PREVIEW_MODES); + if ( + PRIVATE_PREVIEW_MODES.some(mode => ( + typeof previewDigests[mode] !== 'string' + || !SHA256_PATTERN.test(previewDigests[mode]) + )) + ) fail('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + const sharpModule = await import('sharp'); + for (const mode of PRIVATE_PREVIEW_MODES) { + let preview: Buffer | undefined; + let expectedPreview: Buffer | undefined; + try { + expectedPreview = await renderGreaterRealmPrivatePreview(expectedCandidate, mode); + if (expectedPreview.length > PRIVATE_PREVIEW_MAXIMUM_BYTES) { + fail('GREATER_REALM_PRIVATE_PREVIEW_INVALID'); + } + preview = input.workspace.readFile( + `${base}/previews/private-preview-${mode}.png`, + expectedPreview.length, + ); + // Do not feed owner-workspace bytes into a PNG parser until they match + // the deterministic renderer output exactly. This also bounds all CRC + // work to the expected encoded size rather than the broad file cap. + if (!safeBufferEqual(preview, expectedPreview)) { + fail('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); + } + if ( + preview.length < PNG_SIGNATURE.length + || !preview.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE) + || !hasValidPrivatePreviewMarker(preview) + || createHash('sha256').update(preview).digest('hex') !== previewDigests[mode] + ) { + fail('GREATER_REALM_PRIVATE_PREVIEW_INVALID'); + } + try { + const metadata = await sharpModule.default( + preview, + { failOn: 'error', limitInputPixels: true }, + ) + .metadata(); + if ( + metadata.format !== 'png' + || metadata.width !== 1_280 + || metadata.height !== 1_024 + || metadata.hasAlpha !== true + || (metadata.pages !== undefined && metadata.pages !== 1) + ) fail('GREATER_REALM_PRIVATE_PREVIEW_INVALID'); + } catch { + fail('GREATER_REALM_PRIVATE_PREVIEW_INVALID'); + } + } finally { + expectedPreview?.fill(0); + preview?.fill(0); + } + } + input.onVerifiedPrivateShortlistMetrics?.( + verifiedPrivateShortlistMetrics(input.candidateHandle, expectedCandidate), + ); + return Object.freeze({ atlasDigest, manifestDigest }); + } finally { + batchSeedEnvelope?.fill(0); + candidateSeedEnvelope?.fill(0); + batchSeed?.fill(0); + derivedSeed?.fill(0); + seed?.fill(0); + atlas?.fill(0); + expectedAtlas?.fill(0); + manifestBytes?.fill(0); + canonicalManifestBytes?.fill(0); + if (expectedCandidate) clearGreaterRealmPrivateCandidateBuffers(expectedCandidate); + } +} diff --git a/scripts/atlas/greater-realm-candidate-rejection.ts b/scripts/atlas/greater-realm-candidate-rejection.ts new file mode 100644 index 00000000..fa7ae9ae --- /dev/null +++ b/scripts/atlas/greater-realm-candidate-rejection.ts @@ -0,0 +1,57 @@ +/** + * Exact, audited candidate-geography exhaustion outcomes. + * + * These codes mean a deterministic seed exhausted a bounded placement search; + * the batch generator may record the attempt and continue with the next + * ordinal. Invariants, malformed input, toolchain, filesystem, and package + * failures must remain ordinary fatal errors and are intentionally absent. + */ +export const GREATER_REALM_CANDIDATE_REJECTION_CODES = Object.freeze([ + 'GREATER_REALM_TECTONIC_DOMAIN_PLACEMENT_FAILED', + 'GREATER_REALM_ISLAND_ARC_PLACEMENT_FAILED', + 'GREATER_REALM_ACTIVE_MASK_EMPTY', + 'GREATER_REALM_LEGACY_LOWLANDS_PLACEMENT_MISSING', + 'GREATER_REALM_LEGACY_LOWLANDS_PLACEMENT_FAILED', + 'GREATER_REALM_OCEAN_OUTLETS_MISSING', + 'GREATER_REALM_RECONCILED_OCEAN_OUTLETS_MISSING', + 'GREATER_REALM_LEGACY_LOWLANDS_RESERVE_TOO_LARGE', + 'GREATER_REALM_STRATEGIC_BASIN_CAPACITY_INVARIANT', + 'GREATER_REALM_TIER_THREE_CAPACITY_INVARIANT', + 'GREATER_REALM_TIER_TWO_CAPACITY_INVARIANT', + 'GREATER_REALM_STRATEGIC_HIGHLAND_REFERENCE_MISSING', +] as const); + +export type GreaterRealmCandidateRejectionCode = + typeof GREATER_REALM_CANDIDATE_REJECTION_CODES[number]; + +const CANDIDATE_REJECTION_CODE_SET: ReadonlySet = new Set( + GREATER_REALM_CANDIDATE_REJECTION_CODES, +); + +export class GreaterRealmCandidateRejectionError extends Error { + readonly code: GreaterRealmCandidateRejectionCode; + + constructor(code: GreaterRealmCandidateRejectionCode) { + super(code); + if (!CANDIDATE_REJECTION_CODE_SET.has(code)) { + throw new Error('GREATER_REALM_CANDIDATE_REJECTION_CODE_INVALID'); + } + this.name = 'GreaterRealmCandidateRejectionError'; + this.code = code; + } +} + +export function rejectGreaterRealmCandidate( + code: GreaterRealmCandidateRejectionCode, +): never { + throw new GreaterRealmCandidateRejectionError(code); +} + +/** Never classify an ordinary Error merely because its message matches. */ +export function greaterRealmCandidateRejectionCode( + error: unknown, +): GreaterRealmCandidateRejectionCode | undefined { + return error instanceof GreaterRealmCandidateRejectionError + ? error.code + : undefined; +} diff --git a/scripts/atlas/greater-realm-cli.ts b/scripts/atlas/greater-realm-cli.ts new file mode 100644 index 00000000..09383fe3 --- /dev/null +++ b/scripts/atlas/greater-realm-cli.ts @@ -0,0 +1,1970 @@ +import { createHash, randomBytes, randomUUID } from 'node:crypto'; +import { + closeSync, + constants, + existsSync, + fchmodSync, + fstatSync, + fsyncSync, + linkSync, + lstatSync, + openSync, + readSync, + realpathSync, + unlinkSync, + writeSync, +} from 'node:fs'; +import type { Stats } from 'node:fs'; +import { basename, dirname, isAbsolute, parse, relative, resolve, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { + generateGreaterRealmCandidate, + GREATER_REALM_GENERATOR_VERSION, +} from './greater-realm-candidate-generator'; +import { + GREATER_REALM_CANDIDATE_REJECTION_CODES, + greaterRealmCandidateRejectionCode, + type GreaterRealmCandidateRejectionCode, +} from './greater-realm-candidate-rejection'; +import { + clearGreaterRealmPrivateCandidateBuffers, + verifyGreaterRealmPrivateCandidatePackage, + writeGreaterRealmPrivateCandidate, + type GreaterRealmCandidatePerformance, + type GreaterRealmVerifiedPrivateShortlistMetrics, +} from './greater-realm-candidate-package'; +import { + createGreaterRealmCandidateHandle, + createGreaterRealmReviewBatchHandle, + GREATER_REALM_MAXIMUM_CANDIDATE_COUNT, + GREATER_REALM_MINIMUM_CANDIDATE_COUNT, + type GreaterRealmSanitizedCandidateSource, + type GreaterRealmSanitizedReview, +} from './greater-realm-contracts'; +import { + assertGreaterRealmLegacyLowlandsPatchLocked, +} from './greater-realm-legacy-lowlands'; +import { + assertGreaterRealmPrivateInvocation, + defaultGreaterRealmPrivateWorkspaceRoot, + openGreaterRealmPrivateWorkspace, +} from './greater-realm-private-workspace'; +import { + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + decodeGreaterRealmPrivateSeed, + encodeGreaterRealmPrivateSeed, +} from './greater-realm-private-seed'; +import { + createGreaterRealmSanitizedReview, + parseGreaterRealmSanitizedReview, + serializeGreaterRealmSanitizedReview, +} from './greater-realm-sanitized-review'; +import { runGreaterRealmTrustedGit } from './greater-realm-git'; + +type Command = + | 'generate-candidates' + | 'compare-candidates' + | 'verify-private-package' + | 'export-sanitized-review' + | 'verify-sanitized-review' + | 'select-candidate'; + +type ParsedArguments = Readonly<{ + command: Command; + workspaceRoot: string; + count?: number; + maximumAttempts?: number; + batchHandle?: string; + candidateHandle?: string; + approvalReference?: string; + outputPath?: string; + inputPath?: string; + confirmSelection: boolean; +}>; + +const BATCH_HANDLE = /^GR-B-[A-Z2-7]{16}$/u; +const CANDIDATE_HANDLE = /^GR-A-[A-Z2-7]{16}$/u; +const APPROVAL_REFERENCE = /^OWNER-[A-Z0-9][A-Z0-9._-]{7,63}$/u; +const ROOT = resolve(import.meta.dirname, '..', '..'); +const PRIVATE_JSON_MAXIMUM_BYTES = 16 * 1024 * 1024; +const PRIVATE_BATCH_MAXIMUM_BYTES = GREATER_REALM_MAXIMUM_CANDIDATE_COUNT + * ( + 128 * 1024 * 1024 + + 6 * 16 * 1024 * 1024 + + 4 * 1024 * 1024 + + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES + ) + + 4 * PRIVATE_JSON_MAXIMUM_BYTES + + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES; + +function fail(code: string): never { + throw new Error(code); +} + +function exactRecord( + value: unknown, + expectedKeys: readonly string[], + code: string, +): Readonly> { + if ( + value === null + || typeof value !== 'object' + || Array.isArray(value) + || ![Object.prototype, null].includes(Object.getPrototypeOf(value)) + ) fail(code); + const ownKeys = Reflect.ownKeys(value); + if (ownKeys.some(key => typeof key !== 'string')) fail(code); + const actual = (ownKeys as string[]).sort(); + const expected = [...expectedKeys].sort(); + if ( + actual.length !== expected.length + || actual.some((key, index) => key !== expected[index]) + ) fail(code); + const descriptors = Object.getOwnPropertyDescriptors(value); + if (Object.values(descriptors).some(descriptor => !('value' in descriptor))) fail(code); + return value as Readonly>; +} + +function safeInteger(value: string, minimum: number, maximum: number): number { + if (!/^(?:0|[1-9][0-9]*)$/u.test(value)) fail('GREATER_REALM_CLI_INTEGER_INVALID'); + const parsed = Number(value); + if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) { + fail('GREATER_REALM_CLI_INTEGER_INVALID'); + } + return parsed; +} + +function parseArguments(argv: readonly string[]): ParsedArguments { + const commandValue = argv[0]; + if (![ + 'generate-candidates', + 'compare-candidates', + 'verify-private-package', + 'export-sanitized-review', + 'verify-sanitized-review', + 'select-candidate', + ].includes(commandValue ?? '')) fail('GREATER_REALM_CLI_USAGE'); + const command = commandValue as Command; + let workspaceRoot = defaultGreaterRealmPrivateWorkspaceRoot(); + let count: number | undefined; + let maximumAttempts: number | undefined; + let batchHandle: string | undefined; + let candidateHandle: string | undefined; + let approvalReference: string | undefined; + let outputPath: string | undefined; + let inputPath: string | undefined; + let confirmSelection = false; + const seen = new Set(); + for (let index = 1; index < argv.length; index += 1) { + const flag = argv[index]!; + if (seen.has(flag)) fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + seen.add(flag); + if (flag === '--confirm-selection') { + confirmSelection = true; + continue; + } + if (![ + '--workspace', '--count', '--maximum-attempts', '--batch', '--candidate', + '--approval-reference', '--output', '--input', + ].includes(flag)) fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + const value = argv[index + 1]; + if (value === undefined || value.startsWith('--')) fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + index += 1; + if (flag === '--workspace') workspaceRoot = value; + else if (flag === '--count') count = safeInteger( + value, + GREATER_REALM_MINIMUM_CANDIDATE_COUNT, + GREATER_REALM_MAXIMUM_CANDIDATE_COUNT, + ); + else if (flag === '--maximum-attempts') maximumAttempts = safeInteger(value, 8, 256); + else if (flag === '--batch') batchHandle = value; + else if (flag === '--candidate') candidateHandle = value; + else if (flag === '--approval-reference') approvalReference = value; + else if (flag === '--output') outputPath = value; + else if (flag === '--input') inputPath = value; + } + if (!isAbsolute(workspaceRoot)) fail('GREATER_REALM_CLI_WORKSPACE_NOT_ABSOLUTE'); + if (batchHandle !== undefined && !BATCH_HANDLE.test(batchHandle)) { + fail('GREATER_REALM_CLI_BATCH_INVALID'); + } + if (candidateHandle !== undefined && !CANDIDATE_HANDLE.test(candidateHandle)) { + fail('GREATER_REALM_CLI_CANDIDATE_INVALID'); + } + if (approvalReference !== undefined && !APPROVAL_REFERENCE.test(approvalReference)) { + fail('GREATER_REALM_CLI_APPROVAL_INVALID'); + } + if (command === 'generate-candidates') { + if (batchHandle || candidateHandle || approvalReference || outputPath || inputPath || confirmSelection) { + fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + } + if (count !== undefined && maximumAttempts !== undefined && maximumAttempts < count) { + fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + } + } else if (command === 'verify-sanitized-review') { + if (!inputPath || count || maximumAttempts || batchHandle || candidateHandle || approvalReference || outputPath || confirmSelection) { + fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + } + } else if (command === 'select-candidate') { + if (!batchHandle || !candidateHandle || !approvalReference || !confirmSelection || count || maximumAttempts || outputPath || inputPath) { + fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + } + } else if (command === 'export-sanitized-review') { + if (!batchHandle || !outputPath || count || maximumAttempts || candidateHandle || approvalReference || inputPath || confirmSelection) { + fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + } + } else if (!batchHandle || count || maximumAttempts || candidateHandle || approvalReference || outputPath || inputPath || confirmSelection) { + fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + } + return Object.freeze({ + command, + workspaceRoot, + ...(count === undefined ? {} : { count }), + ...(maximumAttempts === undefined ? {} : { maximumAttempts }), + ...(batchHandle === undefined ? {} : { batchHandle }), + ...(candidateHandle === undefined ? {} : { candidateHandle }), + ...(approvalReference === undefined ? {} : { approvalReference }), + ...(outputPath === undefined ? {} : { outputPath }), + ...(inputPath === undefined ? {} : { inputPath }), + confirmSelection, + }); +} + +function sourceCommit(): string { + const topLevel = runGreaterRealmTrustedGit( + ['rev-parse', '--path-format=absolute', '--show-toplevel'], + ROOT, + ); + if ( + topLevel.error + || topLevel.status !== 0 + || topLevel.stderr.length !== 0 + || resolve(topLevel.stdout.trim()) !== resolve(ROOT) + ) fail('GREATER_REALM_CLI_SOURCE_COMMIT_FAILED'); + const result = runGreaterRealmTrustedGit( + ['rev-parse', '--verify', 'HEAD^{commit}'], + ROOT, + ); + const value = result.stdout.trim(); + if ( + result.error + || result.status !== 0 + || result.stderr.length !== 0 + || !/^[0-9a-f]{40}$/u.test(value) + ) { + fail('GREATER_REALM_CLI_SOURCE_COMMIT_FAILED'); + } + const status = runGreaterRealmTrustedGit( + ['status', '--porcelain=v1', '-z', '--untracked-files=all', '--no-renames'], + ROOT, + ); + if ( + status.error + || status.status !== 0 + || status.stderr.length !== 0 + || status.stdout.length !== 0 + ) { + fail('GREATER_REALM_CLI_SOURCE_TREE_DIRTY'); + } + return value; +} + +const GENERATOR_PROVENANCE_PATHS = Object.freeze([ + 'package.json', + 'package-lock.json', + 'tsconfig.json', + 'tsconfig.app.json', + 'tsconfig.node.json', + 'scripts/atlas', + 'spacetimedb/src/world.ts', + 'spacetimedb/src/goldSitePolicy.ts', + 'spacetimedb/src/foodSitePolicy.ts', + 'spacetimedb/src/woodSitePolicy.ts', + 'spacetimedb/src/stoneSitePolicy.ts', + 'spacetimedb/src/forestLayoutPolicy.ts', + 'spacetimedb/src/forestLayoutContract.ts', + 'spacetimedb/src/waterWorld.ts', + 'spacetimedb/src/waterRevision.ts', +]); + +function assertGeneratorSourceProvenance(commit: string): void { + if (!/^[0-9a-f]{40}$/u.test(commit)) fail('GREATER_REALM_PRIVATE_SOURCE_INVALID'); + const ancestor = runGreaterRealmTrustedGit( + ['merge-base', '--is-ancestor', commit, 'HEAD'], + ROOT, + ); + const unchanged = runGreaterRealmTrustedGit( + [ + 'diff', '--quiet', '--no-ext-diff', '--no-textconv', commit, + '--', ...GENERATOR_PROVENANCE_PATHS, + ], + ROOT, + ); + const untracked = runGreaterRealmTrustedGit( + [ + 'ls-files', '--others', '--exclude-standard', '-z', + '--', ...GENERATOR_PROVENANCE_PATHS, + ], + ROOT, + ); + const ignored = runGreaterRealmTrustedGit( + [ + 'ls-files', '--others', '--ignored', '--exclude-standard', '-z', + '--', ...GENERATOR_PROVENANCE_PATHS, + ], + ROOT, + ); + if ( + ancestor.error + || ancestor.status !== 0 + || unchanged.error + || unchanged.status !== 0 + || untracked.error + || untracked.status !== 0 + || ignored.error + || ignored.status !== 0 + || ancestor.stdout.length !== 0 + || unchanged.stdout.length !== 0 + || untracked.stdout.length !== 0 + || ignored.stdout.length !== 0 + || ancestor.stderr.length !== 0 + || unchanged.stderr.length !== 0 + || untracked.stderr.length !== 0 + || ignored.stderr.length !== 0 + ) fail('GREATER_REALM_PRIVATE_SOURCE_MISMATCH'); +} + +function roundedPerformance(startedAt: bigint): GreaterRealmCandidatePerformance { + const milliseconds = Number(process.hrtime.bigint() - startedAt) / 1_000_000; + const processPeakMemoryMiB = process.resourceUsage().maxRSS / 1_024; + return Object.freeze({ + generationMilliseconds: Math.max(100, Math.round(milliseconds / 100) * 100), + processPeakMemoryMiB: Math.max(1, Math.round(processPeakMemoryMiB / 8) * 8), + }); +} + +function publicPerformance(performance: GreaterRealmCandidatePerformance) { + return Object.freeze({ + generationMillisecondsRounded: performance.generationMilliseconds, + processPeakMemoryMiBRounded: performance.processPeakMemoryMiB, + }); +} + +function writePrivateJson( + workspace: ReturnType, + path: string, + value: unknown, +): void { + const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}\n`, 'utf8'); + try { + workspace.writeFileAtomic(path, bytes, PRIVATE_JSON_MAXIMUM_BYTES); + } finally { + bytes.fill(0); + } +} + +async function generateCandidates(arguments_: ParsedArguments): Promise { + assertGreaterRealmLegacyLowlandsPatchLocked(); + const workspace = openGreaterRealmPrivateWorkspace({ + repositoryRoot: ROOT, + workspaceRoot: arguments_.workspaceRoot, + }); + const batchHandle = createGreaterRealmReviewBatchHandle(); + const requestedCount = arguments_.count ?? 12; + const maximumAttempts = arguments_.maximumAttempts ?? Math.max(128, requestedCount * 16); + if (maximumAttempts < requestedCount) fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); + const commit = sourceCommit(); + const rootSeed = randomBytes(32); + try { + await workspace.withAtomicDirectoryPublish( + `batches/${batchHandle}`, + async stagedWorkspace => { + await workspace.withExclusiveLock('locks/generate-candidates.lock', async () => { + const batchSeedEnvelope = encodeGreaterRealmPrivateSeed(rootSeed, 'batch'); + try { + stagedWorkspace.writeFileAtomic( + `batches/${batchHandle}/batch-seed.bin`, + batchSeedEnvelope, + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + } finally { + batchSeedEnvelope.fill(0); + } + const publicCandidates: GreaterRealmSanitizedCandidateSource[] = []; + const privateCandidates: Array> = []; + const rejectedAttempts: GreaterRealmPrivateRejectedAttempt[] = []; + for ( + let ordinal = 0; + ordinal < maximumAttempts && publicCandidates.length < requestedCount; + ordinal += 1 + ) { + const startedAt = process.hrtime.bigint(); + let candidate: ReturnType | undefined; + try { + candidate = generateGreaterRealmCandidate({ + rootSeed, + candidateOrdinal: ordinal, + }); + const performance = roundedPerformance(startedAt); + if (!candidate.aggregate.eligible) { + rejectedAttempts.push(Object.freeze({ + kind: 'proof-rejection', + candidateOrdinal: ordinal, + activeCellCount: candidate.grid.cellCount, + failedProofs: candidate.privateMetrics.eligibilityFailureCodes, + })); + continue; + } + const candidateHandle = createGreaterRealmCandidateHandle(); + const written = await writeGreaterRealmPrivateCandidate({ + workspace: stagedWorkspace, + batchHandle, + candidateHandle, + sourceCommit: commit, + candidate, + performance, + }); + privateCandidates.push(Object.freeze({ + candidateHandle, + candidateOrdinal: ordinal, + manifestDigest: written.manifestDigest, + atlasDigest: written.atlasDigest, + })); + publicCandidates.push(Object.freeze({ + candidateHandle, + ...candidate.aggregate, + performance: publicPerformance(performance), + })); + } catch (error) { + const rejectionCode = greaterRealmCandidateRejectionCode(error); + if (rejectionCode === undefined) throw error; + rejectedAttempts.push(Object.freeze({ + kind: 'geography-exhaustion', + candidateOrdinal: ordinal, + rejectionCode, + })); + } finally { + if (candidate) clearGreaterRealmPrivateCandidateBuffers(candidate); + } + } + if (publicCandidates.length !== requestedCount) { + fail('GREATER_REALM_CANDIDATE_BATCH_INCOMPLETE'); + } + const review = createGreaterRealmSanitizedReview({ + generatorVersion: GREATER_REALM_GENERATOR_VERSION, + sourceCommit: commit, + reviewBatchHandle: batchHandle, + selectionStatus: 'pending', + selectedCandidateHandle: null, + candidates: publicCandidates, + }); + const sanitizedBytes = Buffer.from( + serializeGreaterRealmSanitizedReview(review), + 'utf8', + ); + try { + stagedWorkspace.writeFileAtomic( + `batches/${batchHandle}/sanitized-review.json`, + sanitizedBytes, + 4 * 1024 * 1024, + ); + } finally { + sanitizedBytes.fill(0); + } + writePrivateJson( + stagedWorkspace, + `batches/${batchHandle}/batch.private.json`, + { + kind: 'warpkeep.greater-realm.private-batch.v1', + generatorVersion: GREATER_REALM_GENERATOR_VERSION, + sourceCommit: commit, + batchHandle, + batchSeedDigest: createHash('sha256').update(rootSeed).digest('hex'), + sanitizedReviewDigest: review.reportDigest, + requestedCount, + maximumAttempts, + attemptsUsed: privateCandidates.length + rejectedAttempts.length, + candidates: privateCandidates, + rejectedAttempts, + }, + ); + }); + }, + ); + } finally { + rootSeed.fill(0); + } + process.stdout.write(`${JSON.stringify({ + batchHandle, + eligibleCandidates: requestedCount, + selectionStatus: 'pending', + productionUntouched: true, + })}\n`); +} + +function reviewRelativePath(batchHandle: string): string { + return `batches/${batchHandle}/sanitized-review.json`; +} + +function selectionRelativePath(batchHandle: string): string { + return `batches/${batchHandle}/selection.private.json`; +} + +function shortlistRelativePath(batchHandle: string): string { + return `batches/${batchHandle}/shortlist.private.json`; +} + +function candidateSource( + candidate: ReturnType['candidates'][number], +): GreaterRealmSanitizedCandidateSource { + return Object.freeze({ + candidateHandle: candidate.candidateHandle, + eligible: candidate.eligible, + activeCellCount: candidate.activeCellCount, + landCellCount: candidate.landCellCount, + waterCellCount: candidate.waterCellCount, + tierCellCounts: candidate.tierCellCounts, + regionSizeRanges: candidate.regionSizeRanges, + hydrology: candidate.hydrology, + geology: candidate.geology, + topography: candidate.topography, + biomes: candidate.biomes, + quality: candidate.quality, + gateCount: candidate.gateCount, + castleSlotCount: candidate.castleSlotCount, + proofs: candidate.proofs, + performance: candidate.performance, + }); +} + +function candidateAggregateExpectation( + candidate: ReturnType['candidates'][number], +) { + return Object.freeze({ + eligible: candidate.eligible, + activeCellCount: candidate.activeCellCount, + landCellCount: candidate.landCellCount, + waterCellCount: candidate.waterCellCount, + tierCellCounts: candidate.tierCellCounts, + regionSizeRanges: candidate.regionSizeRanges, + hydrology: candidate.hydrology, + geology: candidate.geology, + topography: candidate.topography, + biomes: candidate.biomes, + quality: candidate.quality, + gateCount: candidate.gateCount, + castleSlotCount: candidate.castleSlotCount, + proofs: candidate.proofs, + }); +} + +type ShortlistCandidate = Readonly<{ + candidateHandle: string; + publicCandidate: GreaterRealmSanitizedReview['candidates'][number]; + privateMetrics: GreaterRealmVerifiedPrivateShortlistMetrics; +}>; + +const PRIVATE_SHORTLIST_METRIC_KEYS = Object.freeze([ + 'candidateHandle', + 'maximumBoundaryRadiusShareBasisPoints', + 'rotationalSimilarityBasisPoints', + 'maximumAlignedBoundaryRun', + 'saltwaterBoundaryBasisPoints', + 'minimumLargestPassableRegionShareBasisPoints', + 'maximumMinorPassableFragmentShareBasisPoints', + 'maximumPassableBoundaryDensityBasisPoints', + 'maximumPassableTendrilShareBasisPoints', + 'throneAnchorBarrierClearance', + 'gateRouteRedundancyProof', + 'measuredMinimumBarrierWidth', + 'measuredMaximumBarrierWidth', + 'chunkCount', + 'chunkPopulationSpread', + 'chunkUpperTailSpread', + 'highlandBarrierShareBasisPoints', + 'barrierMeanElevationAdvantage', + 'barrierMeanUpliftAdvantage', + 'ridgeUpliftAlignmentBasisPoints', + 'riverValleyAlignmentBasisPoints', + 'landformClimateCompatibilityFloorBasisPoints', + 'coastalProximityCompatibilityBasisPoints', + 'coastalClassCount', +] as const); + +function validatedPrivateShortlistMetrics( + value: unknown, +): GreaterRealmVerifiedPrivateShortlistMetrics { + const row = exactRecord( + value, + PRIVATE_SHORTLIST_METRIC_KEYS, + 'GREATER_REALM_PRIVATE_SHORTLIST_INVALID', + ); + const numericKeys = PRIVATE_SHORTLIST_METRIC_KEYS.filter(key => ![ + 'candidateHandle', + 'gateRouteRedundancyProof', + ].includes(key)); + if ( + typeof row.candidateHandle !== 'string' + || !CANDIDATE_HANDLE.test(row.candidateHandle) + || row.gateRouteRedundancyProof !== true + || numericKeys.some(key => !Number.isSafeInteger(row[key])) + || (row.measuredMinimumBarrierWidth as number) < 4 + || (row.measuredMaximumBarrierWidth as number) > 8 + || (row.measuredMinimumBarrierWidth as number) > (row.measuredMaximumBarrierWidth as number) + || (row.chunkCount as number) < 1 + || (row.chunkPopulationSpread as number) < 0 + || (row.chunkUpperTailSpread as number) < 0 + || (row.throneAnchorBarrierClearance as number) < 0 + || (row.coastalClassCount as number) < 1 + ) fail('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + for (const key of [ + 'maximumBoundaryRadiusShareBasisPoints', + 'rotationalSimilarityBasisPoints', + 'saltwaterBoundaryBasisPoints', + 'minimumLargestPassableRegionShareBasisPoints', + 'maximumMinorPassableFragmentShareBasisPoints', + 'maximumPassableBoundaryDensityBasisPoints', + 'maximumPassableTendrilShareBasisPoints', + 'highlandBarrierShareBasisPoints', + 'ridgeUpliftAlignmentBasisPoints', + 'riverValleyAlignmentBasisPoints', + 'landformClimateCompatibilityFloorBasisPoints', + 'coastalProximityCompatibilityBasisPoints', + ] as const) { + if ((row[key] as number) < 0 || (row[key] as number) > 10_000) { + fail('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + } + } + return row as unknown as GreaterRealmVerifiedPrivateShortlistMetrics; +} + +const PRIVATE_SHORTLIST_OBJECTIVES = Object.freeze([ + Object.freeze({ + code: 'NATURALNESS', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.publicCandidate.quality.naturalnessBasisPoints + ), + }), + Object.freeze({ + code: 'PASSABLE_REGION_COHERENCE', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.minimumLargestPassableRegionShareBasisPoints + ), + }), + Object.freeze({ + code: 'CHUNK_POPULATION_SPREAD', + direction: 'minimize' as const, + value: (candidate: ShortlistCandidate) => candidate.privateMetrics.chunkPopulationSpread, + }), + Object.freeze({ + code: 'RIDGE_UPLIFT_ALIGNMENT', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.ridgeUpliftAlignmentBasisPoints + ), + }), + Object.freeze({ + code: 'BIOME_VISUAL_DIVERSITY', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => candidate.publicCandidate.biomes.visualClassCount, + }), + Object.freeze({ + code: 'OUTER_BOUNDARY_RADIUS_ARTIFACT', + direction: 'minimize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.maximumBoundaryRadiusShareBasisPoints + ), + }), + Object.freeze({ + code: 'OUTER_BOUNDARY_ROTATIONAL_ARTIFACT', + direction: 'minimize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.rotationalSimilarityBasisPoints + ), + }), + Object.freeze({ + code: 'OUTER_BOUNDARY_AXIAL_RUN', + direction: 'minimize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.maximumAlignedBoundaryRun + ), + }), + Object.freeze({ + code: 'SALTWATER_OUTER_BOUNDARY', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.saltwaterBoundaryBasisPoints + ), + }), + Object.freeze({ + code: 'COASTAL_PROCESS_COMPATIBILITY', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.coastalProximityCompatibilityBasisPoints + ), + }), + Object.freeze({ + code: 'COASTAL_CLASS_DIVERSITY', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => candidate.privateMetrics.coastalClassCount, + }), + Object.freeze({ + code: 'MINOR_PASSABLE_FRAGMENTATION', + direction: 'minimize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.maximumMinorPassableFragmentShareBasisPoints + ), + }), + Object.freeze({ + code: 'PASSABLE_REGION_BOUNDARY_DENSITY', + direction: 'minimize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.maximumPassableBoundaryDensityBasisPoints + ), + }), + Object.freeze({ + code: 'PASSABLE_REGION_TENDRILS', + direction: 'minimize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.maximumPassableTendrilShareBasisPoints + ), + }), + Object.freeze({ + code: 'THRONE_ROUTE_CLEARANCE', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.throneAnchorBarrierClearance + ), + }), + Object.freeze({ + code: 'CHUNK_UPPER_TAIL_SPREAD', + direction: 'minimize' as const, + value: (candidate: ShortlistCandidate) => candidate.privateMetrics.chunkUpperTailSpread, + }), + Object.freeze({ + code: 'HIGHLAND_BARRIER_SHARE', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.highlandBarrierShareBasisPoints + ), + }), + Object.freeze({ + code: 'BARRIER_ELEVATION_ADVANTAGE', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.barrierMeanElevationAdvantage + ), + }), + Object.freeze({ + code: 'BARRIER_UPLIFT_ADVANTAGE', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.barrierMeanUpliftAdvantage + ), + }), + Object.freeze({ + code: 'RIVER_VALLEY_ALIGNMENT', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.riverValleyAlignmentBasisPoints + ), + }), + Object.freeze({ + code: 'LANDFORM_CLIMATE_COMPATIBILITY_FLOOR', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.privateMetrics.landformClimateCompatibilityFloorBasisPoints + ), + }), + Object.freeze({ + code: 'RIDGE_CONTINUITY', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.publicCandidate.quality.ridgeContinuityBasisPoints + ), + }), + Object.freeze({ + code: 'HYDROLOGY_COHERENCE', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.publicCandidate.quality.hydrologyCoherenceBasisPoints + ), + }), + Object.freeze({ + code: 'REGIONAL_BIOME_FLOOR', + direction: 'maximize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.publicCandidate.biomes.minimumPerRegionVisualClassCount + ), + }), + Object.freeze({ + code: 'TIER_I_SINGLE_BIOME_DOMINANCE', + direction: 'minimize' as const, + value: (candidate: ShortlistCandidate) => ( + candidate.publicCandidate.biomes.maximumTierISingleBiomeShareBasisPoints + ), + }), +] as const); + +const PRIVATE_SHORTLIST_HARD_CONSTRAINTS = Object.freeze([ + 'equal-zero:INCOMPATIBLE_BIOME_ADJACENCY', + 'equal-zero:INCOMPATIBLE_BIOME_LANDFORM_PAIRING', + 'equal-true:GATE_ROUTE_REDUNDANCY', + 'range-inclusive:MOUNTAIN_BARRIER_WIDTH_4_8', +] as const); + +function shortlistObjectiveComparison( + first: ShortlistCandidate, + second: ShortlistCandidate, + objective: typeof PRIVATE_SHORTLIST_OBJECTIVES[number], +): number { + const firstValue = objective.value(first); + const secondValue = objective.value(second); + return objective.direction === 'maximize' + ? firstValue - secondValue + : secondValue - firstValue; +} + +function shortlistCandidateDominates( + first: ShortlistCandidate, + second: ShortlistCandidate, +): boolean { + let strictlyBetter = false; + for (const objective of PRIVATE_SHORTLIST_OBJECTIVES) { + const comparison = shortlistObjectiveComparison(first, second, objective); + if (comparison < 0) return false; + if (comparison > 0) strictlyBetter = true; + } + return strictlyBetter; +} + +function shortlistDiversityDistance( + first: ShortlistCandidate, + second: ShortlistCandidate, + ranges: readonly Readonly<{ minimum: number; span: number }>[], +): number { + let maximumDistance = 0; + for (let index = 0; index < PRIVATE_SHORTLIST_OBJECTIVES.length; index += 1) { + const objective = PRIVATE_SHORTLIST_OBJECTIVES[index]!; + const range = ranges[index]!; + const distance = Math.abs(objective.value(first) - objective.value(second)) / range.span; + maximumDistance = Math.max(maximumDistance, distance); + } + return maximumDistance; +} + +/** + * Produces an unranked owner-review set. It deliberately has no scalar score, + * recommendation, or selection side effect: Pareto specialists and vector + * separation preserve materially different terrain trade-offs for a human. + */ +export function buildGreaterRealmPrivateCandidateShortlist( + review: GreaterRealmSanitizedReview, + verifiedPrivateMetrics: readonly GreaterRealmVerifiedPrivateShortlistMetrics[], +) { + if ( + review.selectionStatus !== 'pending' + || review.selectedCandidateHandle !== null + || review.candidates.length < 3 + || review.candidates.some(candidate => candidate.eligible !== true) + || review.candidates.some(candidate => ( + candidate.biomes.incompatibleVisualAdjacencyCount !== 0 + || candidate.biomes.incompatibleBiomeLandformPairCount !== 0 + )) + || !Array.isArray(verifiedPrivateMetrics) + || verifiedPrivateMetrics.length !== review.candidates.length + ) fail('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + const privateByHandle = new Map(); + for (const value of verifiedPrivateMetrics) { + const metrics = validatedPrivateShortlistMetrics(value); + if (privateByHandle.has(metrics.candidateHandle)) { + fail('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + } + privateByHandle.set(metrics.candidateHandle, metrics); + } + const candidates = review.candidates.map(publicCandidate => { + const privateMetrics = privateByHandle.get(publicCandidate.candidateHandle); + if (!privateMetrics) fail('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + return Object.freeze({ + candidateHandle: publicCandidate.candidateHandle, + publicCandidate, + privateMetrics, + }); + }).sort((first, second) => first.candidateHandle.localeCompare(second.candidateHandle)); + const paretoFront = candidates.filter(candidate => !candidates.some(other => ( + other !== candidate && shortlistCandidateDominates(other, candidate) + ))); + const selected = new Map(); + for (const objective of PRIVATE_SHORTLIST_OBJECTIVES) { + const specialist = [...paretoFront].sort((first, second) => { + const comparison = shortlistObjectiveComparison(second, first, objective); + return comparison === 0 + ? first.candidateHandle.localeCompare(second.candidateHandle) + : comparison; + })[0]; + if (specialist) selected.set(specialist.candidateHandle, specialist); + if (selected.size === 5) break; + } + const ranges = PRIVATE_SHORTLIST_OBJECTIVES.map(objective => { + const values = candidates.map(candidate => objective.value(candidate)); + const minimum = Math.min(...values); + return Object.freeze({ minimum, span: Math.max(1, Math.max(...values) - minimum) }); + }); + while (selected.size < 3) { + const remaining = candidates.filter(candidate => !selected.has(candidate.candidateHandle)); + if (remaining.length === 0) fail('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + const next = remaining.sort((first, second) => { + const minimumDistance = (candidate: ShortlistCandidate) => Math.min( + ...[...selected.values()].map(chosen => ( + shortlistDiversityDistance(candidate, chosen, ranges) + )), + ); + const firstDistance = selected.size === 0 ? 0 : minimumDistance(first); + const secondDistance = selected.size === 0 ? 0 : minimumDistance(second); + return secondDistance === firstDistance + ? first.candidateHandle.localeCompare(second.candidateHandle) + : secondDistance - firstDistance; + })[0]!; + selected.set(next.candidateHandle, next); + } + const candidateHandles = Object.freeze([...selected.keys()].sort()); + return Object.freeze({ + kind: 'warpkeep.greater-realm.private-owner-shortlist.v1' as const, + method: 'pareto-private-vector-diversity-v2' as const, + comparisonBasis: 'verified-private-package-aggregate-metrics-v1' as const, + batchHandle: review.reviewBatchHandle, + sourceReviewDigest: review.reportDigest, + selectionStatus: 'pending' as const, + selectedCandidateHandle: null, + ranked: false as const, + automaticSelection: false as const, + shortlistCount: candidateHandles.length, + candidateHandles, + objectiveDirections: Object.freeze(PRIVATE_SHORTLIST_OBJECTIVES.map(objective => ( + `${objective.direction}:${objective.code}` + ))), + requiredConstraints: PRIVATE_SHORTLIST_HARD_CONSTRAINTS, + }); +} + +function verifyExistingPrivateShortlist( + workspace: ReturnType, + review: GreaterRealmSanitizedReview, + verifiedPrivateMetrics: readonly GreaterRealmVerifiedPrivateShortlistMetrics[], +): void { + const path = shortlistRelativePath(review.reviewBatchHandle); + if (!workspace.hasFile(path)) return; + const actual = workspace.readFile(path, PRIVATE_JSON_MAXIMUM_BYTES); + const expected = Buffer.from( + `${JSON.stringify(buildGreaterRealmPrivateCandidateShortlist( + review, + verifiedPrivateMetrics, + ), null, 2)}\n`, + 'utf8', + ); + try { + if (actual.length !== expected.length || !actual.equals(expected)) { + fail('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + } + } finally { + actual.fill(0); + expected.fill(0); + } +} + +function parseOwnerSelection( + value: unknown, + batchHandle: string, + pendingReview: ReturnType, +) { + if ( + value === null + || typeof value !== 'object' + || Array.isArray(value) + || Object.getPrototypeOf(value) !== Object.prototype + ) fail('GREATER_REALM_SELECTION_RECEIPT_INVALID'); + const row = value as Record; + const actualKeys = Object.keys(row).sort(); + const expectedKeys = [ + 'approvalReference', + 'batchHandle', + 'candidateHandle', + 'generatorVersion', + 'kind', + 'selectedReview', + 'sourceReviewDigest', + ].sort(); + if ( + actualKeys.length !== expectedKeys.length + || actualKeys.some((key, index) => key !== expectedKeys[index]) + || row.kind !== 'warpkeep.greater-realm.private-owner-selection.v1' + || row.batchHandle !== batchHandle + || row.generatorVersion !== GREATER_REALM_GENERATOR_VERSION + || typeof row.candidateHandle !== 'string' + || !CANDIDATE_HANDLE.test(row.candidateHandle) + || typeof row.approvalReference !== 'string' + || !APPROVAL_REFERENCE.test(row.approvalReference) + || row.sourceReviewDigest !== pendingReview.reportDigest + || pendingReview.selectionStatus !== 'pending' + ) fail('GREATER_REALM_SELECTION_RECEIPT_INVALID'); + const selectedReview = parseGreaterRealmSanitizedReview(row.selectedReview); + const expectedReview = createGreaterRealmSanitizedReview({ + generatorVersion: pendingReview.generatorVersion, + sourceCommit: pendingReview.sourceCommit, + reviewBatchHandle: pendingReview.reviewBatchHandle, + selectionStatus: 'selected', + selectedCandidateHandle: row.candidateHandle, + candidates: pendingReview.candidates.map(candidateSource), + }); + if (selectedReview.reportDigest !== expectedReview.reportDigest) { + fail('GREATER_REALM_SELECTION_RECEIPT_INVALID'); + } + return selectedReview; +} + +function readPendingReview( + workspace: ReturnType, + batchHandle: string, +) { + const bytes = workspace.readFile(reviewRelativePath(batchHandle), 4 * 1024 * 1024); + let pendingReview: ReturnType; + try { + let parsed: unknown; + try { + parsed = JSON.parse(bytes.toString('utf8')); + } catch { + fail('GREATER_REALM_PRIVATE_REVIEW_INVALID'); + } + pendingReview = parseGreaterRealmSanitizedReview(parsed); + } finally { + bytes.fill(0); + } + if ( + pendingReview.reviewBatchHandle !== batchHandle + || pendingReview.generatorVersion !== GREATER_REALM_GENERATOR_VERSION + || pendingReview.selectionStatus !== 'pending' + || pendingReview.selectedCandidateHandle !== null + ) fail('GREATER_REALM_PRIVATE_REVIEW_INVALID'); + return pendingReview; +} + +function readReview( + workspace: ReturnType, + batchHandle: string, +) { + const pendingReview = readPendingReview(workspace, batchHandle); + const selectionPath = selectionRelativePath(batchHandle); + if (!workspace.hasFile(selectionPath)) return pendingReview; + const selection = workspace.readFile(selectionPath, 4 * 1024 * 1024); + try { + return parseOwnerSelection( + JSON.parse(selection.toString('utf8')), + batchHandle, + pendingReview, + ); + } catch (error) { + if (error instanceof SyntaxError) fail('GREATER_REALM_SELECTION_RECEIPT_INVALID'); + throw error; + } finally { + selection.fill(0); + } +} + +type GreaterRealmPrivateBatchCandidate = Readonly<{ + candidateHandle: string; + candidateOrdinal: number; + manifestDigest: string; + atlasDigest: string; +}>; + +export type GreaterRealmPrivateProofRejectedAttempt = Readonly<{ + kind: 'proof-rejection'; + candidateOrdinal: number; + activeCellCount: number; + failedProofs: readonly string[]; +}>; + +export type GreaterRealmPrivateGeographyRejectedAttempt = Readonly<{ + kind: 'geography-exhaustion'; + candidateOrdinal: number; + rejectionCode: GreaterRealmCandidateRejectionCode; +}>; + +export type GreaterRealmPrivateRejectedAttempt = + | GreaterRealmPrivateProofRejectedAttempt + | GreaterRealmPrivateGeographyRejectedAttempt; + +export function verifyGreaterRealmPrivateRejectedAttempt(input: Readonly<{ + rootSeed: Uint8Array; + rejectedAttempt: GreaterRealmPrivateRejectedAttempt; +}>): void { + let candidate: ReturnType | undefined; + try { + candidate = generateGreaterRealmCandidate({ + rootSeed: input.rootSeed, + candidateOrdinal: input.rejectedAttempt.candidateOrdinal, + }); + if (input.rejectedAttempt.kind !== 'proof-rejection') { + fail('GREATER_REALM_PRIVATE_ATTEMPT_LEDGER_INVALID'); + } + const proofAttempt = input.rejectedAttempt; + const actualFailures = candidate.privateMetrics.eligibilityFailureCodes; + if ( + candidate.aggregate.eligible + || candidate.grid.cellCount !== proofAttempt.activeCellCount + || actualFailures.length !== proofAttempt.failedProofs.length + || actualFailures.some((failure, index) => ( + failure !== proofAttempt.failedProofs[index] + )) + ) fail('GREATER_REALM_PRIVATE_ATTEMPT_LEDGER_INVALID'); + } catch (error) { + const actualRejectionCode = greaterRealmCandidateRejectionCode(error); + if (actualRejectionCode === undefined) throw error; + if ( + input.rejectedAttempt.kind !== 'geography-exhaustion' + || input.rejectedAttempt.rejectionCode !== actualRejectionCode + ) fail('GREATER_REALM_PRIVATE_ATTEMPT_LEDGER_INVALID'); + } finally { + if (candidate) clearGreaterRealmPrivateCandidateBuffers(candidate); + } +} + +function readPrivateBatch( + workspace: ReturnType, + batchHandle: string, +): Readonly<{ + sourceCommit: string; + batchSeedDigest: string; + sanitizedReviewDigest: string; + requestedCount: number; + maximumAttempts: number; + attemptsUsed: number; + candidates: readonly GreaterRealmPrivateBatchCandidate[]; + rejectedAttempts: readonly GreaterRealmPrivateRejectedAttempt[]; +}> { + const bytes = workspace.readFile( + `batches/${batchHandle}/batch.private.json`, + PRIVATE_JSON_MAXIMUM_BYTES, + ); + let batchSeedEnvelope: Buffer | undefined; + let batchSeed: Buffer | undefined; + try { + let parsed: unknown; + try { + parsed = JSON.parse(bytes.toString('utf8')); + } catch { + fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + } + const row = exactRecord(parsed, [ + 'attemptsUsed', + 'batchHandle', + 'batchSeedDigest', + 'candidates', + 'generatorVersion', + 'kind', + 'maximumAttempts', + 'rejectedAttempts', + 'requestedCount', + 'sanitizedReviewDigest', + 'sourceCommit', + ], 'GREATER_REALM_PRIVATE_BATCH_INVALID'); + if ( + row.kind !== 'warpkeep.greater-realm.private-batch.v1' + || row.generatorVersion !== GREATER_REALM_GENERATOR_VERSION + || row.batchHandle !== batchHandle + || typeof row.sourceCommit !== 'string' + || !/^[0-9a-f]{40}$/u.test(row.sourceCommit) + || typeof row.batchSeedDigest !== 'string' + || !/^[0-9a-f]{64}$/u.test(row.batchSeedDigest) + || typeof row.sanitizedReviewDigest !== 'string' + || !/^[0-9a-f]{64}$/u.test(row.sanitizedReviewDigest) + || !Number.isSafeInteger(row.requestedCount) + || (row.requestedCount as number) < GREATER_REALM_MINIMUM_CANDIDATE_COUNT + || (row.requestedCount as number) > GREATER_REALM_MAXIMUM_CANDIDATE_COUNT + || !Number.isSafeInteger(row.maximumAttempts) + || (row.maximumAttempts as number) < (row.requestedCount as number) + || (row.maximumAttempts as number) > 256 + || !Number.isSafeInteger(row.attemptsUsed) + || (row.attemptsUsed as number) < (row.requestedCount as number) + || (row.attemptsUsed as number) > (row.maximumAttempts as number) + || !Array.isArray(row.candidates) + || row.candidates.length !== row.requestedCount + || !Array.isArray(row.rejectedAttempts) + || row.rejectedAttempts.length !== ( + (row.attemptsUsed as number) - (row.requestedCount as number) + ) + ) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + const handles = new Set(); + const ordinals = new Set(); + const candidates = row.candidates.map(value => { + const candidate = exactRecord(value, [ + 'atlasDigest', + 'candidateHandle', + 'candidateOrdinal', + 'manifestDigest', + ], 'GREATER_REALM_PRIVATE_BATCH_INVALID'); + if ( + typeof candidate.candidateHandle !== 'string' + || !CANDIDATE_HANDLE.test(candidate.candidateHandle) + || handles.has(candidate.candidateHandle) + || !Number.isSafeInteger(candidate.candidateOrdinal) + || (candidate.candidateOrdinal as number) < 0 + || (candidate.candidateOrdinal as number) >= (row.attemptsUsed as number) + || ordinals.has(candidate.candidateOrdinal as number) + || typeof candidate.manifestDigest !== 'string' + || !/^[0-9a-f]{64}$/u.test(candidate.manifestDigest) + || typeof candidate.atlasDigest !== 'string' + || !/^[0-9a-f]{64}$/u.test(candidate.atlasDigest) + ) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + handles.add(candidate.candidateHandle); + ordinals.add(candidate.candidateOrdinal as number); + return Object.freeze({ + candidateHandle: candidate.candidateHandle, + candidateOrdinal: candidate.candidateOrdinal as number, + manifestDigest: candidate.manifestDigest, + atlasDigest: candidate.atlasDigest, + }); + }); + if (candidates.some((candidate, index) => ( + index > 0 && candidate.candidateOrdinal <= candidates[index - 1]!.candidateOrdinal + ))) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + const rejectedOrdinals = new Set(); + const rejectedAttempts: GreaterRealmPrivateRejectedAttempt[] = []; + for (const value of row.rejectedAttempts) { + if ( + value === null + || typeof value !== 'object' + || Array.isArray(value) + || ![Object.prototype, null].includes(Object.getPrototypeOf(value)) + ) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + const kindDescriptor = Object.getOwnPropertyDescriptor(value, 'kind'); + if (!kindDescriptor || !('value' in kindDescriptor)) { + fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + } + const kind = kindDescriptor.value; + const rejected = exactRecord( + value, + kind === 'proof-rejection' + ? ['activeCellCount', 'candidateOrdinal', 'failedProofs', 'kind'] + : kind === 'geography-exhaustion' + ? ['candidateOrdinal', 'kind', 'rejectionCode'] + : [], + 'GREATER_REALM_PRIVATE_BATCH_INVALID', + ); + if ( + !Number.isSafeInteger(rejected.candidateOrdinal) + || (rejected.candidateOrdinal as number) < 0 + || (rejected.candidateOrdinal as number) >= (row.attemptsUsed as number) + || ordinals.has(rejected.candidateOrdinal as number) + || rejectedOrdinals.has(rejected.candidateOrdinal as number) + ) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + rejectedOrdinals.add(rejected.candidateOrdinal as number); + if (kind === 'proof-rejection') { + if ( + !Number.isSafeInteger(rejected.activeCellCount) + || (rejected.activeCellCount as number) < 1 + || (rejected.activeCellCount as number) > 219_511 + || !Array.isArray(rejected.failedProofs) + || rejected.failedProofs.length < 1 + || rejected.failedProofs.length > 64 + || new Set(rejected.failedProofs).size !== rejected.failedProofs.length + || rejected.failedProofs.some(proof => ( + typeof proof !== 'string' + || !/^[A-Z0-9_:-]{3,160}$/u.test(proof) + )) + ) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + rejectedAttempts.push(Object.freeze({ + kind, + candidateOrdinal: rejected.candidateOrdinal as number, + activeCellCount: rejected.activeCellCount as number, + failedProofs: Object.freeze([...(rejected.failedProofs as string[])]), + })); + } else { + if ( + typeof rejected.rejectionCode !== 'string' + || !(GREATER_REALM_CANDIDATE_REJECTION_CODES as readonly string[]) + .includes(rejected.rejectionCode) + ) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + rejectedAttempts.push(Object.freeze({ + kind, + candidateOrdinal: rejected.candidateOrdinal as number, + rejectionCode: rejected.rejectionCode as GreaterRealmCandidateRejectionCode, + })); + } + } + if (rejectedAttempts.some((attempt, index) => ( + index > 0 && attempt.candidateOrdinal <= rejectedAttempts[index - 1]!.candidateOrdinal + ))) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + const everyOrdinal = new Set([...ordinals, ...rejectedOrdinals]); + for (let ordinal = 0; ordinal < (row.attemptsUsed as number); ordinal += 1) { + if (!everyOrdinal.has(ordinal)) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + } + if (!ordinals.has((row.attemptsUsed as number) - 1)) { + fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + } + batchSeedEnvelope = workspace.readFile( + `batches/${batchHandle}/batch-seed.bin`, + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + batchSeed = decodeGreaterRealmPrivateSeed(batchSeedEnvelope, 'batch'); + if ( + batchSeed.length !== 32 + || createHash('sha256').update(batchSeed).digest('hex') !== row.batchSeedDigest + ) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + return Object.freeze({ + sourceCommit: row.sourceCommit, + batchSeedDigest: row.batchSeedDigest, + sanitizedReviewDigest: row.sanitizedReviewDigest, + requestedCount: row.requestedCount as number, + maximumAttempts: row.maximumAttempts as number, + attemptsUsed: row.attemptsUsed as number, + candidates: Object.freeze(candidates), + rejectedAttempts: Object.freeze(rejectedAttempts), + }); + } finally { + batchSeedEnvelope?.fill(0); + batchSeed?.fill(0); + bytes.fill(0); + } +} + +function assertPrivateBatchInventory( + workspace: ReturnType, + batchHandle: string, + candidateCount: number, +): void { + const hasSelectionReceipt = workspace.hasFile(selectionRelativePath(batchHandle)); + const hasShortlist = workspace.hasFile(shortlistRelativePath(batchHandle)); + const attestation = workspace.attestTree(`batches/${batchHandle}`); + const expectedFileCount = 3 + + candidateCount * 9 + + (hasSelectionReceipt ? 1 : 0) + + (hasShortlist ? 1 : 0); + const expectedDirectoryCount = 2 + candidateCount * 2; + if ( + attestation.fileCount !== expectedFileCount + || attestation.directoryCount !== expectedDirectoryCount + || attestation.entryCount !== expectedFileCount + expectedDirectoryCount + || attestation.byteCount < GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES + || attestation.byteCount > PRIVATE_BATCH_MAXIMUM_BYTES + ) fail('GREATER_REALM_PRIVATE_PACKAGE_INVENTORY_INVALID'); +} + +async function verifyPrivateReviewBatch( + workspace: ReturnType, + batchHandle: string, +) { + const review = readPendingReview(workspace, batchHandle); + const batch = readPrivateBatch(workspace, batchHandle); + if ( + batch.sourceCommit !== review.sourceCommit + || batch.sanitizedReviewDigest !== review.reportDigest + || batch.requestedCount !== review.candidateCount + || batch.candidates.length !== review.candidateCount + ) fail('GREATER_REALM_PRIVATE_PACKAGE_INCOMPLETE'); + assertGeneratorSourceProvenance(batch.sourceCommit); + const reviewByHandle = new Map(review.candidates.map(candidate => [ + candidate.candidateHandle, + candidate, + ] as const)); + const verifiedPrivateMetrics: GreaterRealmVerifiedPrivateShortlistMetrics[] = []; + let batchSeedEnvelope: Buffer | undefined; + let batchSeed: Buffer | undefined; + try { + batchSeedEnvelope = workspace.readFile( + `batches/${batchHandle}/batch-seed.bin`, + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + batchSeed = decodeGreaterRealmPrivateSeed(batchSeedEnvelope, 'batch'); + if ( + batchSeed.length !== 32 + || createHash('sha256').update(batchSeed).digest('hex') !== batch.batchSeedDigest + ) fail('GREATER_REALM_PRIVATE_BATCH_INVALID'); + for (const rejected of batch.rejectedAttempts) { + verifyGreaterRealmPrivateRejectedAttempt({ + rootSeed: batchSeed, + rejectedAttempt: rejected, + }); + } + } finally { + batchSeedEnvelope?.fill(0); + batchSeed?.fill(0); + } + for (const privateCandidate of batch.candidates) { + const candidate = reviewByHandle.get(privateCandidate.candidateHandle); + if (!candidate || !candidate.eligible) fail('GREATER_REALM_PRIVATE_PACKAGE_INCOMPLETE'); + let comparisonMetrics: GreaterRealmVerifiedPrivateShortlistMetrics | undefined; + await verifyGreaterRealmPrivateCandidatePackage({ + workspace, + batchHandle, + candidateHandle: privateCandidate.candidateHandle, + expectedCandidateOrdinal: privateCandidate.candidateOrdinal, + sourceCommit: batch.sourceCommit, + expectedBatchSeedDigest: batch.batchSeedDigest, + expectedActiveCellCount: candidate.activeCellCount, + expectedAggregate: candidateAggregateExpectation(candidate), + expectedPerformance: Object.freeze({ + generationMilliseconds: candidate.performance.generationMillisecondsRounded, + processPeakMemoryMiB: candidate.performance.processPeakMemoryMiBRounded, + }), + expectedAtlasDigest: privateCandidate.atlasDigest, + expectedManifestDigest: privateCandidate.manifestDigest, + onVerifiedPrivateShortlistMetrics: metrics => { + comparisonMetrics = metrics; + }, + }); + if (!comparisonMetrics) fail('GREATER_REALM_PRIVATE_PACKAGE_INCOMPLETE'); + verifiedPrivateMetrics.push(comparisonMetrics); + } + if (reviewByHandle.size !== batch.candidates.length) { + fail('GREATER_REALM_PRIVATE_PACKAGE_INCOMPLETE'); + } + verifyExistingPrivateShortlist(workspace, review, verifiedPrivateMetrics); + assertPrivateBatchInventory(workspace, batchHandle, review.candidateCount); + const effectiveReview = workspace.hasFile(selectionRelativePath(batchHandle)) + ? readReview(workspace, batchHandle) + : review; + return Object.freeze({ + review: effectiveReview, + pendingReview: review, + batch, + verifiedPrivateMetrics: Object.freeze(verifiedPrivateMetrics), + }); +} + +const PUBLIC_EVIDENCE_FILE_MODE = 0o644; +const PUBLIC_EVIDENCE_MAXIMUM_BYTES = 4 * 1024 * 1024; + +type PublicEvidenceIdentity = Readonly<{ dev: number; ino: number }>; +type PublicEvidenceDirectoryEntry = Readonly<{ + path: string; + identity: PublicEvidenceIdentity; + uid: number; + mode: number; +}>; +type PublicEvidenceDirectoryAttestation = Readonly<{ + descriptor: number; + path: string; + chain: readonly PublicEvidenceDirectoryEntry[]; +}>; + +function samePublicEvidenceIdentity( + left: Pick, + right: Pick, +): boolean { + return left.dev === right.dev && left.ino === right.ino; +} + +function assertPublicEvidenceDirectoryStatus(status: Stats): void { + const currentUser = process.getuid?.(); + const ownerTrusted = currentUser === undefined || status.uid === 0 || status.uid === currentUser; + const writableByOthers = (status.mode & 0o022) !== 0; + const protectedStickyRoot = (status.mode & 0o1000) !== 0 && status.uid === 0; + if ( + !status.isDirectory() + || status.isSymbolicLink() + || !ownerTrusted + || (writableByOthers && !protectedStickyRoot) + ) fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); +} + +function capturePublicEvidenceDirectoryChain( + evidenceRoot: string, + repositoryRoot: string, +): readonly PublicEvidenceDirectoryEntry[] { + const canonicalRepositoryRoot = resolve(repositoryRoot); + const relation = relative(canonicalRepositoryRoot, evidenceRoot); + if ( + relation === '' + || relation === '..' + || relation.startsWith(`..${sep}`) + || isAbsolute(relation) + ) fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + const root = parse(canonicalRepositoryRoot).root; + const components = relative(root, evidenceRoot).split(sep).filter(Boolean); + let current = root; + const chain: PublicEvidenceDirectoryEntry[] = []; + const capture = (path: string): void => { + if (!existsSync(path)) fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + const status = lstatSync(path); + assertPublicEvidenceDirectoryStatus(status); + if (realpathSync(path) !== path) { + fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + } + chain.push(Object.freeze({ + path, + identity: Object.freeze({ dev: status.dev, ino: status.ino }), + uid: status.uid, + mode: status.mode, + })); + }; + capture(root); + for (const component of components) { + current = resolve(current, component); + capture(current); + } + return Object.freeze(chain); +} + +function openPublicEvidenceDirectory( + evidenceRoot: string, + repositoryRoot: string, +): PublicEvidenceDirectoryAttestation { + const chain = capturePublicEvidenceDirectoryChain(evidenceRoot, repositoryRoot); + let descriptor: number | undefined; + try { + descriptor = openSync( + evidenceRoot, + constants.O_RDONLY + | (constants.O_DIRECTORY ?? 0) + | (constants.O_NOFOLLOW ?? 0), + ); + const opened = fstatSync(descriptor); + const expected = chain.at(-1); + if (expected === undefined) fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + assertPublicEvidenceDirectoryStatus(opened); + if (!samePublicEvidenceIdentity(opened, expected.identity)) { + fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + } + const result = Object.freeze({ descriptor, path: evidenceRoot, chain }); + descriptor = undefined; + return result; + } catch (error) { + if (error instanceof Error && error.message.startsWith('GREATER_REALM_')) throw error; + return fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } +} + +function attestPublicEvidenceDirectory( + attestation: PublicEvidenceDirectoryAttestation, + repositoryRoot: string, +): void { + const currentChain = capturePublicEvidenceDirectoryChain(attestation.path, repositoryRoot); + if ( + currentChain.length !== attestation.chain.length + || currentChain.some((current, index) => { + const expected = attestation.chain[index]; + return expected === undefined + || current.path !== expected.path + || !samePublicEvidenceIdentity(current.identity, expected.identity) + || current.uid !== expected.uid + || current.mode !== expected.mode; + }) + ) fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_CHANGED'); + const opened = fstatSync(attestation.descriptor); + const expectedParent = attestation.chain.at(-1); + assertPublicEvidenceDirectoryStatus(opened); + if ( + expectedParent === undefined + || !samePublicEvidenceIdentity(opened, expectedParent.identity) + || opened.uid !== expectedParent.uid + || opened.mode !== expectedParent.mode + ) fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_CHANGED'); +} + +function assertPublicEvidenceDirectorySafe( + evidenceRoot: string, + repositoryRoot = ROOT, +): void { + const attestation = openPublicEvidenceDirectory(evidenceRoot, repositoryRoot); + closeSync(attestation.descriptor); +} + +function resolvePublicEvidenceDestination(repositoryRoot: string, path: string): string { + const absolute = resolve(repositoryRoot, path); + const evidenceRoot = resolve(repositoryRoot, 'docs', 'evidence', 'greater-realm'); + const name = basename(absolute); + if ( + dirname(absolute) !== evidenceRoot + || !/^[a-z0-9][a-z0-9._-]{0,126}\.json$/u.test(name) + ) fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + assertPublicEvidenceDirectorySafe(evidenceRoot, repositoryRoot); + return absolute; +} + +export function resolveGreaterRealmPublicEvidenceDestination(path: string): string { + return resolvePublicEvidenceDestination(ROOT, path); +} + +function readPublicEvidence(path: string): Buffer { + const absolute = resolveGreaterRealmPublicEvidenceDestination(path); + let descriptor: number | undefined; + try { + descriptor = openSync(absolute, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const before = fstatSync(descriptor); + if (!before.isFile() || before.size < 1 || before.size > 4 * 1024 * 1024) { + fail('GREATER_REALM_PUBLIC_EVIDENCE_INPUT_INVALID'); + } + const bytes = Buffer.alloc(before.size); + let offset = 0; + while (offset < bytes.length) { + const count = readSync(descriptor, bytes, offset, bytes.length - offset, null); + if (count <= 0) fail('GREATER_REALM_PUBLIC_EVIDENCE_INPUT_INVALID'); + offset += count; + } + const after = fstatSync(descriptor); + const current = lstatSync(absolute); + if ( + before.dev !== after.dev + || before.ino !== after.ino + || before.size !== after.size + || before.mtimeMs !== after.mtimeMs + || !current.isFile() + || current.isSymbolicLink() + || current.dev !== after.dev + || current.ino !== after.ino + ) { + bytes.fill(0); + fail('GREATER_REALM_PUBLIC_EVIDENCE_INPUT_INVALID'); + } + return bytes; + } catch (error) { + if (error instanceof Error && error.message.startsWith('GREATER_REALM_')) throw error; + return fail('GREATER_REALM_PUBLIC_EVIDENCE_INPUT_INVALID'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } +} + +function assertPublicEvidenceFileStatus( + status: Stats, + identity: PublicEvidenceIdentity, + byteLength: number, + linkCount: number, +): void { + if ( + !status.isFile() + || status.isSymbolicLink() + || !samePublicEvidenceIdentity(status, identity) + || status.size !== byteLength + || status.nlink !== linkCount + || (process.getuid !== undefined && status.uid !== process.getuid()) + || (status.mode & 0o777) !== PUBLIC_EVIDENCE_FILE_MODE + ) fail('GREATER_REALM_PUBLIC_EVIDENCE_WRITE_FAILED'); +} + +function attestPublicEvidenceBytes( + descriptor: number, + identity: PublicEvidenceIdentity, + expected: Buffer, + linkCount: number, +): void { + const before = fstatSync(descriptor); + assertPublicEvidenceFileStatus(before, identity, expected.byteLength, linkCount); + const observed = Buffer.alloc(expected.byteLength); + try { + let offset = 0; + while (offset < observed.byteLength) { + const count = readSync( + descriptor, + observed, + offset, + observed.byteLength - offset, + offset, + ); + if (count <= 0) fail('GREATER_REALM_PUBLIC_EVIDENCE_WRITE_FAILED'); + offset += count; + } + const after = fstatSync(descriptor); + assertPublicEvidenceFileStatus(after, identity, expected.byteLength, linkCount); + if ( + before.mtimeMs !== after.mtimeMs + || before.ctimeMs !== after.ctimeMs + || !observed.equals(expected) + ) fail('GREATER_REALM_PUBLIC_EVIDENCE_WRITE_FAILED'); + } finally { + observed.fill(0); + } +} + +function safeUnlinkPublicEvidenceIdentity( + path: string, + identity: PublicEvidenceIdentity, +): void { + try { + const current = lstatSync(path); + if (samePublicEvidenceIdentity(current, identity)) unlinkSync(path); + } catch { + // Cleanup never removes a substituted entry. + } +} + +type PublicEvidenceWriteInterlock = (input: Readonly<{ + destinationPath: string; + parentPath: string; + temporaryPath: string; +}>) => void; + +function writePublicEvidence( + path: string, + bytes: Buffer, + repositoryRoot = ROOT, + beforeInstall?: PublicEvidenceWriteInterlock, +): void { + if (bytes.byteLength < 1 || bytes.byteLength > PUBLIC_EVIDENCE_MAXIMUM_BYTES) { + fail('GREATER_REALM_PUBLIC_EVIDENCE_WRITE_FAILED'); + } + const destination = resolvePublicEvidenceDestination(repositoryRoot, path); + const parent = dirname(destination); + const parentAttestation = openPublicEvidenceDirectory(parent, repositoryRoot); + if (existsSync(destination)) { + closeSync(parentAttestation.descriptor); + fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + } + const temporary = `${destination}.${randomUUID()}.tmp`; + let descriptor: number | undefined; + let temporaryIdentity: PublicEvidenceIdentity | undefined; + let destinationInstalled = false; + let completed = false; + try { + attestPublicEvidenceDirectory(parentAttestation, repositoryRoot); + descriptor = openSync( + temporary, + constants.O_CREAT + | constants.O_EXCL + | constants.O_RDWR + | (constants.O_NOFOLLOW ?? 0), + PUBLIC_EVIDENCE_FILE_MODE, + ); + fchmodSync(descriptor, PUBLIC_EVIDENCE_FILE_MODE); + const created = fstatSync(descriptor); + temporaryIdentity = Object.freeze({ dev: created.dev, ino: created.ino }); + assertPublicEvidenceFileStatus(created, temporaryIdentity, 0, 1); + let offset = 0; + while (offset < bytes.length) { + const count = writeSync(descriptor, bytes, offset, bytes.length - offset); + if (count <= 0) fail('GREATER_REALM_PUBLIC_EVIDENCE_WRITE_FAILED'); + offset += count; + } + fsyncSync(descriptor); + attestPublicEvidenceBytes(descriptor, temporaryIdentity, bytes, 1); + assertPublicEvidenceFileStatus( + lstatSync(temporary), + temporaryIdentity, + bytes.byteLength, + 1, + ); + + beforeInstall?.(Object.freeze({ + destinationPath: destination, + parentPath: parent, + temporaryPath: temporary, + })); + + attestPublicEvidenceDirectory(parentAttestation, repositoryRoot); + assertPublicEvidenceFileStatus( + lstatSync(temporary), + temporaryIdentity, + bytes.byteLength, + 1, + ); + attestPublicEvidenceBytes(descriptor, temporaryIdentity, bytes, 1); + linkSync(temporary, destination); + destinationInstalled = true; + const linkedDestination = lstatSync(destination); + assertPublicEvidenceFileStatus( + lstatSync(temporary), + temporaryIdentity, + bytes.byteLength, + 2, + ); + assertPublicEvidenceFileStatus( + linkedDestination, + temporaryIdentity, + bytes.byteLength, + 2, + ); + attestPublicEvidenceBytes(descriptor, temporaryIdentity, bytes, 2); + attestPublicEvidenceDirectory(parentAttestation, repositoryRoot); + unlinkSync(temporary); + assertPublicEvidenceFileStatus( + lstatSync(destination), + temporaryIdentity, + bytes.byteLength, + 1, + ); + attestPublicEvidenceBytes(descriptor, temporaryIdentity, bytes, 1); + fsyncSync(descriptor); + fsyncSync(parentAttestation.descriptor); + attestPublicEvidenceDirectory(parentAttestation, repositoryRoot); + completed = true; + } catch (error) { + if (error instanceof Error && error.message.startsWith('GREATER_REALM_')) throw error; + if ((error as NodeJS.ErrnoException)?.code === 'EEXIST') { + fail('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + } + fail('GREATER_REALM_PUBLIC_EVIDENCE_WRITE_FAILED'); + } finally { + if (descriptor !== undefined) { + try { closeSync(descriptor); } catch { /* Preserve the fixed diagnostic. */ } + } + if (!completed && destinationInstalled && temporaryIdentity !== undefined) { + safeUnlinkPublicEvidenceIdentity(destination, temporaryIdentity); + } + if (temporaryIdentity !== undefined) { + safeUnlinkPublicEvidenceIdentity(temporary, temporaryIdentity); + } + try { closeSync(parentAttestation.descriptor); } catch { /* Preserve the fixed diagnostic. */ } + } +} + +/** Executable ESM test seam for deterministic substitution regressions. */ +export const greaterRealmPublicEvidenceTestSeams = Object.freeze({ + write(input: Readonly<{ + repositoryRoot: string; + path: string; + bytes: Buffer; + beforeInstall?: PublicEvidenceWriteInterlock; + }>): void { + writePublicEvidence( + resolvePublicEvidenceDestination(input.repositoryRoot, input.path), + input.bytes, + input.repositoryRoot, + input.beforeInstall, + ); + }, +}); + +async function main(): Promise { + const rawArguments = process.argv.slice(2); + assertGreaterRealmPrivateInvocation(rawArguments, process.env); + const arguments_ = parseArguments(rawArguments); + if (arguments_.command === 'generate-candidates') { + await generateCandidates(arguments_); + return; + } + if (arguments_.command === 'verify-sanitized-review') { + const input = readPublicEvidence(arguments_.inputPath!); + let report: ReturnType; + try { + report = parseGreaterRealmSanitizedReview(JSON.parse(input.toString('utf8'))); + } finally { + input.fill(0); + } + process.stdout.write(`${JSON.stringify({ + candidateCount: report.candidateCount, + selectionStatus: report.selectionStatus, + verified: true, + })}\n`); + return; + } + const workspace = openGreaterRealmPrivateWorkspace({ + repositoryRoot: ROOT, + workspaceRoot: arguments_.workspaceRoot, + }); + if (arguments_.command === 'compare-candidates') { + const batchHandle = arguments_.batchHandle!; + const shortlist = await workspace.withExclusiveLock( + `locks/${batchHandle}.selection.lock`, + async () => { + const { review, verifiedPrivateMetrics } = await verifyPrivateReviewBatch( + workspace, + batchHandle, + ); + const result = buildGreaterRealmPrivateCandidateShortlist( + review, + verifiedPrivateMetrics, + ); + const path = shortlistRelativePath(batchHandle); + if (!workspace.hasFile(path)) writePrivateJson(workspace, path, result); + return result; + }, + ); + process.stdout.write(`${JSON.stringify({ + batchHandle, + shortlistCount: shortlist.shortlistCount, + selectionStatus: shortlist.selectionStatus, + ranked: shortlist.ranked, + automaticSelection: shortlist.automaticSelection, + productionUntouched: true, + })}\n`); + return; + } + if (arguments_.command === 'verify-private-package') { + const { review } = await verifyPrivateReviewBatch( + workspace, + arguments_.batchHandle!, + ); + process.stdout.write(`${JSON.stringify({ + candidateCount: review.candidateCount, + privatePackageVerified: true, + })}\n`); + return; + } + if (arguments_.command === 'export-sanitized-review') { + const { review } = await verifyPrivateReviewBatch(workspace, arguments_.batchHandle!); + const bytes = Buffer.from(serializeGreaterRealmSanitizedReview(review), 'utf8'); + try { + writePublicEvidence( + resolveGreaterRealmPublicEvidenceDestination(arguments_.outputPath!), + bytes, + ); + } finally { + bytes.fill(0); + } + process.stdout.write(`${JSON.stringify({ + candidateCount: review.candidateCount, + selectionStatus: review.selectionStatus, + exported: true, + })}\n`); + return; + } + const batchHandle = arguments_.batchHandle!; + const selectedHandle = await workspace.withExclusiveLock( + `locks/${batchHandle}.selection.lock`, + async () => { + if (workspace.hasFile(selectionRelativePath(batchHandle))) { + fail('GREATER_REALM_SELECTION_INVALID'); + } + const { review } = await verifyPrivateReviewBatch(workspace, batchHandle); + const selected = review.candidates.find(candidate => ( + candidate.candidateHandle === arguments_.candidateHandle && candidate.eligible + )); + if (!selected || review.selectionStatus !== 'pending') { + fail('GREATER_REALM_SELECTION_INVALID'); + } + const selectedReview = createGreaterRealmSanitizedReview({ + generatorVersion: review.generatorVersion, + sourceCommit: review.sourceCommit, + reviewBatchHandle: review.reviewBatchHandle, + selectionStatus: 'selected', + selectedCandidateHandle: selected.candidateHandle, + candidates: review.candidates.map(candidateSource), + }); + writePrivateJson(workspace, selectionRelativePath(batchHandle), { + kind: 'warpkeep.greater-realm.private-owner-selection.v1', + batchHandle, + candidateHandle: arguments_.candidateHandle, + approvalReference: arguments_.approvalReference, + generatorVersion: GREATER_REALM_GENERATOR_VERSION, + sourceReviewDigest: review.reportDigest, + selectedReview, + }); + return selected.candidateHandle; + }, + ); + process.stdout.write(`${JSON.stringify({ + candidateHandle: selectedHandle, + selectionRecorded: true, + productionUntouched: true, + })}\n`); +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + main().catch(error => { + const code = error instanceof Error && /^[A-Z0-9_:-]{3,160}$/u.test(error.message) + ? error.message + : 'GREATER_REALM_CLI_FAILED'; + process.stderr.write(`${code}\n`); + process.exitCode = 1; + }); +} diff --git a/scripts/atlas/greater-realm-contracts.ts b/scripts/atlas/greater-realm-contracts.ts new file mode 100644 index 00000000..6aabf63f --- /dev/null +++ b/scripts/atlas/greater-realm-contracts.ts @@ -0,0 +1,203 @@ +import { randomBytes } from 'node:crypto'; + +export const GREATER_REALM_SANITIZED_REVIEW_SCHEMA = + 'warpkeep.greater-realm.candidate-review.v1' as const; +export const GREATER_REALM_SANITIZED_PRIVACY_BOUNDARY = + 'aggregate-only-no-private-generation-material-v1' as const; +export const GREATER_REALM_MINIMUM_CANDIDATE_COUNT = 8; +export const GREATER_REALM_MAXIMUM_CANDIDATE_COUNT = 16; +export const GREATER_REALM_MINIMUM_ACTIVE_CELL_COUNT = 100_000; +export const GREATER_REALM_MAXIMUM_ACTIVE_CELL_COUNT = 150_000; +export const GREATER_REALM_REQUIRED_GATE_COUNT = 18; +export const GREATER_REALM_REQUIRED_CASTLE_SLOT_COUNT = 600; + +export const GREATER_REALM_CANDIDATE_HANDLE_PATTERN = /^GR-A-[A-Z2-7]{16}$/u; +export const GREATER_REALM_REVIEW_BATCH_HANDLE_PATTERN = /^GR-B-[A-Z2-7]{16}$/u; +export const GREATER_REALM_SOURCE_COMMIT_PATTERN = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/u; +export const GREATER_REALM_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +export const GREATER_REALM_GENERATOR_VERSION_PATTERN = + /^[a-z0-9](?:[a-z0-9.-]{0,62}[a-z0-9])?$/u; + +export const GREATER_REALM_PROOF_KEYS = Object.freeze([ + 'activeMaskConnected', + 'advancedGeomorphology', + 'approvedCellRange', + 'barriersHaveNoBypass', + 'biomeCoherence', + 'biomeDiversity', + 'castleCapacity', + 'deepOceanBoundary', + 'dormantThroneAnchor', + 'gateApproaches', + 'gateGraph', + 'geologicalHighlandBarriers', + 'hydrologyAcyclic', + 'hydrologySurfaceConsistency', + 'legacyLowlandsPreserved', + 'naturalLandmassTopology', + 'naturalStrategicRegions', + 'naturalOuterBoundary', + 'regionPassableLand', + 'regionLandCoherence', + 'regionGraph', +] as const); + +export type GreaterRealmProofKey = typeof GREATER_REALM_PROOF_KEYS[number]; +export type GreaterRealmProofs = Readonly>; + +export type GreaterRealmTierCounts = Readonly<{ + tierI: number; + tierII: number; + tierIII: number; +}>; + +export type GreaterRealmRegionSizeRange = Readonly<{ + minimum: number; + maximum: number; +}>; + +export type GreaterRealmRegionSizeRanges = Readonly<{ + tierI: GreaterRealmRegionSizeRange; + tierII: GreaterRealmRegionSizeRange; + tierIII: GreaterRealmRegionSizeRange; +}>; + +export type GreaterRealmHydrologyCounts = Readonly<{ + majorOceanSeaBodies: number; + majorRivers: number; + minorStreams: number; + lakes: number; +}>; + +export type GreaterRealmGeologyCounts = Readonly<{ + pseudoTectonicDomains: number; + mountainSystems: number; + watersheds: number; +}>; + +/** + * Aggregate terrain-distribution evidence. Elevation and slope values use the + * generator's integer height units; ridge, plateau, and basin counts cover + * land cells, while coast may cover either side of the shore. Counts disclose + * no placement. + */ +export type GreaterRealmTopographyMetrics = Readonly<{ + signedElevationMinimum: number; + signedElevationMaximum: number; + slopeP50: number; + slopeP95: number; + ridgeCellCount: number; + plateauCellCount: number; + basinCellCount: number; + coastCellCount: number; +}>; + +/** Aggregate biome-diversity evidence without class identifiers or locations. */ +export type GreaterRealmBiomeMetrics = Readonly<{ + visualClassCount: number; + minimumPerRegionVisualClassCount: number; + minimumTierIVisualClassCount: number; + minimumTierIIVisualClassCount: number; + tierIIIVisualClassCount: number; + minimumTierIMajorVisualClassCount: number; + minimumTierITransitionVisualClassCount: number; + minimumTierIIMajorVisualClassCount: number; + tierIIIMajorVisualClassCount: number; + maximumTierISingleBiomeShareBasisPoints: number; + incompatibleVisualAdjacencyCount: number; + incompatibleBiomeLandformPairCount: number; +}>; + +export type GreaterRealmQualityScores = Readonly<{ + naturalnessBasisPoints: number; + axialArtifactBasisPoints: number; + ridgeContinuityBasisPoints: number; + hydrologyCoherenceBasisPoints: number; +}>; + +export type GreaterRealmGenerationPerformance = Readonly<{ + generationMillisecondsRounded: number; + processPeakMemoryMiBRounded: number; +}>; + +/** + * Deliberately aggregate-only input accepted by the public-report builder. + * It is not a private package or a redaction target. + */ +export type GreaterRealmSanitizedCandidateSource = Readonly<{ + candidateHandle: string; + eligible: boolean; + activeCellCount: number; + landCellCount: number; + waterCellCount: number; + tierCellCounts: GreaterRealmTierCounts; + regionSizeRanges: GreaterRealmRegionSizeRanges; + hydrology: GreaterRealmHydrologyCounts; + geology: GreaterRealmGeologyCounts; + topography: GreaterRealmTopographyMetrics; + biomes: GreaterRealmBiomeMetrics; + quality: GreaterRealmQualityScores; + gateCount: number; + castleSlotCount: number; + proofs: GreaterRealmProofs; + performance: GreaterRealmGenerationPerformance; +}>; + +export type GreaterRealmSanitizedReviewSource = Readonly<{ + generatorVersion: string; + sourceCommit: string; + reviewBatchHandle: string; + selectionStatus: 'pending' | 'selected'; + selectedCandidateHandle: string | null; + candidates: readonly GreaterRealmSanitizedCandidateSource[]; +}>; + +export type GreaterRealmSanitizedCandidate = GreaterRealmSanitizedCandidateSource & Readonly<{ + insideApprovedRange: boolean; + landBasisPoints: number; + waterBasisPoints: number; + tierBasisPoints: GreaterRealmTierCounts; +}>; + +export type GreaterRealmSanitizedReview = Readonly<{ + schema: typeof GREATER_REALM_SANITIZED_REVIEW_SCHEMA; + generatorVersion: string; + sourceCommit: string; + reviewBatchHandle: string; + selectionStatus: 'pending' | 'selected'; + selectedCandidateHandle: string | null; + candidateCount: number; + candidates: readonly GreaterRealmSanitizedCandidate[]; + privacyBoundary: typeof GREATER_REALM_SANITIZED_PRIVACY_BOUNDARY; + reportDigest: string; +}>; + +const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + +function opaqueHandle(prefix: 'GR-A-' | 'GR-B-'): string { + const bytes = randomBytes(10); + let accumulator = 0; + let availableBits = 0; + let encoded = ''; + for (const byte of bytes) { + accumulator = (accumulator << 8) | byte; + availableBits += 8; + while (availableBits >= 5) { + availableBits -= 5; + encoded += BASE32_ALPHABET[(accumulator >>> availableBits) & 31]; + } + } + bytes.fill(0); + if (encoded.length !== 16) throw new Error('GREATER_REALM_PUBLIC_HANDLE_FAILED'); + return `${prefix}${encoded}`; +} + +/** Public handles are random labels and accept no seed or layout input. */ +export function createGreaterRealmCandidateHandle(): string { + return opaqueHandle('GR-A-'); +} + +/** Public review-batch handles are independent from every candidate seed. */ +export function createGreaterRealmReviewBatchHandle(): string { + return opaqueHandle('GR-B-'); +} diff --git a/scripts/atlas/greater-realm-geomorphology.ts b/scripts/atlas/greater-realm-geomorphology.ts new file mode 100644 index 00000000..968dfe2d --- /dev/null +++ b/scripts/atlas/greater-realm-geomorphology.ts @@ -0,0 +1,1056 @@ +import { + accumulateGreaterRealmSingleFlow, + createGreaterRealmMultiscaleIntegerField, + greaterRealmCounterRandomU32, + greaterRealmTerrainChannelId, + priorityFloodGreaterRealmHexGrid, + routeGreaterRealmSingleFlow, + type GreaterRealmTerrainSeed, + type IndexedAxialGrid, +} from './greater-realm-terrain'; + +export const GREATER_REALM_GEOMORPHOLOGY_VERSION = + 'greater-realm-geomorphology-v1' as const; + +export const GREATER_REALM_COASTAL_CLASS = Object.freeze({ + none: 0, + beachShelf: 1, + seaCliff: 2, + deltaEstuary: 3, + glacialFjord: 4, +} as const); + +const NEIGHBOR_COUNT = 6; +const DISTANCE_UNREACHABLE = 0xffff; +const MAX_PROCESS_CELL_DELTA = 2_400; +// Four independently conserved/endogenic process deltas may meet at a river +// mouth. The combined ceiling is still small relative to the 60k-height atlas +// range, while avoiding order-dependent clipping that would break budgets. +const MAX_TOTAL_CELL_DELTA = 8_192; +const MIN_GLACIAL_SYSTEM_CELLS = 6; +const MIN_ARID_SYSTEM_CELLS = 8; +const MAX_DEPOSITION_SEARCH_STEPS = 32; + +export type GreaterRealmGeomorphologyClimate = Readonly<{ + temperature: Int32Array; + moisture: Int32Array; +}>; + +export type GreaterRealmGeomorphologyProcessMetrics = Readonly<{ + sourceCellCount: number; + changedCellCount: number; + systemCount: number; + minimumSystemCellCount: number; + erodedMaterialUnits: number; + depositedMaterialUnits: number; + exportedMaterialUnits: number; +}>; + +export type GreaterRealmGeomorphologyMetrics = Readonly<{ + changedCellCount: number; + maximumAbsoluteCellDelta: number; + protectedCellCount: number; + protectedChangedCellCount: number; + erodedMaterialUnits: number; + depositedMaterialUnits: number; + exportedMaterialUnits: number; + endogenicUpliftUnits: number; + aeolianMovedMaterialUnits: number; + glacialClimateCompatibilityBasisPoints: number; + aridClimateCompatibilityBasisPoints: number; + volcanicTectonicCompatibilityBasisPoints: number; + coastalProximityCompatibilityBasisPoints: number; + coastalClassCount: number; + volcanicAnchorCount: number; + ridgeUpliftAlignmentBasisPoints: number; + riverValleyAlignmentBasisPoints: number; + glacial: GreaterRealmGeomorphologyProcessMetrics; + arid: GreaterRealmGeomorphologyProcessMetrics; + coastal: GreaterRealmGeomorphologyProcessMetrics; +}>; + +export type GreaterRealmGeomorphologyResult = Readonly<{ + elevation: Int32Array; + temperature: Int32Array; + moisture: Int32Array; + totalDelta: Int32Array; + glacialDelta: Int32Array; + aridDelta: Int32Array; + volcanicDelta: Int32Array; + coastalDelta: Int32Array; + glacialMask: Uint8Array; + aridMask: Uint8Array; + volcanicMask: Uint8Array; + volcanicAnchorMask: Uint8Array; + coastalMask: Uint8Array; + coastalClass: Uint8Array; + metrics: GreaterRealmGeomorphologyMetrics; +}>; + +type PreliminaryHydrology = Readonly<{ + receiver: Int32Array; + accumulation: BigUint64Array; +}>; + +type ComponentMetrics = Readonly<{ + count: number; + minimum: number; +}>; + +type MaterialBudget = Readonly<{ + eroded: number; + deposited: number; + exported: number; +}>; + +function fail(code: string): never { + throw new Error(code); +} + +function clamp(value: number, minimum: number, maximum: number): number { + return Math.max(minimum, Math.min(maximum, value)); +} + +function safeAdd(first: number, second: number): number { + const value = first + second; + if (!Number.isSafeInteger(value)) fail('GREATER_REALM_GEOMORPHOLOGY_METRIC_OVERFLOW'); + return value; +} + +function assertInputs(input: Readonly<{ + grid: IndexedAxialGrid; + elevation: Int32Array; + tectonicUplift: Int32Array; + rockResistance: Int32Array; + volcanicPotential: Int32Array; + legacyReserveCell: Uint8Array; + climate?: GreaterRealmGeomorphologyClimate; + seaLevel: number; +}>): void { + const { grid } = input; + if (!Number.isSafeInteger(input.seaLevel)) { + fail('GREATER_REALM_GEOMORPHOLOGY_SEA_LEVEL_INVALID'); + } + for (const field of [ + input.elevation, + input.tectonicUplift, + input.rockResistance, + input.volcanicPotential, + input.legacyReserveCell, + input.climate?.temperature, + input.climate?.moisture, + ]) { + if (field !== undefined && field.length !== grid.cellCount) { + fail('GREATER_REALM_GEOMORPHOLOGY_INPUT_LENGTH_INVALID'); + } + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (input.legacyReserveCell[cell]! > 1) { + fail('GREATER_REALM_GEOMORPHOLOGY_RESERVE_MASK_INVALID'); + } + if ( + input.rockResistance[cell]! < 0 + || input.rockResistance[cell]! > 20_000 + || input.volcanicPotential[cell]! < 0 + || input.volcanicPotential[cell]! > 10_000 + ) fail('GREATER_REALM_GEOMORPHOLOGY_GEOLOGY_FIELD_INVALID'); + } +} + +function axialDistance( + firstQ: number, + firstR: number, + secondQ: number, + secondR: number, +): number { + const q = firstQ - secondQ; + const r = firstR - secondR; + return Math.max(Math.abs(q), Math.abs(r), Math.abs(-q - r)); +} + +function maximumNeighborDrop(grid: IndexedAxialGrid, elevation: Int32Array): Uint16Array { + const slope = new Uint16Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + let maximum = 0; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0) continue; + maximum = Math.max(maximum, Math.abs(elevation[cell]! - elevation[neighbor]!)); + } + slope[cell] = clamp(maximum, 0, 0xffff); + } + return slope; +} + +function maskDistances(grid: IndexedAxialGrid, starts: Uint8Array): Uint16Array { + const distance = new Uint16Array(grid.cellCount); + distance.fill(DISTANCE_UNREACHABLE); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (starts[cell] !== 1) continue; + distance[cell] = 0; + queue[tail++] = cell; + } + while (head < tail) { + const cell = queue[head++]!; + const nextDistance = distance[cell]! + 1; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || distance[neighbor]! <= nextDistance) continue; + distance[neighbor] = nextDistance; + queue[tail++] = neighbor; + } + } + return distance; +} + +function componentFilteredMask( + grid: IndexedAxialGrid, + candidates: Uint8Array, + minimumSize: number, +): Readonly<{ mask: Uint8Array; metrics: ComponentMetrics }> { + const mask = new Uint8Array(grid.cellCount); + const seen = new Uint8Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + let count = 0; + let minimum = Number.POSITIVE_INFINITY; + for (let start = 0; start < grid.cellCount; start += 1) { + if (candidates[start] !== 1 || seen[start] === 1) continue; + let head = 0; + let tail = 0; + queue[tail++] = start; + seen[start] = 1; + while (head < tail) { + const cell = queue[head++]!; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || candidates[neighbor] !== 1 || seen[neighbor] === 1) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + } + } + if (tail < minimumSize) continue; + count += 1; + minimum = Math.min(minimum, tail); + for (let index = 0; index < tail; index += 1) mask[queue[index]!] = 1; + } + return Object.freeze({ + mask, + metrics: Object.freeze({ count, minimum: count === 0 ? 0 : minimum }), + }); +} + +function componentMetrics(grid: IndexedAxialGrid, mask: Uint8Array): ComponentMetrics { + return componentFilteredMask(grid, mask, 1).metrics; +} + +function preliminaryHydrology( + grid: IndexedAxialGrid, + elevation: Int32Array, + seaLevel: number, +): PreliminaryHydrology { + const outlets: number[] = []; + const contribution = new Uint32Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (elevation[cell]! <= seaLevel) outlets.push(cell); + else contribution[cell] = 1; + } + if (outlets.length === 0) fail('GREATER_REALM_GEOMORPHOLOGY_OCEAN_MISSING'); + const flood = priorityFloodGreaterRealmHexGrid(grid, elevation, outlets); + const routing = routeGreaterRealmSingleFlow(grid, flood); + return Object.freeze({ + receiver: routing.receiver, + accumulation: accumulateGreaterRealmSingleFlow( + grid, + flood.filledElevation, + routing, + contribution, + ), + }); +} + +function accumulationMagnitude(value: bigint): number { + let magnitude = 0; + for (let cursor = value; cursor > 1n; cursor >>= 1n) magnitude += 1; + return magnitude; +} + +function deriveClimate(input: Readonly<{ + grid: IndexedAxialGrid; + candidateSeed: GreaterRealmTerrainSeed; + elevation: Int32Array; + seaLevel: number; + coastDistance: Uint16Array; + slope: Uint16Array; + accumulation: BigUint64Array; +}>): GreaterRealmGeomorphologyClimate { + const { grid, candidateSeed, elevation, seaLevel, coastDistance, slope, accumulation } = input; + const equatorOffset = ( + greaterRealmCounterRandomU32( + candidateSeed, + greaterRealmTerrainChannelId('geomorphology-equator-offset'), + 0, + 0, + ) % 41 + ) - 20; + const temperatureNoise = createGreaterRealmMultiscaleIntegerField(grid, candidateSeed, [ + { channel: 'geomorphology-temperature-macro', amplitude: 1_100, smoothingPasses: 12, selfWeight: 3 }, + ]); + const moistureNoise = createGreaterRealmMultiscaleIntegerField(grid, candidateSeed, [ + { channel: 'geomorphology-moisture-macro', amplitude: 4_800, smoothingPasses: 16, selfWeight: 3 }, + { channel: 'geomorphology-moisture-meso', amplitude: 1_400, smoothingPasses: 4, selfWeight: 2 }, + ]); + const temperature = new Int32Array(grid.cellCount); + const moisture = new Int32Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + temperature[cell] = clamp( + 8_500 + - Math.abs(grid.r[cell]! - equatorOffset) * 26 + - Math.floor(Math.max(0, elevation[cell]! - seaLevel) / 5) + + temperatureNoise[cell]!, + -8_000, + 12_000, + ); + const drainage = Math.min(4_200, accumulationMagnitude(accumulation[cell]!) * 360); + moisture[cell] = clamp( + moistureNoise[cell]! + + Math.max(0, 1_500 - coastDistance[cell]! * 90) + + drainage + - Math.floor(slope[cell]! / 4), + -10_000, + 16_000, + ); + } + return Object.freeze({ temperature, moisture }); +} + +function countMask(mask: Uint8Array): number { + let count = 0; + for (const value of mask) count += value === 1 ? 1 : 0; + return count; +} + +function changedMask(delta: Int32Array): Uint8Array { + const mask = new Uint8Array(delta.length); + for (let cell = 0; cell < delta.length; cell += 1) { + if (delta[cell] !== 0) mask[cell] = 1; + } + return mask; +} + +function downstreamDepositionTarget( + start: number, + receiver: Int32Array, + sourceMask: Uint8Array, + reserveMask: Uint8Array, +): number { + let cursor = start; + for (let step = 0; step < MAX_DEPOSITION_SEARCH_STEPS; step += 1) { + const downstream = receiver[cursor]!; + if (downstream < 0) return -1; + cursor = downstream; + if (sourceMask[cursor] === 0 && reserveMask[cursor] === 0) return cursor; + } + return -1; +} + +function applyConservedErosion(input: Readonly<{ + erosion: Uint16Array; + delta: Int32Array; + receiver: Int32Array; + sourceMask: Uint8Array; + reserveMask: Uint8Array; +}>): MaterialBudget { + let eroded = 0; + let deposited = 0; + let exported = 0; + for (let cell = 0; cell < input.erosion.length; cell += 1) { + const amount = input.erosion[cell]!; + if (amount === 0) continue; + if (input.reserveMask[cell] === 1) fail('GREATER_REALM_GEOMORPHOLOGY_PROTECTED_EDIT'); + input.delta[cell] -= amount; + eroded = safeAdd(eroded, amount); + const target = downstreamDepositionTarget( + cell, + input.receiver, + input.sourceMask, + input.reserveMask, + ); + if (target < 0) { + exported = safeAdd(exported, amount); + continue; + } + const capacity = Math.max(0, MAX_PROCESS_CELL_DELTA - input.delta[target]!); + const placed = Math.min(amount, capacity); + input.delta[target] += placed; + deposited = safeAdd(deposited, placed); + exported = safeAdd(exported, amount - placed); + } + if (eroded !== deposited + exported) fail('GREATER_REALM_GEOMORPHOLOGY_MATERIAL_BUDGET_INVALID'); + return Object.freeze({ eroded, deposited, exported }); +} + +function addErosion(erosion: Uint16Array, cell: number, amount: number): void { + erosion[cell] = clamp(erosion[cell]! + amount, 0, 900); +} + +function glacialProcess(input: Readonly<{ + grid: IndexedAxialGrid; + elevation: Int32Array; + seaLevel: number; + slope: Uint16Array; + temperature: Int32Array; + accumulation: BigUint64Array; + receiver: Int32Array; + reserveMask: Uint8Array; +}>): Readonly<{ + mask: Uint8Array; + delta: Int32Array; + components: ComponentMetrics; + budget: MaterialBudget; +}> { + const candidates = new Uint8Array(input.grid.cellCount); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + input.reserveMask[cell] === 0 + && input.elevation[cell]! > input.seaLevel + 4_500 + && input.temperature[cell]! <= 2_000 + && (input.slope[cell]! >= 450 || input.accumulation[cell]! >= 6n) + ) candidates[cell] = 1; + } + const coherent = componentFilteredMask(input.grid, candidates, MIN_GLACIAL_SYSTEM_CELLS); + const erosion = new Uint16Array(input.grid.cellCount); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (coherent.mask[cell] !== 1) continue; + const amount = clamp( + 50 + accumulationMagnitude(input.accumulation[cell]!) * 18 + + Math.floor(input.slope[cell]! / 20), + 50, + 300, + ); + addErosion(erosion, cell, amount); + const downstream = input.receiver[cell]!; + if (downstream < 0) continue; + let flowDirection = -1; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + if (input.grid.neighbors[cell * NEIGHBOR_COUNT + direction] === downstream) { + flowDirection = direction; + break; + } + } + if (flowDirection < 0) continue; + for (const offset of [2, 4] as const) { + const lateral = input.grid.neighbors[ + cell * NEIGHBOR_COUNT + ((flowDirection + offset) % NEIGHBOR_COUNT) + ]!; + if (lateral >= 0 && coherent.mask[lateral] === 1) { + addErosion(erosion, lateral, Math.max(20, Math.floor(amount / 3))); + } + } + } + const delta = new Int32Array(input.grid.cellCount); + const budget = applyConservedErosion({ + erosion, + delta, + receiver: input.receiver, + sourceMask: coherent.mask, + reserveMask: input.reserveMask, + }); + return Object.freeze({ mask: coherent.mask, delta, components: coherent.metrics, budget }); +} + +function aridProcess(input: Readonly<{ + grid: IndexedAxialGrid; + candidateSeed: GreaterRealmTerrainSeed; + elevation: Int32Array; + seaLevel: number; + slope: Uint16Array; + temperature: Int32Array; + moisture: Int32Array; + accumulation: BigUint64Array; + receiver: Int32Array; + rockResistance: Int32Array; + reserveMask: Uint8Array; +}>): Readonly<{ + mask: Uint8Array; + delta: Int32Array; + components: ComponentMetrics; + budget: MaterialBudget; + aeolianMovedMaterialUnits: number; +}> { + const candidates = new Uint8Array(input.grid.cellCount); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + input.reserveMask[cell] === 0 + && input.elevation[cell]! > input.seaLevel + && input.temperature[cell]! >= 5_500 + && input.moisture[cell]! <= -1_000 + ) candidates[cell] = 1; + } + const coherent = componentFilteredMask(input.grid, candidates, MIN_ARID_SYSTEM_CELLS); + const erosion = new Uint16Array(input.grid.cellCount); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (coherent.mask[cell] !== 1) continue; + const discharge = accumulationMagnitude(input.accumulation[cell]!); + const differentialWeathering = Math.max(0, 7_000 - input.rockResistance[cell]!); + if (input.accumulation[cell]! >= 4n || input.slope[cell]! >= 320 || differentialWeathering > 1_500) { + addErosion(erosion, cell, clamp( + 25 + discharge * 16 + Math.floor(input.slope[cell]! / 28) + + Math.floor(differentialWeathering / 90), + 25, + 240, + )); + } + } + const delta = new Int32Array(input.grid.cellCount); + const budget = applyConservedErosion({ + erosion, + delta, + receiver: input.receiver, + sourceMask: coherent.mask, + reserveMask: input.reserveMask, + }); + const duneChannel = greaterRealmTerrainChannelId('geomorphology-aeolian-dunes'); + const windDirection = greaterRealmCounterRandomU32( + input.candidateSeed, + duneChannel, + 0, + 0, + ) % NEIGHBOR_COUNT; + let aeolianMovedMaterialUnits = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + coherent.mask[cell] !== 1 + || input.slope[cell]! > 550 + || input.accumulation[cell]! > 6n + ) continue; + const random = greaterRealmCounterRandomU32( + input.candidateSeed, + duneChannel, + input.grid.q[cell]!, + input.grid.r[cell]!, + 1, + ); + if (random % 5 !== 0) continue; + const target = input.grid.neighbors[cell * NEIGHBOR_COUNT + windDirection]!; + if (target <= cell || target < 0 || coherent.mask[target] !== 1) continue; + const wanted = 30 + (random % 51); + const sourceCapacity = MAX_PROCESS_CELL_DELTA + delta[cell]!; + const targetCapacity = MAX_PROCESS_CELL_DELTA - delta[target]!; + const moved = Math.max(0, Math.min(wanted, sourceCapacity, targetCapacity)); + if (moved === 0) continue; + delta[cell] -= moved; + delta[target] += moved; + aeolianMovedMaterialUnits = safeAdd(aeolianMovedMaterialUnits, moved); + } + return Object.freeze({ + mask: coherent.mask, + delta, + components: coherent.metrics, + budget, + aeolianMovedMaterialUnits, + }); +} + +function volcanicProcess(input: Readonly<{ + grid: IndexedAxialGrid; + candidateSeed: GreaterRealmTerrainSeed; + elevation: Int32Array; + seaLevel: number; + tectonicUplift: Int32Array; + volcanicPotential: Int32Array; + reserveMask: Uint8Array; +}>): Readonly<{ + mask: Uint8Array; + anchorMask: Uint8Array; + delta: Int32Array; + anchorCount: number; + upliftUnits: number; +}> { + const channel = greaterRealmTerrainChannelId('geomorphology-volcanic-centers'); + const candidates: Array> = []; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + input.reserveMask[cell] === 1 + || input.elevation[cell]! <= input.seaLevel + || input.volcanicPotential[cell]! < 7_000 + || input.tectonicUplift[cell]! < 2_500 + ) continue; + let localMaximum = true; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && ( + input.volcanicPotential[neighbor]! > input.volcanicPotential[cell]! + || ( + input.volcanicPotential[neighbor] === input.volcanicPotential[cell] + && neighbor < cell + ) + ) + ) { + localMaximum = false; + break; + } + } + if (!localMaximum) continue; + candidates.push(Object.freeze({ + cell, + score: input.volcanicPotential[cell]! * 4 + + input.tectonicUplift[cell]! + + (greaterRealmCounterRandomU32( + input.candidateSeed, + channel, + input.grid.q[cell]!, + input.grid.r[cell]!, + ) % 501), + })); + } + candidates.sort((first, second) => second.score - first.score || first.cell - second.cell); + const maximumAnchors = clamp(Math.floor(input.grid.cellCount / 25_000) + 2, 2, 8); + const anchors: number[] = []; + for (const candidate of candidates) { + if (anchors.some(anchor => axialDistance( + input.grid.q[candidate.cell]!, + input.grid.r[candidate.cell]!, + input.grid.q[anchor]!, + input.grid.r[anchor]!, + ) < 14)) continue; + anchors.push(candidate.cell); + if (anchors.length >= maximumAnchors) break; + } + const delta = new Int32Array(input.grid.cellCount); + const anchorMask = new Uint8Array(input.grid.cellCount); + for (const anchor of anchors) { + anchorMask[anchor] = 1; + const random = greaterRealmCounterRandomU32( + input.candidateSeed, + channel, + input.grid.q[anchor]!, + input.grid.r[anchor]!, + 1, + ); + const radius = 4 + (random % 4); + const peak = 800 + (random % 801); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (input.reserveMask[cell] === 1 || input.elevation[cell]! <= input.seaLevel) continue; + const distance = axialDistance( + input.grid.q[cell]!, + input.grid.r[cell]!, + input.grid.q[anchor]!, + input.grid.r[anchor]!, + ); + if (distance > radius) continue; + let uplift = Math.floor((peak * (radius - distance + 1)) / (radius + 1)); + if (distance === 0) uplift = Math.max(80, Math.floor(uplift / 3)); + else if (distance === 1) uplift = Math.max(60, Math.floor(uplift / 2)); + delta[cell] = Math.min(MAX_PROCESS_CELL_DELTA, delta[cell]! + uplift); + } + } + const mask = changedMask(delta); + let upliftUnits = 0; + for (const value of delta) upliftUnits = safeAdd(upliftUnits, Math.max(0, value)); + return Object.freeze({ + mask, + anchorMask, + delta, + anchorCount: anchors.length, + upliftUnits, + }); +} + +function coastalProcess(input: Readonly<{ + grid: IndexedAxialGrid; + elevation: Int32Array; + seaLevel: number; + slope: Uint16Array; + temperature: Int32Array; + accumulation: BigUint64Array; + tectonicUplift: Int32Array; + rockResistance: Int32Array; + glacialMask: Uint8Array; + reserveMask: Uint8Array; +}>): Readonly<{ + mask: Uint8Array; + coastalClass: Uint8Array; + delta: Int32Array; + sourceMask: Uint8Array; + components: ComponentMetrics; + classCount: number; + budget: MaterialBudget; +}> { + const glacialDistance = maskDistances(input.grid, input.glacialMask); + const sourceMask = new Uint8Array(input.grid.cellCount); + const coastalClass = new Uint8Array(input.grid.cellCount); + const delta = new Int32Array(input.grid.cellCount); + let eroded = 0; + let deposited = 0; + let exported = 0; + const classes = new Set(); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (input.reserveMask[cell] === 1 || input.elevation[cell]! <= input.seaLevel) continue; + const seaNeighbors: number[] = []; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && input.reserveMask[neighbor] === 0 + && input.elevation[neighbor]! <= input.seaLevel + ) seaNeighbors.push(neighbor); + } + if (seaNeighbors.length === 0) continue; + sourceMask[cell] = 1; + let classification: number; + if ( + glacialDistance[cell]! <= 4 + && input.temperature[cell]! <= 2_000 + && input.slope[cell]! >= 500 + ) classification = GREATER_REALM_COASTAL_CLASS.glacialFjord; + else if (input.accumulation[cell]! >= 16n && input.slope[cell]! <= 1_200) { + classification = GREATER_REALM_COASTAL_CLASS.deltaEstuary; + } else if ( + input.rockResistance[cell]! >= 6_000 + && (input.tectonicUplift[cell]! >= 2_500 || input.slope[cell]! >= 900) + ) classification = GREATER_REALM_COASTAL_CLASS.seaCliff; + else classification = GREATER_REALM_COASTAL_CLASS.beachShelf; + coastalClass[cell] = classification; + classes.add(classification); + const amount = classification === GREATER_REALM_COASTAL_CLASS.glacialFjord ? 160 + : classification === GREATER_REALM_COASTAL_CLASS.deltaEstuary ? 90 + : classification === GREATER_REALM_COASTAL_CLASS.seaCliff + ? clamp(120 - Math.floor(input.rockResistance[cell]! / 120), 35, 80) + : 45; + delta[cell] -= amount; + eroded = safeAdd(eroded, amount); + seaNeighbors.sort((first, second) => ( + input.elevation[first]! - input.elevation[second]! || first - second + )); + const target = seaNeighbors[0]!; + const capacity = Math.max(0, MAX_PROCESS_CELL_DELTA - delta[target]!); + const placed = Math.min(amount, capacity); + delta[target] += placed; + deposited = safeAdd(deposited, placed); + exported = safeAdd(exported, amount - placed); + if (coastalClass[target] === GREATER_REALM_COASTAL_CLASS.none) { + coastalClass[target] = GREATER_REALM_COASTAL_CLASS.beachShelf; + } + } + if (eroded !== deposited + exported) fail('GREATER_REALM_GEOMORPHOLOGY_MATERIAL_BUDGET_INVALID'); + const mask = changedMask(delta); + return Object.freeze({ + mask, + coastalClass, + delta, + sourceMask, + components: componentMetrics(input.grid, sourceMask), + classCount: classes.size, + budget: Object.freeze({ eroded, deposited, exported }), + }); +} + +function compatibilityBasisPoints( + mask: Uint8Array, + predicate: (cell: number) => boolean, +): number { + let total = 0; + let compatible = 0; + for (let cell = 0; cell < mask.length; cell += 1) { + if (mask[cell] !== 1) continue; + total += 1; + compatible += predicate(cell) ? 1 : 0; + } + return total === 0 ? 0 : Math.round((compatible * 10_000) / total); +} + +function alignmentMetrics(input: Readonly<{ + grid: IndexedAxialGrid; + elevation: Int32Array; + seaLevel: number; + tectonicUplift: Int32Array; + volcanicMask: Uint8Array; + reserveMask: Uint8Array; +}>): Readonly<{ + ridgeUpliftAlignmentBasisPoints: number; + riverValleyAlignmentBasisPoints: number; +}> { + const slope = maximumNeighborDrop(input.grid, input.elevation); + let ridgeCount = 0; + let alignedRidgeCount = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (input.elevation[cell]! <= input.seaLevel || input.reserveMask[cell] === 1) continue; + let neighborSum = 0; + let neighborCount = 0; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0) continue; + neighborSum += input.elevation[neighbor]!; + neighborCount += 1; + } + const exposure = neighborCount === 0 + ? 0 + : input.elevation[cell]! - Math.round(neighborSum / neighborCount); + if (slope[cell]! < 900 || exposure < 300) continue; + ridgeCount += 1; + if (input.tectonicUplift[cell]! >= 2_500 || input.volcanicMask[cell] === 1) { + alignedRidgeCount += 1; + } + } + + const hydrology = preliminaryHydrology(input.grid, input.elevation, input.seaLevel); + let riverCount = 0; + let alignedRiverCount = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + input.reserveMask[cell] === 1 + || input.elevation[cell]! <= input.seaLevel + || hydrology.accumulation[cell]! < 24n + ) continue; + const receiver = hydrology.receiver[cell]!; + if (receiver < 0) continue; + let crossSlopeSum = 0; + let crossSlopeCount = 0; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || neighbor === receiver || hydrology.receiver[neighbor] === cell) continue; + crossSlopeSum += input.elevation[neighbor]!; + crossSlopeCount += 1; + } + if (crossSlopeCount === 0) continue; + riverCount += 1; + if (input.elevation[cell]! <= Math.round(crossSlopeSum / crossSlopeCount) + 150) { + alignedRiverCount += 1; + } + } + return Object.freeze({ + ridgeUpliftAlignmentBasisPoints: ridgeCount === 0 + ? 0 + : Math.round((alignedRidgeCount * 10_000) / ridgeCount), + riverValleyAlignmentBasisPoints: riverCount === 0 + ? 0 + : Math.round((alignedRiverCount * 10_000) / riverCount), + }); +} + +/** + * Apply bounded, deterministic geomorphic processes before the final fluvial + * pass. This stage changes private elevation authority; labels and biomes must + * be derived only after final hydrology is recomputed by the caller. + */ +export function shapeGreaterRealmGeomorphology(input: Readonly<{ + grid: IndexedAxialGrid; + candidateSeed: GreaterRealmTerrainSeed; + elevation: Int32Array; + tectonicUplift: Int32Array; + rockResistance: Int32Array; + volcanicPotential: Int32Array; + legacyReserveCell: Uint8Array; + seaLevel?: number; + climate?: GreaterRealmGeomorphologyClimate; +}>): GreaterRealmGeomorphologyResult { + const seaLevel = input.seaLevel ?? 0; + assertInputs({ ...input, seaLevel }); + const initialElevation = new Int32Array(input.elevation); + const slope = maximumNeighborDrop(input.grid, initialElevation); + const seaMask = new Uint8Array(input.grid.cellCount); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (initialElevation[cell]! <= seaLevel) seaMask[cell] = 1; + } + const coastDistance = maskDistances(input.grid, seaMask); + if (coastDistance.every(distance => distance === DISTANCE_UNREACHABLE)) { + fail('GREATER_REALM_GEOMORPHOLOGY_OCEAN_MISSING'); + } + const hydrology = preliminaryHydrology(input.grid, initialElevation, seaLevel); + const climate = input.climate === undefined + ? deriveClimate({ + grid: input.grid, + candidateSeed: input.candidateSeed, + elevation: initialElevation, + seaLevel, + coastDistance, + slope, + accumulation: hydrology.accumulation, + }) + : Object.freeze({ + temperature: new Int32Array(input.climate.temperature), + moisture: new Int32Array(input.climate.moisture), + }); + + const glacial = glacialProcess({ + grid: input.grid, + elevation: initialElevation, + seaLevel, + slope, + temperature: climate.temperature, + accumulation: hydrology.accumulation, + receiver: hydrology.receiver, + reserveMask: input.legacyReserveCell, + }); + const arid = aridProcess({ + grid: input.grid, + candidateSeed: input.candidateSeed, + elevation: initialElevation, + seaLevel, + slope, + temperature: climate.temperature, + moisture: climate.moisture, + accumulation: hydrology.accumulation, + receiver: hydrology.receiver, + rockResistance: input.rockResistance, + reserveMask: input.legacyReserveCell, + }); + const volcanic = volcanicProcess({ + grid: input.grid, + candidateSeed: input.candidateSeed, + elevation: initialElevation, + seaLevel, + tectonicUplift: input.tectonicUplift, + volcanicPotential: input.volcanicPotential, + reserveMask: input.legacyReserveCell, + }); + const coastal = coastalProcess({ + grid: input.grid, + elevation: initialElevation, + seaLevel, + slope, + temperature: climate.temperature, + accumulation: hydrology.accumulation, + tectonicUplift: input.tectonicUplift, + rockResistance: input.rockResistance, + glacialMask: glacial.mask, + reserveMask: input.legacyReserveCell, + }); + + const totalDelta = new Int32Array(input.grid.cellCount); + const elevation = new Int32Array(input.grid.cellCount); + let changedCellCount = 0; + let maximumAbsoluteCellDelta = 0; + let protectedCellCount = 0; + let protectedChangedCellCount = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + const delta = glacial.delta[cell]! + arid.delta[cell]! + volcanic.delta[cell]! + + coastal.delta[cell]!; + if (!Number.isSafeInteger(delta) || Math.abs(delta) > MAX_TOTAL_CELL_DELTA) { + fail('GREATER_REALM_GEOMORPHOLOGY_DELTA_OUT_OF_RANGE'); + } + totalDelta[cell] = delta; + const nextElevation = initialElevation[cell]! + delta; + if (nextElevation < -0x8000_0000 || nextElevation > 0x7fff_ffff) { + fail('GREATER_REALM_GEOMORPHOLOGY_ELEVATION_OVERFLOW'); + } + elevation[cell] = nextElevation; + if (delta !== 0) changedCellCount += 1; + maximumAbsoluteCellDelta = Math.max(maximumAbsoluteCellDelta, Math.abs(delta)); + if (input.legacyReserveCell[cell] === 1) { + protectedCellCount += 1; + if (delta !== 0) protectedChangedCellCount += 1; + } + } + if (protectedChangedCellCount !== 0) fail('GREATER_REALM_GEOMORPHOLOGY_PROTECTED_EDIT'); + + const erodedMaterialUnits = safeAdd( + safeAdd(glacial.budget.eroded, arid.budget.eroded), + coastal.budget.eroded, + ); + const depositedMaterialUnits = safeAdd( + safeAdd(glacial.budget.deposited, arid.budget.deposited), + coastal.budget.deposited, + ); + const exportedMaterialUnits = safeAdd( + safeAdd(glacial.budget.exported, arid.budget.exported), + coastal.budget.exported, + ); + if (erodedMaterialUnits !== depositedMaterialUnits + exportedMaterialUnits) { + fail('GREATER_REALM_GEOMORPHOLOGY_MATERIAL_BUDGET_INVALID'); + } + const alignment = alignmentMetrics({ + grid: input.grid, + elevation, + seaLevel, + tectonicUplift: input.tectonicUplift, + volcanicMask: volcanic.mask, + reserveMask: input.legacyReserveCell, + }); + const coastalSourceMask = coastal.sourceMask; + const glacialChanged = changedMask(glacial.delta); + const aridChanged = changedMask(arid.delta); + const coastalChanged = changedMask(coastal.delta); + + return Object.freeze({ + elevation, + temperature: climate.temperature, + moisture: climate.moisture, + totalDelta, + glacialDelta: glacial.delta, + aridDelta: arid.delta, + volcanicDelta: volcanic.delta, + coastalDelta: coastal.delta, + glacialMask: glacial.mask, + aridMask: arid.mask, + volcanicMask: volcanic.mask, + volcanicAnchorMask: volcanic.anchorMask, + coastalMask: coastal.mask, + coastalClass: coastal.coastalClass, + metrics: Object.freeze({ + changedCellCount, + maximumAbsoluteCellDelta, + protectedCellCount, + protectedChangedCellCount, + erodedMaterialUnits, + depositedMaterialUnits, + exportedMaterialUnits, + endogenicUpliftUnits: volcanic.upliftUnits, + aeolianMovedMaterialUnits: arid.aeolianMovedMaterialUnits, + glacialClimateCompatibilityBasisPoints: compatibilityBasisPoints( + glacial.mask, + cell => climate.temperature[cell]! <= 2_000 + && initialElevation[cell]! > seaLevel + 4_500, + ), + aridClimateCompatibilityBasisPoints: compatibilityBasisPoints( + arid.mask, + cell => climate.temperature[cell]! >= 5_500 && climate.moisture[cell]! <= -1_000, + ), + volcanicTectonicCompatibilityBasisPoints: compatibilityBasisPoints( + volcanic.anchorMask, + cell => input.volcanicPotential[cell]! >= 7_000 + && input.tectonicUplift[cell]! >= 2_500, + ), + coastalProximityCompatibilityBasisPoints: compatibilityBasisPoints( + coastalSourceMask, + cell => coastDistance[cell] === 1, + ), + coastalClassCount: coastal.classCount, + volcanicAnchorCount: volcanic.anchorCount, + ridgeUpliftAlignmentBasisPoints: alignment.ridgeUpliftAlignmentBasisPoints, + riverValleyAlignmentBasisPoints: alignment.riverValleyAlignmentBasisPoints, + glacial: Object.freeze({ + sourceCellCount: countMask(glacial.mask), + changedCellCount: countMask(glacialChanged), + systemCount: glacial.components.count, + minimumSystemCellCount: glacial.components.minimum, + erodedMaterialUnits: glacial.budget.eroded, + depositedMaterialUnits: glacial.budget.deposited, + exportedMaterialUnits: glacial.budget.exported, + }), + arid: Object.freeze({ + sourceCellCount: countMask(arid.mask), + changedCellCount: countMask(aridChanged), + systemCount: arid.components.count, + minimumSystemCellCount: arid.components.minimum, + erodedMaterialUnits: arid.budget.eroded, + depositedMaterialUnits: arid.budget.deposited, + exportedMaterialUnits: arid.budget.exported, + }), + coastal: Object.freeze({ + sourceCellCount: countMask(coastalSourceMask), + changedCellCount: countMask(coastalChanged), + systemCount: coastal.components.count, + minimumSystemCellCount: coastal.components.minimum, + erodedMaterialUnits: coastal.budget.eroded, + depositedMaterialUnits: coastal.budget.deposited, + exportedMaterialUnits: coastal.budget.exported, + }), + }), + }); +} diff --git a/scripts/atlas/greater-realm-git.ts b/scripts/atlas/greater-realm-git.ts new file mode 100644 index 00000000..ef06e6d1 --- /dev/null +++ b/scripts/atlas/greater-realm-git.ts @@ -0,0 +1,263 @@ +import { createHash } from 'node:crypto'; +import { spawnSync, type SpawnSyncReturns } from 'node:child_process'; +import { + closeSync, + constants, + existsSync, + fstatSync, + lstatSync, + openSync, + readSync, + realpathSync, +} from 'node:fs'; +import { dirname, isAbsolute, parse, relative, resolve, sep } from 'node:path'; + +const SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const MAXIMUM_ATTESTED_BINARY_BYTES = 512 * 1024 * 1024; +const NULL_DEVICE = process.platform === 'win32' ? 'NUL' : '/dev/null'; + +export type GreaterRealmTrustedGitAttestation = Readonly<{ + binaryPath: string; + binarySha256: string; + execPath: string; + version: string; +}>; + +function fail(): never { + throw new Error('GREATER_REALM_TOOLCHAIN_INVALID'); +} + +function pathInside(parent: string, candidate: string): boolean { + const difference = relative(parent, candidate); + return difference === '' || ( + difference !== '..' + && !difference.startsWith(`..${sep}`) + && !isAbsolute(difference) + ); +} + +function assertTrustedSystemPath(path: string, expectedKind: 'directory' | 'file'): void { + const absolute = resolve(path); + const root = parse(absolute).root; + let current = root; + const paths = [root]; + for (const component of relative(root, absolute).split(sep).filter(Boolean)) { + current = resolve(current, component); + paths.push(current); + } + for (let index = 0; index < paths.length; index += 1) { + const currentPath = paths[index]!; + const status = lstatSync(currentPath); + const final = index === paths.length - 1; + if (status.isSymbolicLink()) fail(); + if (final ? expectedKind === 'directory' && !status.isDirectory() : !final && !status.isDirectory()) { + fail(); + } + if (final && expectedKind === 'file' && !status.isFile()) fail(); + if ( + process.getuid !== undefined + && status.uid !== 0 + || (status.mode & 0o022) !== 0 + ) fail(); + } +} + +export function sha256GreaterRealmAttestedFile( + path: string, + allowedRoot?: string, +): Readonly<{ canonicalPath: string; sha256: string }> { + let descriptor: number | undefined; + try { + const canonicalPath = realpathSync(path); + if (allowedRoot !== undefined) { + const canonicalRoot = realpathSync(allowedRoot); + if (!pathInside(canonicalRoot, canonicalPath) || canonicalPath === canonicalRoot) fail(); + } + const before = lstatSync(canonicalPath); + if ( + !before.isFile() + || before.isSymbolicLink() + || before.size < 1 + || before.size > MAXIMUM_ATTESTED_BINARY_BYTES + || (before.mode & 0o022) !== 0 + || ( + process.getuid !== undefined + && before.uid !== 0 + && before.uid !== process.getuid() + ) + ) fail(); + descriptor = openSync(canonicalPath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const opened = fstatSync(descriptor); + if ( + !opened.isFile() + || opened.dev !== before.dev + || opened.ino !== before.ino + || opened.size !== before.size + || opened.mtimeMs !== before.mtimeMs + || opened.ctimeMs !== before.ctimeMs + ) fail(); + const digest = createHash('sha256'); + const chunk = Buffer.allocUnsafe(Math.min(1024 * 1024, opened.size)); + try { + let offset = 0; + while (offset < opened.size) { + const count = readSync( + descriptor, + chunk, + 0, + Math.min(chunk.length, opened.size - offset), + offset, + ); + if (count <= 0) fail(); + digest.update(chunk.subarray(0, count)); + offset += count; + } + } finally { + chunk.fill(0); + } + const after = fstatSync(descriptor); + const current = lstatSync(canonicalPath); + if ( + after.dev !== opened.dev + || after.ino !== opened.ino + || after.size !== opened.size + || after.mtimeMs !== opened.mtimeMs + || after.ctimeMs !== opened.ctimeMs + || current.dev !== after.dev + || current.ino !== after.ino + || current.size !== after.size + || realpathSync(path) !== canonicalPath + ) fail(); + const sha256 = digest.digest('hex'); + if (!SHA256_PATTERN.test(sha256)) fail(); + return Object.freeze({ canonicalPath, sha256 }); + } catch (error) { + if (error instanceof Error && error.message === 'GREATER_REALM_TOOLCHAIN_INVALID') { + throw error; + } + return fail(); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } +} + +function trustedGitCandidates(): readonly string[] { + if (process.platform === 'darwin') { + return Object.freeze([ + '/Library/Developer/CommandLineTools/usr/bin/git', + '/usr/bin/git', + ]); + } + if (process.platform === 'win32') { + return Object.freeze([ + 'C:\\Program Files\\Git\\cmd\\git.exe', + 'C:\\Program Files\\Git\\bin\\git.exe', + ]); + } + return Object.freeze(['/usr/bin/git']); +} + +function baseEnvironment(execPath?: string): NodeJS.ProcessEnv { + return { + GIT_ATTR_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: NULL_DEVICE, + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_SYSTEM: NULL_DEVICE, + GIT_OPTIONAL_LOCKS: '0', + GIT_PAGER: 'cat', + GIT_TERMINAL_PROMPT: '0', + LANG: 'C', + LC_ALL: 'C', + PAGER: 'cat', + ...(execPath === undefined ? {} : { GIT_EXEC_PATH: execPath }), + ...(process.platform === 'win32' ? { + ComSpec: process.env.ComSpec, + SystemRoot: process.env.SystemRoot, + WINDIR: process.env.WINDIR, + } : {}), + }; +} + +function rawGit( + binaryPath: string, + arguments_: readonly string[], + execPath?: string, +): SpawnSyncReturns { + return spawnSync(binaryPath, [...arguments_], { + cwd: parse(binaryPath).root, + encoding: 'utf8', + env: baseEnvironment(execPath), + maxBuffer: 1024 * 1024, + timeout: 10_000, + }); +} + +let cachedAttestation: GreaterRealmTrustedGitAttestation | undefined; + +export function inspectGreaterRealmTrustedGit(): GreaterRealmTrustedGitAttestation { + if (cachedAttestation !== undefined) return cachedAttestation; + const requested = trustedGitCandidates().find(candidate => existsSync(candidate)); + if (requested === undefined) fail(); + const binary = sha256GreaterRealmAttestedFile(requested); + assertTrustedSystemPath(binary.canonicalPath, 'file'); + const execResult = rawGit(binary.canonicalPath, ['--exec-path']); + const execPathValue = execResult.stdout.trim(); + if ( + execResult.error + || execResult.status !== 0 + || execResult.stderr.length !== 0 + || !isAbsolute(execPathValue) + || !existsSync(execPathValue) + ) fail(); + const execPath = realpathSync(execPathValue); + assertTrustedSystemPath(execPath, 'directory'); + const versionResult = rawGit(binary.canonicalPath, ['--version'], execPath); + const version = versionResult.stdout.trim(); + if ( + versionResult.error + || versionResult.status !== 0 + || versionResult.stderr.length !== 0 + || !/^git version [0-9][ -~]{0,126}$/u.test(version) + ) fail(); + cachedAttestation = Object.freeze({ + binaryPath: binary.canonicalPath, + binarySha256: binary.sha256, + execPath, + version, + }); + return cachedAttestation; +} + +export function runGreaterRealmTrustedGit( + arguments_: readonly string[], + cwd: string, +): SpawnSyncReturns { + if ( + !Array.isArray(arguments_) + || arguments_.length === 0 + || arguments_.length > 256 + || arguments_.some(argument => ( + typeof argument !== 'string' + || argument.length === 0 + || argument.length > 4_096 + || argument.includes('\0') + )) + || !isAbsolute(cwd) + ) fail(); + const git = inspectGreaterRealmTrustedGit(); + return spawnSync(git.binaryPath, [ + '--no-pager', + '--no-optional-locks', + '--no-replace-objects', + '-c', `core.hooksPath=${NULL_DEVICE}`, + '-c', 'core.fsmonitor=false', + '-c', 'core.untrackedCache=false', + ...arguments_, + ], { + cwd, + encoding: 'utf8', + env: baseEnvironment(git.execPath), + maxBuffer: 16 * 1024 * 1024, + timeout: 60_000, + }); +} diff --git a/scripts/atlas/greater-realm-legacy-lowlands.ts b/scripts/atlas/greater-realm-legacy-lowlands.ts new file mode 100644 index 00000000..a6ee7902 --- /dev/null +++ b/scripts/atlas/greater-realm-legacy-lowlands.ts @@ -0,0 +1,542 @@ +import { createHash } from 'node:crypto'; + +import { + CANONICAL_CASTLE_SLOTS, + CANONICAL_REALM, + CANONICAL_WORLD_TILES, + CANONICAL_WORLD_TILE_META, + GENESIS_AUTHORITATIVE_CELL_COUNT, + GENESIS_CASTLE_SLOT_COUNT, + HEGEMONY_REALM_ID, + hexKey, + matchesCanonicalCastleSlot, + matchesCanonicalRealm, + matchesCanonicalTerrain, + matchesCanonicalWorldMeta, +} from '../../spacetimedb/src/world'; +import { + CANONICAL_TIER_I_GOLD_SITES_V1, + GENESIS_TIER_I_GOLD_SITE_COUNT, + GENESIS_TIER_I_GOLD_SITE_DIGEST, + GOLD_SITE_POLICY_VERSION, + canonicalTierIGoldSiteDigestInput, + matchesCanonicalTierIGoldSiteV1, +} from '../../spacetimedb/src/goldSitePolicy'; +import { + CANONICAL_TIER_I_FOOD_SITES_V1, + FOOD_SITE_POLICY_VERSION, + GENESIS_TIER_I_FOOD_SITE_COUNT, + GENESIS_TIER_I_FOOD_SITE_DIGEST, + canonicalTierIFoodSiteDigestInput, + matchesCanonicalTierIFoodSiteV1, +} from '../../spacetimedb/src/foodSitePolicy'; +import { + CANONICAL_TIER_I_WOOD_SITES_V1, + GENESIS_TIER_I_WOOD_SITE_COUNT, + GENESIS_TIER_I_WOOD_SITE_DIGEST, + WOOD_SITE_POLICY_VERSION, + canonicalTierIWoodSiteDigestInput, + matchesCanonicalTierIWoodSiteV1, +} from '../../spacetimedb/src/woodSitePolicy'; +import { + CANONICAL_TIER_I_STONE_SITES_V1, + GENESIS_TIER_I_STONE_SITE_COUNT, + GENESIS_TIER_I_STONE_SITE_DIGEST, + STONE_SITE_POLICY_VERSION, + canonicalTierIStoneSiteDigestInput, + matchesCanonicalTierIStoneSiteV1, +} from '../../spacetimedb/src/stoneSitePolicy'; +import { + CANONICAL_GENESIS_FOREST_INSTANCES_V1, + CANONICAL_GENESIS_FOREST_LAYOUT_V1, + canonicalGenesisForestAssetCatalogV1DigestInput, + canonicalGenesisForestLayoutV1DigestInput, + isCompleteCanonicalGenesisForestLayoutV1, +} from '../../spacetimedb/src/forestLayoutPolicy'; +import { + GENESIS_FOREST_LAYOUT_V1_ASSET_CATALOG_DIGEST, + GENESIS_FOREST_LAYOUT_V1_DIGEST, + GENESIS_FOREST_LAYOUT_V1_TREE_COUNT, +} from '../../spacetimedb/src/forestLayoutContract'; +import { + GENESIS_WATER_BODIES_V1, + GENESIS_WATER_CELLS_V1, + GENESIS_WATER_LAYOUT_DIGEST, + GENESIS_WATER_LAYOUT_V1, + matchesGenesisWaterLayoutV1, +} from '../../spacetimedb/src/waterWorld'; +import { + CANONICAL_GENESIS_WATER_REVISION_V1, + GENESIS_WATER_REVISION_DIGEST, + GENESIS_WATER_REVISION_ENABLED_BODIES_V1, + GENESIS_WATER_REVISION_ENABLED_BODY_COUNT, + GENESIS_WATER_REVISION_ENABLED_CELLS_V1, + GENESIS_WATER_REVISION_ENABLED_CELL_COUNT, + GENESIS_WATER_REVISION_RECLAIMED_LAKE_CELL_COUNT, + GENESIS_WATER_REVISION_RECLAIMED_LAKE_KEYS_V1, + canonicalGenesisWaterRevisionV1DigestInput, + matchesCanonicalGenesisWaterRevisionV1, +} from '../../spacetimedb/src/waterRevision'; + +/** + * This module is an input boundary for offline atlas generation only. Nothing + * in SpacetimeDB or the shipped client imports it, and its exact coordinates + * must never be passed to the aggregate-only public candidate report. + */ +export const GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_SCHEMA = + 'warpkeep.greater-realm.private-legacy-lowlands-patch.v1' as const; +export const GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_CLASSIFICATION = + 'private-generator-input-only-never-public-report' as const; +export const GREATER_REALM_LOWLANDS_REGION_ID = 'T1_LOWLANDS' as const; +export const GREATER_REALM_LEGACY_BRIDGE_VERSION = 1; + +export const LEGACY_LOWLANDS_AXIAL_ROTATION_STEPS = Object.freeze([ + 0, 1, 2, 3, 4, 5, +] as const); + +export type LegacyLowlandsAxialRotationSteps = + (typeof LEGACY_LOWLANDS_AXIAL_ROTATION_STEPS)[number]; + +export type AxialCoordinate = Readonly<{ + q: number; + r: number; +}>; + +export type LegacyLowlandsAtlasTransform = Readonly<{ + rotationSteps: LegacyLowlandsAxialRotationSteps; + globalOffsetQ: number; + globalOffsetR: number; +}>; + +const REGION_ID_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/u; + +function fail(code: string): never { + // Exact coordinates are deliberately omitted from failures: callers may + // surface codes in review automation, while private layout data stays local. + throw new Error(`GREATER_REALM_LEGACY_LOWLANDS_${code}`); +} + +function assertSafeInteger(value: number, code: string): void { + if (!Number.isSafeInteger(value)) fail(code); +} + +function assertAxialCoordinate(coordinate: AxialCoordinate): void { + assertSafeInteger(coordinate.q, 'AXIAL_Q'); + assertSafeInteger(coordinate.r, 'AXIAL_R'); + assertSafeInteger(-coordinate.q - coordinate.r, 'AXIAL_S'); +} + +function assertRotationSteps( + rotationSteps: number, +): asserts rotationSteps is LegacyLowlandsAxialRotationSteps { + if (!(LEGACY_LOWLANDS_AXIAL_ROTATION_STEPS as readonly number[]).includes(rotationSteps)) { + fail('ROTATION_STEPS'); + } +} + +/** Exact canonical key for one private axial coordinate. */ +export function privateAxialCoordinateKey(coordinate: AxialCoordinate): string { + assertAxialCoordinate(coordinate); + return hexKey(coordinate.q, coordinate.r); +} + +/** + * Region-scoped key format required by the future bridge. Region validation + * makes the delimiter unambiguous rather than relying on escaping. + */ +export function privateRegionAxialCoordinateKey( + regionId: string, + coordinate: AxialCoordinate, +): string { + if (!REGION_ID_PATTERN.test(regionId)) fail('REGION_ID'); + return `${regionId}:${privateAxialCoordinateKey(coordinate)}`; +} + +/** + * Clockwise rotation around the axial origin in exact 60-degree increments. + * In cube terms `(q, s, r) -> (-r, -q, -s)`; no floating point is involved. + */ +export function rotateAxialCoordinate60( + coordinate: AxialCoordinate, + rotationSteps: LegacyLowlandsAxialRotationSteps, +): AxialCoordinate { + assertAxialCoordinate(coordinate); + assertRotationSteps(rotationSteps); + let q = coordinate.q; + let r = coordinate.r; + for (let step = 0; step < rotationSteps; step += 1) { + const nextQ = -r; + const nextR = q + r; + assertAxialCoordinate({ q: nextQ, r: nextR }); + q = nextQ; + r = nextR; + } + return Object.freeze({ q, r }); +} + +/** Applies a reviewed rotation followed by an exact global axial translation. */ +export function transformLegacyLowlandsToGlobal( + localCoordinate: AxialCoordinate, + transform: LegacyLowlandsAtlasTransform, +): AxialCoordinate { + assertRotationSteps(transform.rotationSteps); + assertSafeInteger(transform.globalOffsetQ, 'GLOBAL_OFFSET_Q'); + assertSafeInteger(transform.globalOffsetR, 'GLOBAL_OFFSET_R'); + const rotated = rotateAxialCoordinate60(localCoordinate, transform.rotationSteps); + const globalCoordinate = { + q: rotated.q + transform.globalOffsetQ, + r: rotated.r + transform.globalOffsetR, + }; + assertAxialCoordinate(globalCoordinate); + return Object.freeze(globalCoordinate); +} + +/** Exactly reverses translation and the selected 60-degree rotation. */ +export function inverseGlobalToLegacyLowlands( + globalCoordinate: AxialCoordinate, + transform: LegacyLowlandsAtlasTransform, +): AxialCoordinate { + assertAxialCoordinate(globalCoordinate); + assertRotationSteps(transform.rotationSteps); + assertSafeInteger(transform.globalOffsetQ, 'GLOBAL_OFFSET_Q'); + assertSafeInteger(transform.globalOffsetR, 'GLOBAL_OFFSET_R'); + const translated = { + q: globalCoordinate.q - transform.globalOffsetQ, + r: globalCoordinate.r - transform.globalOffsetR, + }; + assertAxialCoordinate(translated); + const inverseSteps = ((6 - transform.rotationSteps) % 6) as LegacyLowlandsAxialRotationSteps; + return rotateAxialCoordinate60(translated, inverseSteps); +} + +/** + * Fails without disclosing the colliding key. The returned count is safe for + * private validation summaries but contains no coordinate material. + */ +export function assertAxialKeyCollisionFree( + coordinates: Iterable, +): number { + const keys = new Set(); + let count = 0; + for (const coordinate of coordinates) { + const key = privateAxialCoordinateKey(coordinate); + if (keys.has(key)) fail('AXIAL_KEY_COLLISION'); + keys.add(key); + count += 1; + } + return count; +} + +export const GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1 = Object.freeze({ + worldCellCount: 10_000, + worldGenerationDigest: '4c111ec1f5e127c7cfd8f42f87c4085f94a4bc46bdacbdc9779866dfdb3edab6', + castleSlotCount: 100, + castleSlotDigest: 'd770a084b7c8f59abbc505239a026a98e17bd55d3507c204cd1517858db017ed', + waterLayoutDigest: 'e6e3601063254a232a80bcc2921e6717b7564f8fce7b276207ffca39c1843dba', + waterBaseCellCount: 3_680, + waterRevisionDigest: '82c18efe71afff1e1dcd4db17b2f6bd1815042d88c7471793bf6cd6d03780aec', + waterEnabledBodyCount: 13, + waterEnabledCellCount: 3_271, + waterReclaimedLakeCellCount: 409, + goldSiteCount: 24, + goldSiteDigest: '84ea3eed9ff5cd3eb7e4704aee6fb562ef3f969c490e95d3bf88645abded7d7d', + foodSiteCount: 96, + foodSiteDigest: '10756337e27138b536a250ad6bf704c603a8c3946c72a1f0d3a041630610ce72', + woodSiteCount: 96, + woodSiteDigest: '3f0ae99d2052c32b7fec9aec6126e86f53031c13d619fcef12dd42a02b4063d6', + stoneSiteCount: 96, + stoneSiteDigest: '22c902d5bfb033e7faf3eaa303e89228d9aad0cff712853618dc34b994d28467', + forestInstanceCount: 210, + forestLayoutDigest: '8a7e7c290e319f9495c3ca2485114659a52f84411e7864a4ed0127ac248b52b2', + forestAssetCatalogDigest: 'e544942ee29a61215c2afce360b8a19f943ff703957e84b20973452f1b93cde7', +} as const); + +/** + * The exact deployed Lowlands patch. The arrays below are the frozen canonical + * source objects, not regenerated copies. This descriptor is intentionally not + * exported from an application index or consumed by public report tooling. + */ +export const GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1 = Object.freeze({ + schema: GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_SCHEMA, + classification: GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_CLASSIFICATION, + bridgeVersion: GREATER_REALM_LEGACY_BRIDGE_VERSION, + regionId: GREATER_REALM_LOWLANDS_REGION_ID, + coordinateSpace: 'region-local-axial' as const, + realm: CANONICAL_REALM, + world: Object.freeze({ + generationDigest: GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.worldGenerationDigest, + tiles: CANONICAL_WORLD_TILES, + metadata: CANONICAL_WORLD_TILE_META, + }), + castleSlots: Object.freeze({ + catalogDigest: GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.castleSlotDigest, + rows: CANONICAL_CASTLE_SLOTS, + }), + water: Object.freeze({ + layoutDigest: GENESIS_WATER_LAYOUT_DIGEST, + layout: GENESIS_WATER_LAYOUT_V1, + bodies: GENESIS_WATER_BODIES_V1, + cells: GENESIS_WATER_CELLS_V1, + activeRevision: CANONICAL_GENESIS_WATER_REVISION_V1, + enabledBodies: GENESIS_WATER_REVISION_ENABLED_BODIES_V1, + enabledCells: GENESIS_WATER_REVISION_ENABLED_CELLS_V1, + reclaimedLakeCellKeys: GENESIS_WATER_REVISION_RECLAIMED_LAKE_KEYS_V1, + }), + resources: Object.freeze({ + gold: Object.freeze({ + policyVersion: GOLD_SITE_POLICY_VERSION, + catalogDigest: GENESIS_TIER_I_GOLD_SITE_DIGEST, + sites: CANONICAL_TIER_I_GOLD_SITES_V1, + }), + food: Object.freeze({ + policyVersion: FOOD_SITE_POLICY_VERSION, + catalogDigest: GENESIS_TIER_I_FOOD_SITE_DIGEST, + sites: CANONICAL_TIER_I_FOOD_SITES_V1, + }), + wood: Object.freeze({ + policyVersion: WOOD_SITE_POLICY_VERSION, + catalogDigest: GENESIS_TIER_I_WOOD_SITE_DIGEST, + sites: CANONICAL_TIER_I_WOOD_SITES_V1, + }), + stone: Object.freeze({ + policyVersion: STONE_SITE_POLICY_VERSION, + catalogDigest: GENESIS_TIER_I_STONE_SITE_DIGEST, + sites: CANONICAL_TIER_I_STONE_SITES_V1, + }), + }), + forest: Object.freeze({ + layoutDigest: GENESIS_FOREST_LAYOUT_V1_DIGEST, + assetCatalogDigest: GENESIS_FOREST_LAYOUT_V1_ASSET_CATALOG_DIGEST, + layout: CANONICAL_GENESIS_FOREST_LAYOUT_V1, + instances: CANONICAL_GENESIS_FOREST_INSTANCES_V1, + }), +}); + +function sha256(value: string): string { + return createHash('sha256').update(value).digest('hex'); +} + +function assertEqual(actual: unknown, expected: unknown, code: string): void { + if (actual !== expected) fail(code); +} + +type SiteRow = Readonly<{ + siteId: string; + q: number; + r: number; + tier: number; + active: boolean; +}>; + +function assertSiteCatalog( + sites: readonly T[], + expectedCount: number, + expectedDigest: string, + digestInput: () => string, + matchesCanonical: (site: T) => boolean, + worldKeys: ReadonlySet, + occupiedSiteKeys: Set, +): void { + assertEqual(sites.length, expectedCount, 'RESOURCE_SITE_COUNT_DRIFT'); + assertEqual(sha256(digestInput()), expectedDigest, 'RESOURCE_SITE_DIGEST_DRIFT'); + const ids = new Set(); + for (const site of sites) { + const key = privateAxialCoordinateKey(site); + if ( + ids.has(site.siteId) + || occupiedSiteKeys.has(key) + || !worldKeys.has(key) + || !matchesCanonical(site) + ) fail('RESOURCE_SITE_CATALOG_DRIFT'); + ids.add(site.siteId); + occupiedSiteKeys.add(key); + } +} + +/** Re-runnable fail-closed audit used before any private candidate generation. */ +export function assertGreaterRealmLegacyLowlandsPatchLocked(): void { + const pins = GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1; + const patch = GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1; + + assertEqual(patch.realm.realmId, HEGEMONY_REALM_ID, 'REALM_ID_DRIFT'); + if (!matchesCanonicalRealm(patch.realm)) fail('REALM_DRIFT'); + assertEqual(patch.world.tiles.length, GENESIS_AUTHORITATIVE_CELL_COUNT, 'WORLD_COUNT_POLICY'); + assertEqual(patch.world.tiles.length, pins.worldCellCount, 'WORLD_COUNT_DRIFT'); + assertEqual(patch.world.metadata.length, pins.worldCellCount, 'WORLD_META_COUNT_DRIFT'); + assertEqual(assertAxialKeyCollisionFree(patch.world.tiles), pins.worldCellCount, 'WORLD_KEY_COUNT'); + + const worldKeys = new Set(); + for (const tile of patch.world.tiles) { + if (tile.key !== privateAxialCoordinateKey(tile) || !matchesCanonicalTerrain(tile)) { + fail('WORLD_TILE_DRIFT'); + } + worldKeys.add(tile.key); + } + for (const meta of patch.world.metadata) { + if (!worldKeys.has(meta.tileKey) || !matchesCanonicalWorldMeta(meta)) { + fail('WORLD_META_DRIFT'); + } + } + + assertEqual( + sha256(JSON.stringify({ + realm: patch.realm, + tiles: patch.world.tiles, + meta: patch.world.metadata, + slots: patch.castleSlots.rows, + })), + pins.worldGenerationDigest, + 'WORLD_DIGEST_DRIFT', + ); + + assertEqual(patch.castleSlots.rows.length, GENESIS_CASTLE_SLOT_COUNT, 'CASTLE_COUNT_POLICY'); + assertEqual(patch.castleSlots.rows.length, pins.castleSlotCount, 'CASTLE_COUNT_DRIFT'); + assertEqual(sha256(JSON.stringify(patch.castleSlots.rows)), pins.castleSlotDigest, 'CASTLE_DIGEST_DRIFT'); + assertEqual(assertAxialKeyCollisionFree(patch.castleSlots.rows), pins.castleSlotCount, 'CASTLE_KEY_COUNT'); + const slotIds = new Set(); + for (const slot of patch.castleSlots.rows) { + const key = privateAxialCoordinateKey(slot); + if ( + slotIds.has(slot.slotId) + || slot.tileKey !== key + || !worldKeys.has(key) + || !matchesCanonicalCastleSlot(slot) + ) fail('CASTLE_CATALOG_DRIFT'); + slotIds.add(slot.slotId); + } + + if (!matchesGenesisWaterLayoutV1(patch.water.layout)) fail('WATER_LAYOUT_DRIFT'); + if (!matchesCanonicalGenesisWaterRevisionV1(patch.water.activeRevision)) { + fail('WATER_REVISION_DRIFT'); + } + assertEqual(patch.water.layoutDigest, pins.waterLayoutDigest, 'WATER_LAYOUT_DIGEST_DRIFT'); + assertEqual(GENESIS_WATER_LAYOUT_DIGEST, pins.waterLayoutDigest, 'WATER_LAYOUT_PIN_DRIFT'); + assertEqual(patch.water.cells.length, pins.waterBaseCellCount, 'WATER_BASE_CELL_COUNT_DRIFT'); + assertEqual(patch.water.enabledBodies.length, pins.waterEnabledBodyCount, 'WATER_BODY_COUNT_DRIFT'); + assertEqual(patch.water.enabledCells.length, pins.waterEnabledCellCount, 'WATER_CELL_COUNT_DRIFT'); + assertEqual( + patch.water.reclaimedLakeCellKeys.length, + pins.waterReclaimedLakeCellCount, + 'WATER_RECLAIMED_COUNT_DRIFT', + ); + assertEqual( + sha256(canonicalGenesisWaterRevisionV1DigestInput()), + pins.waterRevisionDigest, + 'WATER_REVISION_DIGEST_DRIFT', + ); + assertEqual(GENESIS_WATER_REVISION_DIGEST, pins.waterRevisionDigest, 'WATER_REVISION_PIN_DRIFT'); + assertEqual(GENESIS_WATER_REVISION_ENABLED_BODY_COUNT, pins.waterEnabledBodyCount, 'WATER_BODY_POLICY'); + assertEqual(GENESIS_WATER_REVISION_ENABLED_CELL_COUNT, pins.waterEnabledCellCount, 'WATER_CELL_POLICY'); + assertEqual( + GENESIS_WATER_REVISION_RECLAIMED_LAKE_CELL_COUNT, + pins.waterReclaimedLakeCellCount, + 'WATER_RECLAIMED_POLICY', + ); + + const bodyIds = new Set(); + for (const body of patch.water.bodies) { + if (bodyIds.has(body.bodyId)) fail('WATER_BODY_CATALOG_DRIFT'); + bodyIds.add(body.bodyId); + } + assertEqual(assertAxialKeyCollisionFree(patch.water.cells), patch.water.cells.length, 'WATER_KEY_COUNT'); + for (const cell of patch.water.cells) { + if (cell.cellKey !== privateAxialCoordinateKey(cell) || !bodyIds.has(cell.bodyId)) { + fail('WATER_CELL_CATALOG_DRIFT'); + } + if (cell.underlyingTileKey !== undefined && !worldKeys.has(cell.underlyingTileKey)) { + fail('WATER_UNDERLYING_TILE_DRIFT'); + } + } + + const occupiedSiteKeys = new Set(); + assertSiteCatalog( + patch.resources.gold.sites, + pins.goldSiteCount, + pins.goldSiteDigest, + canonicalTierIGoldSiteDigestInput, + matchesCanonicalTierIGoldSiteV1, + worldKeys, + occupiedSiteKeys, + ); + assertSiteCatalog( + patch.resources.food.sites, + pins.foodSiteCount, + pins.foodSiteDigest, + canonicalTierIFoodSiteDigestInput, + matchesCanonicalTierIFoodSiteV1, + worldKeys, + occupiedSiteKeys, + ); + assertSiteCatalog( + patch.resources.wood.sites, + pins.woodSiteCount, + pins.woodSiteDigest, + canonicalTierIWoodSiteDigestInput, + matchesCanonicalTierIWoodSiteV1, + worldKeys, + occupiedSiteKeys, + ); + assertSiteCatalog( + patch.resources.stone.sites, + pins.stoneSiteCount, + pins.stoneSiteDigest, + canonicalTierIStoneSiteDigestInput, + matchesCanonicalTierIStoneSiteV1, + worldKeys, + occupiedSiteKeys, + ); + assertEqual(GENESIS_TIER_I_GOLD_SITE_COUNT, pins.goldSiteCount, 'GOLD_COUNT_POLICY'); + assertEqual(GENESIS_TIER_I_FOOD_SITE_COUNT, pins.foodSiteCount, 'FOOD_COUNT_POLICY'); + assertEqual(GENESIS_TIER_I_WOOD_SITE_COUNT, pins.woodSiteCount, 'WOOD_COUNT_POLICY'); + assertEqual(GENESIS_TIER_I_STONE_SITE_COUNT, pins.stoneSiteCount, 'STONE_COUNT_POLICY'); + assertEqual(GENESIS_TIER_I_GOLD_SITE_DIGEST, pins.goldSiteDigest, 'GOLD_DIGEST_POLICY'); + assertEqual(GENESIS_TIER_I_FOOD_SITE_DIGEST, pins.foodSiteDigest, 'FOOD_DIGEST_POLICY'); + assertEqual(GENESIS_TIER_I_WOOD_SITE_DIGEST, pins.woodSiteDigest, 'WOOD_DIGEST_POLICY'); + assertEqual(GENESIS_TIER_I_STONE_SITE_DIGEST, pins.stoneSiteDigest, 'STONE_DIGEST_POLICY'); + + if (!isCompleteCanonicalGenesisForestLayoutV1(patch.forest.layout, patch.forest.instances)) { + fail('FOREST_CATALOG_DRIFT'); + } + assertEqual(patch.forest.instances.length, GENESIS_FOREST_LAYOUT_V1_TREE_COUNT, 'FOREST_COUNT_POLICY'); + assertEqual(patch.forest.instances.length, pins.forestInstanceCount, 'FOREST_COUNT_DRIFT'); + assertEqual( + sha256(canonicalGenesisForestLayoutV1DigestInput()), + pins.forestLayoutDigest, + 'FOREST_LAYOUT_DIGEST_DRIFT', + ); + assertEqual( + sha256(canonicalGenesisForestAssetCatalogV1DigestInput()), + pins.forestAssetCatalogDigest, + 'FOREST_ASSET_DIGEST_DRIFT', + ); + assertEqual(GENESIS_FOREST_LAYOUT_V1_DIGEST, pins.forestLayoutDigest, 'FOREST_LAYOUT_POLICY'); + assertEqual( + GENESIS_FOREST_LAYOUT_V1_ASSET_CATALOG_DIGEST, + pins.forestAssetCatalogDigest, + 'FOREST_ASSET_POLICY', + ); + const treeIds = new Set(); + const treeWorldCoordinates = new Set(); + for (const instance of patch.forest.instances) { + const worldCoordinateKey = `${instance.worldXMicrounits},${instance.worldZMicrounits}`; + if ( + treeIds.has(instance.treeId) + || treeWorldCoordinates.has(worldCoordinateKey) + || instance.tileKey !== privateAxialCoordinateKey(instance) + || !worldKeys.has(instance.tileKey) + ) fail('FOREST_COORDINATE_DRIFT'); + treeIds.add(instance.treeId); + treeWorldCoordinates.add(worldCoordinateKey); + } + + if ( + !Object.isFrozen(patch) + || !Object.isFrozen(patch.world) + || !Object.isFrozen(patch.castleSlots) + || !Object.isFrozen(patch.water) + || !Object.isFrozen(patch.resources) + || !Object.isFrozen(patch.forest) + ) fail('PATCH_NOT_LOCKED'); +} + +assertGreaterRealmLegacyLowlandsPatchLocked(); diff --git a/scripts/atlas/greater-realm-private-markers.mjs b/scripts/atlas/greater-realm-private-markers.mjs new file mode 100644 index 00000000..7757a105 --- /dev/null +++ b/scripts/atlas/greater-realm-private-markers.mjs @@ -0,0 +1,38 @@ +import { Buffer } from 'node:buffer'; + +/** + * Exact byte markers embedded in every Greater Realm private envelope, + * package, preview, and manifest family. Public-surface scanners share this + * one list so a renamed artifact cannot exploit scanner drift. + */ +export const GREATER_REALM_PRIVATE_MARKER_TEXT = Object.freeze([ + 'WKGR-PRIVATE-PACKAGE-V1', + 'WKGR-PRIVATE-CHECKPOINT-V1', + 'WKGR-PRIVATE-ATLAS-V1', + 'WKGR-PRIVATE-PREVIEW-V1', + 'WKGR-PRIVATE-SEED-V1', + 'warpkeep.greater-realm.private-candidate.v1', + 'warpkeep.greater-realm.private-checkpoint.v1', + 'warpkeep.greater-realm.private-batch.v1', + 'warpkeep.greater-realm.private-owner-selection.v1', + 'warpkeep.greater-realm.private-owner-shortlist.v1', + 'warpkeep.greater-realm.private-legacy-lowlands-patch.v1', + 'warpkeep.greater-realm.private-chunk-manifest.v1', + 'warpkeep.greater-realm.private-topography-patch.v1', + 'warpkeep.greater-realm.private-provenance.v1', +]); + +const GREATER_REALM_PRIVATE_MARKERS = Object.freeze( + GREATER_REALM_PRIVATE_MARKER_TEXT.map(marker => Buffer.from(marker, 'utf8')), +); + +export const GREATER_REALM_PRIVATE_MARKER_OVERLAP_BYTES = Math.max( + ...GREATER_REALM_PRIVATE_MARKERS.map(marker => marker.length), +) - 1; + +export function containsGreaterRealmPrivateMarker(bytes) { + if (!Buffer.isBuffer(bytes)) { + throw new TypeError('GREATER_REALM_PRIVATE_MARKER_INPUT_INVALID'); + } + return GREATER_REALM_PRIVATE_MARKERS.some(marker => bytes.indexOf(marker) !== -1); +} diff --git a/scripts/atlas/greater-realm-private-seed.ts b/scripts/atlas/greater-realm-private-seed.ts new file mode 100644 index 00000000..eb294ef9 --- /dev/null +++ b/scripts/atlas/greater-realm-private-seed.ts @@ -0,0 +1,65 @@ +const PRIVATE_SEED_MARKER = 'WKGR-PRIVATE-SEED-V1' as const; +const PRIVATE_SEED_MARKER_BYTES = Buffer.from(PRIVATE_SEED_MARKER, 'ascii'); +const PRIVATE_SEED_PAYLOAD_BYTES = 32; +const PRIVATE_SEED_KIND = Object.freeze({ batch: 1, candidate: 2 } as const); +const PRIVATE_SEED_HEADER_BYTES = PRIVATE_SEED_MARKER_BYTES.length + 3; + +export const GREATER_REALM_PRIVATE_SEED_MARKER = PRIVATE_SEED_MARKER; +export const GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES = + PRIVATE_SEED_HEADER_BYTES + PRIVATE_SEED_PAYLOAD_BYTES; + +export type GreaterRealmPrivateSeedKind = keyof typeof PRIVATE_SEED_KIND; + +function fail(): never { + throw new Error('GREATER_REALM_PRIVATE_SEED_ENVELOPE_INVALID'); +} + +/** + * Mark a private 32-byte seed so renamed seed files remain detectable by the + * repository privacy scanner. Cryptographic derivation never includes this + * envelope; callers must continue hashing only the extracted payload. + */ +export function encodeGreaterRealmPrivateSeed( + seed: Uint8Array, + kind: GreaterRealmPrivateSeedKind, +): Buffer { + if (!(seed instanceof Uint8Array) || seed.byteLength !== PRIVATE_SEED_PAYLOAD_BYTES) { + fail(); + } + const kindCode = kind === 'batch' + ? PRIVATE_SEED_KIND.batch + : kind === 'candidate' + ? PRIVATE_SEED_KIND.candidate + : undefined; + if (kindCode === undefined) fail(); + const envelope = Buffer.alloc(GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES); + PRIVATE_SEED_MARKER_BYTES.copy(envelope, 0); + let offset = PRIVATE_SEED_MARKER_BYTES.length; + envelope.writeUInt8(0, offset); + offset += 1; + envelope.writeUInt8(kindCode, offset); + offset += 1; + envelope.writeUInt8(PRIVATE_SEED_PAYLOAD_BYTES, offset); + offset += 1; + envelope.set(seed, offset); + return envelope; +} + +/** Parse a strict envelope and return a new owner-cleared 32-byte payload. */ +export function decodeGreaterRealmPrivateSeed( + envelope: Uint8Array, + expectedKind: GreaterRealmPrivateSeedKind, +): Buffer { + if ( + !(envelope instanceof Uint8Array) + || envelope.byteLength !== GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES + ) fail(); + const markerBytes = PRIVATE_SEED_MARKER_BYTES.length; + if ( + PRIVATE_SEED_MARKER_BYTES.some((byte, index) => envelope[index] !== byte) + || envelope[markerBytes] !== 0 + || envelope[markerBytes + 1] !== PRIVATE_SEED_KIND[expectedKind] + || envelope[markerBytes + 2] !== PRIVATE_SEED_PAYLOAD_BYTES + ) fail(); + return Buffer.from(envelope.subarray(PRIVATE_SEED_HEADER_BYTES)); +} diff --git a/scripts/atlas/greater-realm-private-workspace.ts b/scripts/atlas/greater-realm-private-workspace.ts new file mode 100644 index 00000000..ec050037 --- /dev/null +++ b/scripts/atlas/greater-realm-private-workspace.ts @@ -0,0 +1,1381 @@ +import { createHash, randomUUID } from 'node:crypto'; +import { + closeSync, + constants, + existsSync, + fchmodSync, + fstatSync, + fsyncSync, + linkSync, + lstatSync, + mkdirSync, + openSync, + realpathSync, + readSync, + readdirSync, + renameSync, + rmdirSync, + unlinkSync, + writeSync, +} from 'node:fs'; +import type { Stats } from 'node:fs'; +import { homedir } from 'node:os'; +import { + basename, + dirname, + isAbsolute, + join, + parse, + relative, + resolve, + sep, +} from 'node:path'; + +const PRIVATE_DIRECTORY_MODE = 0o700; +const PRIVATE_FILE_MODE = 0o600; +const PRIVATE_PUBLICATION_RESERVED_PREFIX = '.wk-publish-'; +const PRIVATE_PUBLICATION_ENVELOPE = '.wk-publish-envelope-v1'; +const PRIVATE_PUBLICATION_COMMIT = '.wk-publish-commit-v1'; +const PRIVATE_PUBLICATION_PAYLOAD_PREFIX = '.wk-publish-payload-'; +const PRIVATE_PUBLICATION_ENVELOPE_BYTES = Buffer.from( + 'warpkeep-greater-realm-private-directory-envelope-v1\n', + 'utf8', +); +const PRIVATE_PUBLICATION_PAYLOAD = /^\.wk-publish-payload-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u; +const DEFAULT_MAXIMUM_FILE_BYTES = 512 * 1024 * 1024; +const MAXIMUM_TREE_ENTRIES = 250_000; +const FORBIDDEN_SECRET_ARGUMENT = /^(?:--)?(?:private-)?(?:atlas-)?(?:seed|seed-hex|seed-material|layout-digest|stage-digest|package-digest)(?:=|$)/iu; +const RESERVED_ENVIRONMENT_KEY = /^WARPKEEP_GREATER_REALM_/u; +const POSSIBLE_SECRET_VALUE = /^(?:[0-9a-f]{64}|[A-Za-z0-9+/]{43}=?)$/u; + +type FilesystemIdentity = Readonly<{ + dev: number; + ino: number; +}>; + +type DirectoryAttestation = Readonly<{ + path: string; + identity: FilesystemIdentity; +}>; + +export class GreaterRealmPrivateWorkspaceError extends Error { + constructor(readonly code: string) { + super(code); + this.name = 'GreaterRealmPrivateWorkspaceError'; + } +} + +function fail(code: string): never { + throw new GreaterRealmPrivateWorkspaceError(code); +} + +function pathInside(parent: string, candidate: string): boolean { + const difference = relative(parent, candidate); + return difference === '' || ( + difference !== '..' + && !difference.startsWith(`..${sep}`) + && !isAbsolute(difference) + ); +} + +function sameIdentity( + left: Pick, + right: Pick, +): boolean { + return left.dev === right.dev && left.ino === right.ino; +} + +function ownerOnlyDirectoryStatus(status: Stats): void { + if ( + !status.isDirectory() + || status.isSymbolicLink() + || (process.getuid !== undefined && status.uid !== process.getuid()) + ) fail('GREATER_REALM_PRIVATE_DIRECTORY_INVALID'); + if ((status.mode & 0o777) !== PRIVATE_DIRECTORY_MODE) { + fail('GREATER_REALM_PRIVATE_DIRECTORY_PERMISSIONS'); + } +} + +function ownerOnlyDirectory(path: string): void { + ownerOnlyDirectoryStatus(lstatSync(path)); +} + +function assertTrustedAncestorStatus( + status: Stats, +): void { + if (!status.isDirectory() || status.isSymbolicLink()) { + fail('GREATER_REALM_PRIVATE_PATH_INVALID'); + } + if (process.getuid !== undefined) { + const currentUser = process.getuid(); + if (status.uid !== 0 && status.uid !== currentUser) { + fail('GREATER_REALM_PRIVATE_PATH_UNTRUSTED_ANCESTOR'); + } + } + const writableByOthers = (status.mode & 0o022) !== 0; + const protectedByStickyRoot = ( + (status.mode & 0o1000) !== 0 + && status.uid === 0 + ); + if (writableByOthers && !protectedByStickyRoot) { + fail('GREATER_REALM_PRIVATE_PATH_UNTRUSTED_ANCESTOR'); + } +} + +function directoryPaths(path: string): readonly string[] { + const absolute = resolve(path); + const root = parse(absolute).root; + const paths = [root]; + let current = root; + for (const component of relative(root, absolute).split(sep).filter(Boolean)) { + current = join(current, component); + paths.push(current); + } + return Object.freeze(paths); +} + +function attestDirectory( + path: string, + privateDirectory: boolean, + expected?: FilesystemIdentity, +): DirectoryAttestation { + let descriptor: number | undefined; + try { + const before = lstatSync(path); + if (privateDirectory) ownerOnlyDirectoryStatus(before); + else assertTrustedAncestorStatus(before); + descriptor = openSync( + path, + constants.O_RDONLY + | (constants.O_DIRECTORY ?? 0) + | (constants.O_NOFOLLOW ?? 0), + ); + const opened = fstatSync(descriptor); + const after = lstatSync(path); + if ( + !opened.isDirectory() + || !sameIdentity(before, opened) + || !sameIdentity(opened, after) + || (expected !== undefined && !sameIdentity(opened, expected)) + || realpathSync(path) !== path + ) fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + if (privateDirectory) ownerOnlyDirectoryStatus(after); + else assertTrustedAncestorStatus(after); + return Object.freeze({ + path, + identity: Object.freeze({ dev: opened.dev, ino: opened.ino }), + }); + } catch (error) { + if (error instanceof GreaterRealmPrivateWorkspaceError) throw error; + return fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } +} + +function hardenNewPrivateDirectory(path: string): void { + let descriptor: number | undefined; + try { + const before = lstatSync(path); + if ( + !before.isDirectory() + || before.isSymbolicLink() + || (process.getuid !== undefined && before.uid !== process.getuid()) + ) fail('GREATER_REALM_PRIVATE_DIRECTORY_INVALID'); + descriptor = openSync( + path, + constants.O_RDONLY + | (constants.O_DIRECTORY ?? 0) + | (constants.O_NOFOLLOW ?? 0), + ); + const opened = fstatSync(descriptor); + if (!opened.isDirectory() || !sameIdentity(before, opened)) { + fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + } + fchmodSync(descriptor, PRIVATE_DIRECTORY_MODE); + const hardened = fstatSync(descriptor); + const current = lstatSync(path); + ownerOnlyDirectoryStatus(hardened); + ownerOnlyDirectoryStatus(current); + if (!sameIdentity(opened, hardened) || !sameIdentity(hardened, current)) { + fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + } + } catch (error) { + if (error instanceof GreaterRealmPrivateWorkspaceError) throw error; + return fail('GREATER_REALM_PRIVATE_DIRECTORY_CREATE_FAILED'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } +} + +function assertExistingPathHasNoSymlinks(path: string): void { + const absolute = resolve(path); + const root = parse(absolute).root; + const components = relative(root, absolute).split(sep).filter(Boolean); + let current = root; + for (const component of components) { + current = join(current, component); + if (!existsSync(current)) break; + const status = lstatSync(current); + if (status.isSymbolicLink()) fail('GREATER_REALM_PRIVATE_PATH_SYMLINK'); + if (current !== absolute && !status.isDirectory()) { + fail('GREATER_REALM_PRIVATE_PATH_INVALID'); + } + } +} + +/** + * Resolve any symlinks in an existing ancestor without accepting a symlink as + * the requested workspace or repository itself. This keeps macOS' `/var` + * alias usable while still rejecting attacker-controlled workspace aliases. + */ +function canonicalProspectivePath(path: string): string { + const absolute = resolve(path); + const missingComponents: string[] = []; + let existing = absolute; + while (!existsSync(existing)) { + const parent = dirname(existing); + if (parent === existing) fail('GREATER_REALM_PRIVATE_PATH_INVALID'); + missingComponents.unshift(basename(existing)); + existing = parent; + } + const status = lstatSync(existing); + if (status.isSymbolicLink()) fail('GREATER_REALM_PRIVATE_PATH_SYMLINK'); + if (!status.isDirectory()) fail('GREATER_REALM_PRIVATE_PATH_INVALID'); + return resolve(realpathSync(existing), ...missingComponents); +} + +function ensurePrivateWorkspaceRoot(path: string): void { + const absolute = resolve(path); + const root = parse(absolute).root; + const components = relative(root, absolute).split(sep).filter(Boolean); + let current = root; + for (const component of components) { + current = join(current, component); + let status = existsSync(current) ? lstatSync(current) : undefined; + let created = false; + if (!status) { + try { + mkdirSync(current, { mode: PRIVATE_DIRECTORY_MODE }); + created = true; + } catch { + status = existsSync(current) ? lstatSync(current) : undefined; + if (!status) fail('GREATER_REALM_PRIVATE_DIRECTORY_CREATE_FAILED'); + } + status = lstatSync(current); + } + if (status === undefined) fail('GREATER_REALM_PRIVATE_DIRECTORY_CREATE_FAILED'); + if (!status.isDirectory() || status.isSymbolicLink()) { + fail('GREATER_REALM_PRIVATE_PATH_INVALID'); + } + if (created) { + hardenNewPrivateDirectory(current); + } + } + ownerOnlyDirectory(absolute); +} + +function validateRelativePath(value: string): readonly string[] { + if ( + typeof value !== 'string' + || value.length === 0 + || value.length > 4_096 + || value.includes('\0') + || isAbsolute(value) + || value.normalize('NFC') !== value + ) fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + const components = value.split(/[\\/]/u); + if (components.some(component => ( + component.length === 0 + || component === '.' + || component === '..' + || component.startsWith(PRIVATE_PUBLICATION_RESERVED_PREFIX) + || component.length > 255 + || component.normalize('NFC') !== component + ))) fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + return Object.freeze(components); +} + +function publicationClaimName(component: string): string { + const digest = createHash('sha256') + .update('warpkeep-greater-realm-private-publication-claim-v1\0', 'utf8') + .update(component, 'utf8') + .digest('hex'); + return `${PRIVATE_PUBLICATION_RESERVED_PREFIX}claim-${digest}`; +} + +function resolvePrivatePath(root: string, value: string): string { + const components = validateRelativePath(value); + const target = resolve(root, ...components); + if (!pathInside(root, target) || target === root) { + fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + } + return target; +} + +function assertRegularOwnerFile(path: string): Stats { + const status = lstatSync(path); + if ( + !status.isFile() + || status.isSymbolicLink() + || status.nlink !== 1 + || (process.getuid !== undefined && status.uid !== process.getuid()) + || (status.mode & 0o077) !== 0 + ) fail('GREATER_REALM_PRIVATE_FILE_INVALID'); + return status; +} + +function assertRegularOwnerFileStatus(status: Stats): void { + if ( + !status.isFile() + || status.isSymbolicLink() + || status.nlink !== 1 + || (process.getuid !== undefined && status.uid !== process.getuid()) + || (status.mode & 0o077) !== 0 + ) fail('GREATER_REALM_PRIVATE_FILE_INVALID'); +} + +function safeUnlinkIdentity(path: string, identity: FilesystemIdentity): void { + try { + const current = lstatSync(path); + if (sameIdentity(current, identity)) unlinkSync(path); + } catch { + // Cleanup is best-effort and never follows or removes a replacement entry. + } +} + +function zeroizeOpenFile( + descriptor: number, + byteLength: number, +): void { + if (!Number.isSafeInteger(byteLength) || byteLength <= 0) return; + const zeros = Buffer.alloc(Math.min(64 * 1_024, byteLength)); + try { + let offset = 0; + while (offset < byteLength) { + const length = Math.min(zeros.byteLength, byteLength - offset); + const written = writeSync(descriptor, zeros, 0, length, offset); + if (written <= 0) break; + offset += written; + } + fsyncSync(descriptor); + } catch { + // Secure erasure cannot be guaranteed by a filesystem. We still overwrite + // every reachable byte best-effort before unlinking the private inode. + } finally { + zeros.fill(0); + } +} + +function writeAll( + descriptor: number, + bytes: Uint8Array, + onProgress?: (writtenByteCount: number) => void, +): void { + let offset = 0; + while (offset < bytes.byteLength) { + const written = writeSync(descriptor, bytes, offset, bytes.byteLength - offset); + if (written <= 0) fail('GREATER_REALM_PRIVATE_WRITE_FAILED'); + offset += written; + onProgress?.(offset); + } +} + +export function defaultGreaterRealmPrivateWorkspaceRoot(): string { + return join(homedir(), '.warpkeep', 'private', 'greater-realm'); +} + +/** + * The atlas CLI accepts only non-secret selectors. Generation material must be + * created internally or read from a protected file descriptor. + */ +export function assertGreaterRealmPrivateInvocation( + arguments_: readonly string[] = process.argv.slice(2), + environment: Readonly> = process.env, +): void { + if (!Array.isArray(arguments_) || arguments_.some(argument => ( + typeof argument !== 'string' + || FORBIDDEN_SECRET_ARGUMENT.test(argument) + || POSSIBLE_SECRET_VALUE.test(argument) + ))) fail('GREATER_REALM_PRIVATE_INVOCATION_REJECTED'); + if (Object.entries(environment).some(([key, value]) => ( + RESERVED_ENVIRONMENT_KEY.test(key) + || (typeof value === 'string' && POSSIBLE_SECRET_VALUE.test(value)) + ))) { + fail('GREATER_REALM_PRIVATE_INVOCATION_REJECTED'); + } +} + +export type GreaterRealmPrivateTreeAttestation = Readonly<{ + entryCount: number; + fileCount: number; + directoryCount: number; + byteCount: number; +}>; + +export type GreaterRealmPrivateWorkspace = Readonly<{ + root: string; + ensureDirectory(relativePath: string): string; + hasFile(relativePath: string): boolean; + readFile(relativePath: string, maximumBytes?: number): Buffer; + writeFileAtomic(relativePath: string, bytes: Uint8Array, maximumBytes?: number): void; + attestTree(relativePath?: string): GreaterRealmPrivateTreeAttestation; + withExclusiveLock(relativePath: string, operation: () => Promise): Promise; + withAtomicDirectoryPublish( + relativePath: string, + operation: (stagedWorkspace: GreaterRealmPrivateWorkspace) => Promise, + ): Promise; +}>; + +export function openGreaterRealmPrivateWorkspace(input: Readonly<{ + repositoryRoot: string; + workspaceRoot?: string; +}>): GreaterRealmPrivateWorkspace { + if (!isAbsolute(input.repositoryRoot) || !existsSync(input.repositoryRoot)) { + fail('GREATER_REALM_PRIVATE_REPOSITORY_INVALID'); + } + const repositoryRoot = canonicalProspectivePath(input.repositoryRoot); + assertExistingPathHasNoSymlinks(repositoryRoot); + const repositoryStatus = lstatSync(repositoryRoot); + if (!repositoryStatus.isDirectory() || repositoryStatus.isSymbolicLink()) { + fail('GREATER_REALM_PRIVATE_REPOSITORY_INVALID'); + } + const requestedRoot = input.workspaceRoot ?? defaultGreaterRealmPrivateWorkspaceRoot(); + if (!isAbsolute(requestedRoot)) fail('GREATER_REALM_PRIVATE_ROOT_NOT_ABSOLUTE'); + const workspaceRoot = canonicalProspectivePath(requestedRoot); + if (pathInside(repositoryRoot, workspaceRoot) || pathInside(workspaceRoot, repositoryRoot)) { + fail('GREATER_REALM_PRIVATE_ROOT_REPOSITORY_OVERLAP'); + } + assertExistingPathHasNoSymlinks(workspaceRoot); + let existingWorkspaceAncestor = workspaceRoot; + while (!existsSync(existingWorkspaceAncestor)) { + const parent = dirname(existingWorkspaceAncestor); + if (parent === existingWorkspaceAncestor) fail('GREATER_REALM_PRIVATE_PATH_INVALID'); + existingWorkspaceAncestor = parent; + } + const existingAncestorAttestations = Object.freeze( + directoryPaths(existingWorkspaceAncestor).map(ancestorPath => ( + attestDirectory(ancestorPath, false) + )), + ); + const existingAncestorIdentities = new Map(existingAncestorAttestations.map(attestation => ( + [attestation.path, attestation.identity] as const + ))); + ensurePrivateWorkspaceRoot(workspaceRoot); + + const workspaceBoundaryPaths = directoryPaths(workspaceRoot); + const workspaceBoundary = Object.freeze(workspaceBoundaryPaths.map((path, index) => ( + attestDirectory( + path, + index === workspaceBoundaryPaths.length - 1, + existingAncestorIdentities.get(path), + ) + ))); + + const attestWorkspaceBoundary = (): void => { + for (let index = 0; index < workspaceBoundary.length; index += 1) { + const expected = workspaceBoundary[index]; + if (expected === undefined) fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + attestDirectory( + expected.path, + index === workspaceBoundary.length - 1, + expected.identity, + ); + } + }; + + const attestPrivateDirectoryChain = ( + path: string, + expected?: readonly DirectoryAttestation[], + ): readonly DirectoryAttestation[] => { + const absolute = resolve(path); + if (!pathInside(workspaceRoot, absolute)) { + fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + } + attestWorkspaceBoundary(); + const difference = relative(workspaceRoot, absolute); + const paths: string[] = [workspaceRoot]; + let current = workspaceRoot; + for (const component of difference.split(sep).filter(Boolean)) { + current = join(current, component); + paths.push(current); + } + if (expected !== undefined && expected.length !== paths.length) { + fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + } + const attestations = paths.map((directoryPath, index) => { + const expectedEntry = expected?.[index]; + if (expectedEntry !== undefined && expectedEntry.path !== directoryPath) { + fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + } + return attestDirectory(directoryPath, true, expectedEntry?.identity); + }); + attestWorkspaceBoundary(); + return Object.freeze(attestations); + }; + + const readPrivatePublicationControl = ( + path: string, + maximumBytes: number, + ): Buffer => { + const parent = dirname(path); + const parentAttestation = attestPrivateDirectoryChain(parent); + let descriptor: number | undefined; + let bytes: Buffer | undefined; + try { + descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const before = fstatSync(descriptor); + assertRegularOwnerFileStatus(before); + if ( + before.size < 1 + || before.size > maximumBytes + || (before.mode & 0o777) !== PRIVATE_FILE_MODE + ) fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + bytes = Buffer.alloc(before.size); + let offset = 0; + while (offset < bytes.byteLength) { + const count = readSync(descriptor, bytes, offset, bytes.byteLength - offset, null); + if (count <= 0) fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + offset += count; + } + const after = fstatSync(descriptor); + const current = lstatSync(path); + if ( + !sameIdentity(before, after) + || !sameIdentity(after, current) + || before.size !== after.size + || before.mtimeMs !== after.mtimeMs + || before.ctimeMs !== after.ctimeMs + || after.nlink !== 1 + || (after.mode & 0o777) !== PRIVATE_FILE_MODE + ) fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + attestPrivateDirectoryChain(parent, parentAttestation); + const result = bytes; + bytes = undefined; + return result; + } catch (error) { + bytes?.fill(0); + if ( + error instanceof GreaterRealmPrivateWorkspaceError + && error.code === 'GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE' + ) throw error; + fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } + }; + + /** + * A published logical directory is an owner-only envelope whose complete + * payload becomes visible only when its no-clobber commit link is present. + * Callers continue to address the logical path; control entries are never + * exposed through the workspace API. + */ + const resolveWorkspacePath = (value: string): string => { + const components = validateRelativePath(value); + let current = workspaceRoot; + for (let index = 0; index < components.length; index += 1) { + const component = components[index]; + if (component === undefined) fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + const claim = join(current, publicationClaimName(component)); + if (existsSync(claim)) fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + current = join(current, component); + if (!existsSync(current)) { + for (const suffix of components.slice(index + 1)) current = join(current, suffix); + break; + } + const currentStatus = lstatSync(current); + if (currentStatus.isSymbolicLink()) fail('GREATER_REALM_PRIVATE_PATH_SYMLINK'); + if (!currentStatus.isDirectory()) continue; + + const envelopeMarker = join(current, PRIVATE_PUBLICATION_ENVELOPE); + const commitMarker = join(current, PRIVATE_PUBLICATION_COMMIT); + const hasEnvelopeMarker = existsSync(envelopeMarker); + const hasCommitMarker = existsSync(commitMarker); + if (!hasEnvelopeMarker && !hasCommitMarker) continue; + if (!hasEnvelopeMarker || !hasCommitMarker) { + fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + } + + const envelopeAttestation = attestDirectory(current, true); + let envelopeBytes: Buffer | undefined; + let commitBytes: Buffer | undefined; + try { + envelopeBytes = readPrivatePublicationControl( + envelopeMarker, + PRIVATE_PUBLICATION_ENVELOPE_BYTES.byteLength, + ); + if (!envelopeBytes.equals(PRIVATE_PUBLICATION_ENVELOPE_BYTES)) { + fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + } + commitBytes = readPrivatePublicationControl(commitMarker, 256); + const commitMatch = /^warpkeep-greater-realm-private-directory-commit-v1\n([^\n]+)\n$/u + .exec(commitBytes.toString('utf8')); + const payloadName = commitMatch?.[1]; + if (payloadName === undefined || !PRIVATE_PUBLICATION_PAYLOAD.test(payloadName)) { + fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + } + const expectedEntries = [ + PRIVATE_PUBLICATION_COMMIT, + PRIVATE_PUBLICATION_ENVELOPE, + payloadName, + ].sort(); + const actualEntries = readdirSync(current).sort(); + if ( + actualEntries.length !== expectedEntries.length + || actualEntries.some((entry, entryIndex) => entry !== expectedEntries[entryIndex]) + ) fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + const payload = join(current, payloadName); + attestDirectory(payload, true); + attestDirectory(current, true, envelopeAttestation.identity); + current = payload; + } catch (error) { + if ( + error instanceof GreaterRealmPrivateWorkspaceError + && error.code === 'GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE' + ) throw error; + fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + } finally { + envelopeBytes?.fill(0); + commitBytes?.fill(0); + } + } + if (!pathInside(workspaceRoot, current) || current === workspaceRoot) { + fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + } + return current; + }; + + const ensurePrivateDirectory = (path: string): void => { + const absolute = resolve(path); + if (!pathInside(workspaceRoot, absolute)) { + fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + } + attestWorkspaceBoundary(); + let current = workspaceRoot; + let parentAttestation = attestDirectory(current, true); + for (const component of relative(workspaceRoot, absolute).split(sep).filter(Boolean)) { + const next = join(current, component); + let created = false; + try { + mkdirSync(next, { mode: PRIVATE_DIRECTORY_MODE }); + created = true; + } catch { + if (!existsSync(next)) fail('GREATER_REALM_PRIVATE_DIRECTORY_CREATE_FAILED'); + } + if (created) hardenNewPrivateDirectory(next); + const nextAttestation = attestDirectory(next, true); + attestDirectory(current, true, parentAttestation.identity); + current = next; + parentAttestation = nextAttestation; + } + attestWorkspaceBoundary(); + attestPrivateDirectoryChain(absolute); + }; + + const ensureDirectory = (relativePath: string): string => { + const destination = resolveWorkspacePath(relativePath); + assertExistingPathHasNoSymlinks(destination); + ensurePrivateDirectory(destination); + if (!pathInside(workspaceRoot, destination)) { + fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + } + return destination; + }; + + const readFile = ( + relativePath: string, + maximumBytes = DEFAULT_MAXIMUM_FILE_BYTES, + ): Buffer => { + if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 0) { + fail('GREATER_REALM_PRIVATE_FILE_LIMIT_INVALID'); + } + const path = resolveWorkspacePath(relativePath); + assertExistingPathHasNoSymlinks(path); + const parentAttestation = attestPrivateDirectoryChain(dirname(path)); + const flags = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0); + let descriptor: number | undefined; + let bytes: Buffer | undefined; + try { + descriptor = openSync(path, flags); + const before = fstatSync(descriptor); + if ( + !before.isFile() + || before.size < 0 + || before.size > maximumBytes + || before.nlink !== 1 + || (process.getuid !== undefined && before.uid !== process.getuid()) + || (before.mode & 0o077) !== 0 + ) fail('GREATER_REALM_PRIVATE_FILE_INVALID'); + bytes = Buffer.alloc(before.size); + let offset = 0; + while (offset < bytes.byteLength) { + const count = readSync(descriptor, bytes, offset, bytes.byteLength - offset, null); + if (count <= 0) fail('GREATER_REALM_PRIVATE_READ_FAILED'); + offset += count; + } + const after = fstatSync(descriptor); + const current = lstatSync(path); + if ( + before.dev !== after.dev + || before.ino !== after.ino + || before.size !== after.size + || before.mtimeMs !== after.mtimeMs + || before.ctimeMs !== after.ctimeMs + || before.nlink !== after.nlink + || !current.isFile() + || current.isSymbolicLink() + || current.nlink !== 1 + || current.dev !== after.dev + || current.ino !== after.ino + ) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + assertRegularOwnerFileStatus(after); + attestPrivateDirectoryChain(dirname(path), parentAttestation); + closeSync(descriptor); + descriptor = undefined; + const result = bytes; + bytes = undefined; + return result; + } catch (error) { + bytes?.fill(0); + if (error instanceof GreaterRealmPrivateWorkspaceError) throw error; + fail('GREATER_REALM_PRIVATE_READ_FAILED'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } + }; + + const hasFile = (relativePath: string): boolean => { + const path = resolveWorkspacePath(relativePath); + assertExistingPathHasNoSymlinks(path); + const parentAttestation = attestPrivateDirectoryChain(dirname(path)); + if (!existsSync(path)) { + attestPrivateDirectoryChain(dirname(path), parentAttestation); + return false; + } + let descriptor: number | undefined; + try { + descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const opened = fstatSync(descriptor); + assertRegularOwnerFileStatus(opened); + const current = assertRegularOwnerFile(path); + if (!sameIdentity(opened, current)) { + fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + } + attestPrivateDirectoryChain(dirname(path), parentAttestation); + return true; + } catch (error) { + if (error instanceof GreaterRealmPrivateWorkspaceError) throw error; + fail('GREATER_REALM_PRIVATE_FILE_INVALID'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } + }; + + const writeFileAtomic = ( + relativePath: string, + bytes: Uint8Array, + maximumBytes = DEFAULT_MAXIMUM_FILE_BYTES, + ): void => { + if ( + !(bytes instanceof Uint8Array) + || !Number.isSafeInteger(maximumBytes) + || maximumBytes < 0 + || bytes.byteLength > maximumBytes + ) fail('GREATER_REALM_PRIVATE_FILE_LIMIT_INVALID'); + const destination = resolveWorkspacePath(relativePath); + const parentRelativePath = relativePath.split(/[\\/]/u).slice(0, -1).join('/'); + const parent = parentRelativePath + ? ensureDirectory(parentRelativePath) + : workspaceRoot; + const parentAttestation = attestPrivateDirectoryChain(parent); + if (dirname(destination) !== parent || existsSync(destination)) { + fail('GREATER_REALM_PRIVATE_DESTINATION_EXISTS'); + } + const temporaryName = `.${basename(destination)}.${randomUUID()}.tmp`; + const temporary = join(parent, temporaryName); + let descriptor: number | undefined; + let temporaryIdentity: FilesystemIdentity | undefined; + let destinationInstalled = false; + let writtenByteCount = 0; + let completed = false; + try { + descriptor = openSync( + temporary, + constants.O_CREAT + | constants.O_EXCL + | constants.O_WRONLY + | (constants.O_NOFOLLOW ?? 0), + PRIVATE_FILE_MODE, + ); + fchmodSync(descriptor, PRIVATE_FILE_MODE); + const created = fstatSync(descriptor); + assertRegularOwnerFileStatus(created); + temporaryIdentity = Object.freeze({ dev: created.dev, ino: created.ino }); + writeAll(descriptor, bytes, byteCount => { writtenByteCount = byteCount; }); + fsyncSync(descriptor); + const written = fstatSync(descriptor); + assertRegularOwnerFileStatus(written); + if ( + temporaryIdentity === undefined + || !sameIdentity(written, temporaryIdentity) + || written.size !== bytes.byteLength + ) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + const temporaryPathStatus = assertRegularOwnerFile(temporary); + if (!sameIdentity(temporaryPathStatus, temporaryIdentity)) { + fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + } + attestPrivateDirectoryChain(parent, parentAttestation); + linkSync(temporary, destination); + destinationInstalled = true; + const linked = lstatSync(destination); + if ( + !linked.isFile() + || linked.isSymbolicLink() + || linked.nlink !== 2 + || !sameIdentity(linked, temporaryIdentity) + ) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + unlinkSync(temporary); + const installed = assertRegularOwnerFile(destination); + if (!sameIdentity(installed, temporaryIdentity)) { + fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + } + const openInstalled = fstatSync(descriptor); + assertRegularOwnerFileStatus(openInstalled); + if (!sameIdentity(openInstalled, temporaryIdentity)) { + fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + } + attestPrivateDirectoryChain(parent, parentAttestation); + closeSync(descriptor); + descriptor = undefined; + completed = true; + } catch (error) { + if (error instanceof GreaterRealmPrivateWorkspaceError) throw error; + fail('GREATER_REALM_PRIVATE_WRITE_FAILED'); + } finally { + if (!completed && descriptor !== undefined) { + zeroizeOpenFile(descriptor, writtenByteCount); + } + if (descriptor !== undefined) { + try { closeSync(descriptor); } catch { /* Preserve the original fixed diagnostic. */ } + } + if (!completed && destinationInstalled && temporaryIdentity !== undefined) { + safeUnlinkIdentity(destination, temporaryIdentity); + } + if (temporaryIdentity !== undefined) safeUnlinkIdentity(temporary, temporaryIdentity); + } + }; + + const attestTreeAt = (start: string): GreaterRealmPrivateTreeAttestation => { + assertExistingPathHasNoSymlinks(start); + const startParent = start === workspaceRoot ? workspaceRoot : dirname(start); + const startParentAttestation = attestPrivateDirectoryChain(startParent); + let entryCount = 0; + let fileCount = 0; + let directoryCount = 0; + let byteCount = 0; + const visit = (path: string): void => { + const status = lstatSync(path); + if (status.isSymbolicLink()) fail('GREATER_REALM_PRIVATE_PATH_SYMLINK'); + if (status.isFile()) { + let descriptor: number | undefined; + try { + descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const opened = fstatSync(descriptor); + assertRegularOwnerFileStatus(opened); + const current = assertRegularOwnerFile(path); + if ( + !sameIdentity(status, opened) + || !sameIdentity(opened, current) + || status.size !== opened.size + || opened.size !== current.size + ) { + fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + } + } catch (error) { + if (error instanceof GreaterRealmPrivateWorkspaceError) throw error; + fail('GREATER_REALM_PRIVATE_FILE_INVALID'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } + entryCount += 1; + fileCount += 1; + byteCount += status.size; + } else if (status.isDirectory()) { + const directoryAttestation = attestDirectory(path, true); + entryCount += 1; + directoryCount += 1; + for (const entry of readdirSync(path, { withFileTypes: true }) + .sort((left, right) => left.name.localeCompare(right.name))) { + if ( + entry.name.includes('\0') + || entry.name.normalize('NFC') !== entry.name + ) fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + visit(join(path, entry.name)); + } + attestDirectory(path, true, directoryAttestation.identity); + } else { + fail('GREATER_REALM_PRIVATE_SPECIAL_FILE'); + } + if ( + entryCount > MAXIMUM_TREE_ENTRIES + || !Number.isSafeInteger(byteCount) + ) fail('GREATER_REALM_PRIVATE_TREE_LIMIT'); + }; + visit(start); + attestPrivateDirectoryChain(startParent, startParentAttestation); + return Object.freeze({ entryCount, fileCount, directoryCount, byteCount }); + }; + + const attestTree = (relativePath?: string): GreaterRealmPrivateTreeAttestation => ( + attestTreeAt(relativePath === undefined + ? workspaceRoot + : resolveWorkspacePath(relativePath)) + ); + + /** + * Remove only the exact entries reached below a pinned private directory. + * Cleanup never opens file contents and never follows a symbolic or hard + * link. This is deletion, not secure erasure: copy-on-write and journaled + * filesystems cannot promise physical media overwrites. + */ + const removePrivateTree = ( + path: string, + expectedRoot?: FilesystemIdentity, + ): void => { + const parent = dirname(path); + const parentAttestation = attestPrivateDirectoryChain(parent); + const visit = (entryPath: string, expected?: FilesystemIdentity): void => { + const status = lstatSync(entryPath); + if (expected !== undefined && !sameIdentity(status, expected)) { + fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + } + if (!status.isDirectory() || status.isSymbolicLink()) { + const identity = Object.freeze({ dev: status.dev, ino: status.ino }); + safeUnlinkIdentity(entryPath, identity); + if (existsSync(entryPath)) fail('GREATER_REALM_PRIVATE_STAGING_CLEANUP_FAILED'); + return; + } + const directory = attestDirectory(entryPath, true, expected); + for (const entry of readdirSync(entryPath, { withFileTypes: true }) + .sort((left, right) => left.name.localeCompare(right.name))) { + if ( + entry.name.includes('\0') + || entry.name.normalize('NFC') !== entry.name + ) fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + visit(join(entryPath, entry.name)); + } + attestDirectory(entryPath, true, directory.identity); + rmdirSync(entryPath); + if (existsSync(entryPath)) fail('GREATER_REALM_PRIVATE_STAGING_CLEANUP_FAILED'); + }; + visit(path, expectedRoot); + attestPrivateDirectoryChain(parent, parentAttestation); + }; + + const fsyncPrivateDirectory = ( + path: string, + expected: FilesystemIdentity, + ): void => { + let descriptor: number | undefined; + try { + descriptor = openSync( + path, + constants.O_RDONLY + | (constants.O_DIRECTORY ?? 0) + | (constants.O_NOFOLLOW ?? 0), + ); + const opened = fstatSync(descriptor); + ownerOnlyDirectoryStatus(opened); + if (!sameIdentity(opened, expected)) { + fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + } + fsyncSync(descriptor); + const current = lstatSync(path); + if (!sameIdentity(opened, current)) { + fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + } + } catch (error) { + if (error instanceof GreaterRealmPrivateWorkspaceError) throw error; + fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } + }; + + const writePrivatePublicationControlAtomic = ( + parent: string, + name: string, + bytes: Uint8Array, + parentAttestation: readonly DirectoryAttestation[], + ): FilesystemIdentity => { + if ( + !name.startsWith(PRIVATE_PUBLICATION_RESERVED_PREFIX) + || name.includes('/') + || name.includes('\\') + || bytes.byteLength < 1 + || bytes.byteLength > 4_096 + ) fail('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + const destination = join(parent, name); + const temporary = join( + parent, + `${PRIVATE_PUBLICATION_RESERVED_PREFIX}temporary-${randomUUID()}`, + ); + let descriptor: number | undefined; + let temporaryIdentity: FilesystemIdentity | undefined; + let destinationInstalled = false; + let completed = false; + try { + descriptor = openSync( + temporary, + constants.O_CREAT + | constants.O_EXCL + | constants.O_WRONLY + | (constants.O_NOFOLLOW ?? 0), + PRIVATE_FILE_MODE, + ); + fchmodSync(descriptor, PRIVATE_FILE_MODE); + const created = fstatSync(descriptor); + assertRegularOwnerFileStatus(created); + if ((created.mode & 0o777) !== PRIVATE_FILE_MODE) { + fail('GREATER_REALM_PRIVATE_FILE_INVALID'); + } + temporaryIdentity = Object.freeze({ dev: created.dev, ino: created.ino }); + writeAll(descriptor, bytes); + fsyncSync(descriptor); + const written = fstatSync(descriptor); + const temporaryPathStatus = assertRegularOwnerFile(temporary); + if ( + !sameIdentity(written, temporaryIdentity) + || !sameIdentity(temporaryPathStatus, temporaryIdentity) + || written.size !== bytes.byteLength + || temporaryPathStatus.size !== bytes.byteLength + || (written.mode & 0o777) !== PRIVATE_FILE_MODE + ) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + attestPrivateDirectoryChain(parent, parentAttestation); + linkSync(temporary, destination); + destinationInstalled = true; + const linked = lstatSync(destination); + const stillOpened = fstatSync(descriptor); + if ( + !linked.isFile() + || linked.isSymbolicLink() + || linked.nlink !== 2 + || stillOpened.nlink !== 2 + || !sameIdentity(linked, temporaryIdentity) + || !sameIdentity(stillOpened, temporaryIdentity) + ) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + unlinkSync(temporary); + const installed = assertRegularOwnerFile(destination); + const finalOpened = fstatSync(descriptor); + if ( + !sameIdentity(installed, temporaryIdentity) + || !sameIdentity(finalOpened, temporaryIdentity) + || installed.size !== bytes.byteLength + || finalOpened.size !== bytes.byteLength + || (installed.mode & 0o777) !== PRIVATE_FILE_MODE + ) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + attestPrivateDirectoryChain(parent, parentAttestation); + completed = true; + return temporaryIdentity; + } catch (error) { + if (error instanceof GreaterRealmPrivateWorkspaceError) throw error; + fail('GREATER_REALM_PRIVATE_WRITE_FAILED'); + } finally { + if (descriptor !== undefined) { + try { closeSync(descriptor); } catch { /* Preserve the fixed diagnostic. */ } + } + if (!completed && destinationInstalled && temporaryIdentity !== undefined) { + safeUnlinkIdentity(destination, temporaryIdentity); + } + if (temporaryIdentity !== undefined) safeUnlinkIdentity(temporary, temporaryIdentity); + } + }; + + const withExclusiveLock = async ( + relativePath: string, + operation: () => Promise, + ): Promise => { + const lockPath = resolveWorkspacePath(relativePath); + const parentPath = relativePath.split(/[\\/]/u).slice(0, -1).join('/'); + const parent = parentPath ? ensureDirectory(parentPath) : workspaceRoot; + if (dirname(lockPath) !== parent) { + fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + } + const parentAttestation = attestPrivateDirectoryChain(parent); + let descriptor: number; + try { + descriptor = openSync( + lockPath, + constants.O_CREAT + | constants.O_EXCL + | constants.O_WRONLY + | (constants.O_NOFOLLOW ?? 0), + PRIVATE_FILE_MODE, + ); + } catch { + fail('GREATER_REALM_PRIVATE_ALREADY_RUNNING'); + } + let lockIdentity: FilesystemIdentity | undefined; + const lockBytes = Buffer.from('greater-realm-private-lock-v1\n', 'utf8'); + try { + const created = fstatSync(descriptor); + lockIdentity = Object.freeze({ dev: created.dev, ino: created.ino }); + fchmodSync(descriptor, PRIVATE_FILE_MODE); + assertRegularOwnerFileStatus(fstatSync(descriptor)); + attestPrivateDirectoryChain(parent, parentAttestation); + writeAll(descriptor, lockBytes); + fsyncSync(descriptor); + return await operation(); + } finally { + lockBytes.fill(0); + let lockInvalid = false; + try { + const opened = fstatSync(descriptor); + const current = lstatSync(lockPath); + if ( + lockIdentity === undefined + || !sameIdentity(opened, lockIdentity) + || !sameIdentity(current, lockIdentity) + || current.nlink !== 1 + || opened.nlink !== 1 + ) lockInvalid = true; + } catch { + lockInvalid = true; + } + try { closeSync(descriptor); } catch { lockInvalid = true; } + if (lockIdentity !== undefined) safeUnlinkIdentity(lockPath, lockIdentity); + try { + attestPrivateDirectoryChain(parent, parentAttestation); + } catch { + lockInvalid = true; + } + if (lockInvalid) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + } + }; + + const withAtomicDirectoryPublish = async ( + relativePath: string, + operation: (stagedWorkspace: GreaterRealmPrivateWorkspace) => Promise, + ): Promise => { + if (typeof operation !== 'function') { + fail('GREATER_REALM_PRIVATE_STAGING_OPERATION_INVALID'); + } + const destinationComponents = validateRelativePath(relativePath); + if (destinationComponents[0] === '.pending') { + fail('GREATER_REALM_PRIVATE_STAGING_SCOPE_INVALID'); + } + const destinationRelativePath = destinationComponents.join('/'); + const destinationParentRelativePath = destinationComponents.slice(0, -1).join('/'); + const destinationParent = destinationParentRelativePath + ? ensureDirectory(destinationParentRelativePath) + : workspaceRoot; + const destinationName = destinationComponents.at(-1); + if (destinationName === undefined) fail('GREATER_REALM_PRIVATE_RELATIVE_PATH_INVALID'); + const destination = join(destinationParent, destinationName); + const claim = join(destinationParent, publicationClaimName(destinationName)); + const destinationParentAttestation = attestPrivateDirectoryChain(destinationParent); + assertExistingPathHasNoSymlinks(destination); + if (existsSync(destination) || existsSync(claim)) { + fail('GREATER_REALM_PRIVATE_DESTINATION_EXISTS'); + } + + const pendingRelativePath = '.pending'; + const pending = ensureDirectory(pendingRelativePath); + const pendingAttestation = attestPrivateDirectoryChain(pending); + const stagingRelativePath = `${pendingRelativePath}/${randomUUID()}`; + const staging = ensureDirectory(stagingRelativePath); + const stagingIdentity = attestDirectory(staging, true).identity; + + const translate = (logicalPath: string): string => { + const logicalComponents = validateRelativePath(logicalPath); + if ( + logicalComponents.length < destinationComponents.length + || destinationComponents.some((component, index) => logicalComponents[index] !== component) + ) fail('GREATER_REALM_PRIVATE_STAGING_SCOPE_INVALID'); + const suffix = logicalComponents.slice(destinationComponents.length); + return [stagingRelativePath, ...suffix].join('/'); + }; + const stagedWorkspace: GreaterRealmPrivateWorkspace = Object.freeze({ + root: staging, + ensureDirectory: path => ensureDirectory(translate(path)), + hasFile: path => hasFile(translate(path)), + readFile: (path, maximumBytes) => readFile(translate(path), maximumBytes), + writeFileAtomic: (path, bytes, maximumBytes) => ( + writeFileAtomic(translate(path), bytes, maximumBytes) + ), + attestTree: path => attestTree(path === undefined + ? stagingRelativePath + : translate(path)), + withExclusiveLock: (path, nestedOperation) => ( + withExclusiveLock(translate(path), nestedOperation) + ), + withAtomicDirectoryPublish: async () => ( + fail('GREATER_REALM_PRIVATE_STAGING_SCOPE_INVALID') + ), + }); + + let claimDescriptor: number | undefined; + let claimIdentity: FilesystemIdentity | undefined; + let envelopeIdentity: FilesystemIdentity | undefined; + let destinationCreatedByPublisher = false; + let claimRemoved = false; + const claimBytes = Buffer.from( + `warpkeep-greater-realm-private-directory-claim-v1\n${destinationName}\n`, + 'utf8', + ); + try { + const result = await operation(stagedWorkspace); + const stagedTree = attestTree(stagingRelativePath); + attestDirectory(staging, true, stagingIdentity); + attestPrivateDirectoryChain(destinationParent, destinationParentAttestation); + + try { + claimDescriptor = openSync( + claim, + constants.O_CREAT + | constants.O_EXCL + | constants.O_WRONLY + | (constants.O_NOFOLLOW ?? 0), + PRIVATE_FILE_MODE, + ); + } catch { + fail('GREATER_REALM_PRIVATE_DESTINATION_EXISTS'); + } + fchmodSync(claimDescriptor, PRIVATE_FILE_MODE); + const createdClaim = fstatSync(claimDescriptor); + assertRegularOwnerFileStatus(createdClaim); + if ((createdClaim.mode & 0o777) !== PRIVATE_FILE_MODE) { + fail('GREATER_REALM_PRIVATE_FILE_INVALID'); + } + claimIdentity = Object.freeze({ dev: createdClaim.dev, ino: createdClaim.ino }); + writeAll(claimDescriptor, claimBytes); + fsyncSync(claimDescriptor); + const writtenClaim = fstatSync(claimDescriptor); + const currentClaim = assertRegularOwnerFile(claim); + if ( + !sameIdentity(writtenClaim, claimIdentity) + || !sameIdentity(currentClaim, claimIdentity) + || writtenClaim.size !== claimBytes.byteLength + || currentClaim.size !== claimBytes.byteLength + || (writtenClaim.mode & 0o777) !== PRIVATE_FILE_MODE + ) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + attestPrivateDirectoryChain(destinationParent, destinationParentAttestation); + + try { + mkdirSync(destination, { mode: PRIVATE_DIRECTORY_MODE }); + destinationCreatedByPublisher = true; + } catch { + if (existsSync(destination)) fail('GREATER_REALM_PRIVATE_DESTINATION_EXISTS'); + fail('GREATER_REALM_PRIVATE_DIRECTORY_CREATE_FAILED'); + } + hardenNewPrivateDirectory(destination); + envelopeIdentity = attestDirectory(destination, true).identity; + const envelopeAttestation = attestPrivateDirectoryChain(destination); + writePrivatePublicationControlAtomic( + destination, + PRIVATE_PUBLICATION_ENVELOPE, + PRIVATE_PUBLICATION_ENVELOPE_BYTES, + envelopeAttestation, + ); + + const payloadName = `${PRIVATE_PUBLICATION_PAYLOAD_PREFIX}${randomUUID()}`; + const payload = join(destination, payloadName); + renameSync(staging, payload); + attestDirectory(payload, true, stagingIdentity); + const installedTree = attestTreeAt(payload); + if ( + installedTree.entryCount !== stagedTree.entryCount + || installedTree.fileCount !== stagedTree.fileCount + || installedTree.directoryCount !== stagedTree.directoryCount + || installedTree.byteCount !== stagedTree.byteCount + ) fail('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + + const commitBytes = Buffer.from( + `warpkeep-greater-realm-private-directory-commit-v1\n${payloadName}\n`, + 'utf8', + ); + try { + writePrivatePublicationControlAtomic( + destination, + PRIVATE_PUBLICATION_COMMIT, + commitBytes, + envelopeAttestation, + ); + } finally { + commitBytes.fill(0); + } + attestDirectory(destination, true, envelopeIdentity); + attestDirectory(payload, true, stagingIdentity); + attestPrivateDirectoryChain(destination, envelopeAttestation); + fsyncPrivateDirectory(destination, envelopeIdentity); + fsyncPrivateDirectory(destinationParent, destinationParentAttestation.at(-1)!.identity); + attestPrivateDirectoryChain(pending, pendingAttestation); + + const finalClaimDescriptor = fstatSync(claimDescriptor); + const finalClaimPath = assertRegularOwnerFile(claim); + if ( + !sameIdentity(finalClaimDescriptor, claimIdentity) + || !sameIdentity(finalClaimPath, claimIdentity) + || finalClaimDescriptor.nlink !== 1 + || finalClaimPath.nlink !== 1 + || finalClaimDescriptor.size !== claimBytes.byteLength + || finalClaimPath.size !== claimBytes.byteLength + ) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + closeSync(claimDescriptor); + claimDescriptor = undefined; + safeUnlinkIdentity(claim, claimIdentity); + if (existsSync(claim)) fail('GREATER_REALM_PRIVATE_FILE_CHANGED'); + claimRemoved = true; + return result; + } catch (error) { + let cleanupFailed = false; + try { + if (claimRemoved) { + cleanupFailed = true; + } else if (envelopeIdentity !== undefined && existsSync(destination)) { + removePrivateTree(destination, envelopeIdentity); + destinationCreatedByPublisher = false; + } else if (destinationCreatedByPublisher) { + // The name was exclusively claimed by this publisher, but its + // identity could not be pinned. Keep the claim in place so no + // reader can mistake the uncertain directory for a package. + cleanupFailed = true; + } + if (existsSync(staging)) { + removePrivateTree(staging, stagingIdentity); + } + if (claimDescriptor !== undefined) { + try { closeSync(claimDescriptor); } catch { cleanupFailed = true; } + claimDescriptor = undefined; + } + if (!destinationCreatedByPublisher && claimIdentity !== undefined) { + safeUnlinkIdentity(claim, claimIdentity); + } + if (!destinationCreatedByPublisher && existsSync(claim)) cleanupFailed = true; + attestPrivateDirectoryChain(destinationParent, destinationParentAttestation); + attestPrivateDirectoryChain(pending, pendingAttestation); + } catch { + cleanupFailed = true; + } + if (cleanupFailed) fail('GREATER_REALM_PRIVATE_STAGING_CLEANUP_FAILED'); + throw error; + } finally { + claimBytes.fill(0); + if (claimDescriptor !== undefined) { + try { closeSync(claimDescriptor); } catch { /* Catch path owns diagnostics. */ } + } + } + }; + + return Object.freeze({ + root: workspaceRoot, + ensureDirectory, + hasFile, + readFile, + writeFileAtomic, + attestTree, + withExclusiveLock, + withAtomicDirectoryPublish, + }); +} diff --git a/scripts/atlas/greater-realm-sanitized-review.ts b/scripts/atlas/greater-realm-sanitized-review.ts new file mode 100644 index 00000000..b0dad9eb --- /dev/null +++ b/scripts/atlas/greater-realm-sanitized-review.ts @@ -0,0 +1,724 @@ +import { createHash } from 'node:crypto'; + +import { + GREATER_REALM_CANDIDATE_HANDLE_PATTERN, + GREATER_REALM_GENERATOR_VERSION_PATTERN, + GREATER_REALM_MAXIMUM_ACTIVE_CELL_COUNT, + GREATER_REALM_MAXIMUM_CANDIDATE_COUNT, + GREATER_REALM_MINIMUM_ACTIVE_CELL_COUNT, + GREATER_REALM_MINIMUM_CANDIDATE_COUNT, + GREATER_REALM_PROOF_KEYS, + GREATER_REALM_REQUIRED_CASTLE_SLOT_COUNT, + GREATER_REALM_REQUIRED_GATE_COUNT, + GREATER_REALM_REVIEW_BATCH_HANDLE_PATTERN, + GREATER_REALM_SANITIZED_PRIVACY_BOUNDARY, + GREATER_REALM_SANITIZED_REVIEW_SCHEMA, + GREATER_REALM_SHA256_PATTERN, + GREATER_REALM_SOURCE_COMMIT_PATTERN, + type GreaterRealmGenerationPerformance, + type GreaterRealmBiomeMetrics, + type GreaterRealmGeologyCounts, + type GreaterRealmHydrologyCounts, + type GreaterRealmProofs, + type GreaterRealmQualityScores, + type GreaterRealmRegionSizeRange, + type GreaterRealmRegionSizeRanges, + type GreaterRealmSanitizedCandidate, + type GreaterRealmSanitizedCandidateSource, + type GreaterRealmSanitizedReview, + type GreaterRealmSanitizedReviewSource, + type GreaterRealmTierCounts, + type GreaterRealmTopographyMetrics, +} from './greater-realm-contracts'; + +const MAXIMUM_CANONICAL_DEPTH = 32; +const MAXIMUM_PERFORMANCE_MILLISECONDS = 7 * 24 * 60 * 60 * 1_000; +const MAXIMUM_PEAK_MEMORY_MIB = 1_048_576; +const MINIMUM_SIGNED_ELEVATION = -1_000_000; +const MAXIMUM_SIGNED_ELEVATION = 1_000_000; +const MAXIMUM_SLOPE = 2_000_000; +const MAXIMUM_VISUAL_CLASS_COUNT = 256; +const FORBIDDEN_KEY = /(?:^|_)(?:q|r|x|y|z)(?:$|_)|coord|latitude|longitude|seed|transform|translation|rotation|chunk|layoutdigest|stagedigest|packagedigest|preview|screenshot|thumbnail|image|filepath|pathname|url/iu; +const FORBIDDEN_STRING = /(?:data:image\/|(?:^|[\\/])[^\r\n]*\.(?:avif|gif|jpe?g|png|svg|webp)(?:$|[?#])|WKGR[_-]PRIVATE|warpkeep\.greater-realm\.private)/iu; + +type UnknownRecord = Readonly>; +type JsonValue = null | boolean | number | string | readonly JsonValue[] | { + readonly [key: string]: JsonValue; +}; + +export class GreaterRealmSanitizedReviewError extends Error { + constructor(readonly code: string) { + super(code); + this.name = 'GreaterRealmSanitizedReviewError'; + } +} + +function fail(code: string): never { + throw new GreaterRealmSanitizedReviewError(code); +} + +function exactRecord(value: unknown, keys: readonly string[]): UnknownRecord { + if ( + value === null + || typeof value !== 'object' + || Array.isArray(value) + || ![Object.prototype, null].includes(Object.getPrototypeOf(value)) + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + const ownKeys = Reflect.ownKeys(value); + if (ownKeys.some(key => typeof key !== 'string')) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + const actual = (ownKeys as string[]).sort(); + const expected = [...keys].sort(); + if ( + actual.length !== expected.length + || actual.some((key, index) => key !== expected[index]) + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + const descriptors = Object.getOwnPropertyDescriptors(value); + if (Object.values(descriptors).some(descriptor => !('value' in descriptor))) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + return value as UnknownRecord; +} + +function exactArray( + value: unknown, + minimumLength: number, + maximumLength: number, +): readonly unknown[] { + if ( + !Array.isArray(value) + || Object.getPrototypeOf(value) !== Array.prototype + || !Number.isSafeInteger(value.length) + || value.length < minimumLength + || value.length > maximumLength + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + const descriptors = Object.getOwnPropertyDescriptors(value); + const ownKeys = Reflect.ownKeys(value); + if (ownKeys.some(key => typeof key !== 'string') || ownKeys.length !== value.length + 1) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + for (let index = 0; index < value.length; index += 1) { + const descriptor = descriptors[String(index)]; + if (!descriptor || !('value' in descriptor) || !descriptor.enumerable) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + } + const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length'); + if (!lengthDescriptor || !('value' in lengthDescriptor)) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + return value; +} + +function assertNoPrivateMaterial( + value: unknown, + path: readonly string[] = [], + depth = 0, + ancestors = new Set(), +): void { + if (depth > MAXIMUM_CANONICAL_DEPTH) fail('GREATER_REALM_SANITIZED_REVIEW_PRIVATE_MATERIAL'); + if (typeof value === 'string') { + if (FORBIDDEN_STRING.test(value) || value.includes('\0')) { + fail('GREATER_REALM_SANITIZED_REVIEW_PRIVATE_MATERIAL'); + } + return; + } + if (value === null || typeof value !== 'object') return; + if (ancestors.has(value)) fail('GREATER_REALM_SANITIZED_REVIEW_PRIVATE_MATERIAL'); + ancestors.add(value); + try { + const descriptors = Object.getOwnPropertyDescriptors(value); + for (const key of Reflect.ownKeys(value)) { + if (typeof key !== 'string') fail('GREATER_REALM_SANITIZED_REVIEW_PRIVATE_MATERIAL'); + const descriptor = descriptors[key]; + if (!descriptor || !('value' in descriptor)) { + fail('GREATER_REALM_SANITIZED_REVIEW_PRIVATE_MATERIAL'); + } + const isAllowedDigest = path.length === 0 && key === 'reportDigest'; + if (!isAllowedDigest && FORBIDDEN_KEY.test(key.replaceAll('-', '_'))) { + fail('GREATER_REALM_SANITIZED_REVIEW_PRIVATE_MATERIAL'); + } + assertNoPrivateMaterial( + descriptor.value, + [...path, key], + depth + 1, + ancestors, + ); + } + } finally { + ancestors.delete(value); + } +} + +function safeInteger(value: unknown, maximum = Number.MAX_SAFE_INTEGER): number { + if (!Number.isSafeInteger(value) || (value as number) < 0 || (value as number) > maximum) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + return value as number; +} + +function signedSafeInteger(value: unknown, minimum: number, maximum: number): number { + if ( + !Number.isSafeInteger(value) + || (value as number) < minimum + || (value as number) > maximum + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + return value as number; +} + +function basisPoints(numerator: number, denominator: number): number { + if (denominator <= 0 || numerator < 0 || numerator > denominator) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + return Math.round((numerator * 10_000) / denominator); +} + +function tierCounts(value: unknown, activeCellCount: number): GreaterRealmTierCounts { + const row = exactRecord(value, ['tierI', 'tierII', 'tierIII']); + const result = Object.freeze({ + tierI: safeInteger(row.tierI, activeCellCount), + tierII: safeInteger(row.tierII, activeCellCount), + tierIII: safeInteger(row.tierIII, activeCellCount), + }); + if (result.tierI + result.tierII + result.tierIII !== activeCellCount) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + return result; +} + +function tierBasisPointCounts(counts: GreaterRealmTierCounts, activeCellCount: number) { + const tierI = basisPoints(counts.tierI, activeCellCount); + const tierII = basisPoints(counts.tierII, activeCellCount); + return Object.freeze({ tierI, tierII, tierIII: 10_000 - tierI - tierII }); +} + +function regionRange(value: unknown, tierCount: number, regionCount: number) { + const row = exactRecord(value, ['minimum', 'maximum']); + const result: GreaterRealmRegionSizeRange = Object.freeze({ + minimum: safeInteger(row.minimum, tierCount), + maximum: safeInteger(row.maximum, tierCount), + }); + if ( + result.minimum === 0 + || result.minimum > result.maximum + || result.minimum * regionCount > tierCount + || result.maximum * regionCount < tierCount + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + return result; +} + +function regionRanges( + value: unknown, + counts: GreaterRealmTierCounts, +): GreaterRealmRegionSizeRanges { + const row = exactRecord(value, ['tierI', 'tierII', 'tierIII']); + const result = Object.freeze({ + tierI: regionRange(row.tierI, counts.tierI, 6), + tierII: regionRange(row.tierII, counts.tierII, 3), + tierIII: regionRange(row.tierIII, counts.tierIII, 1), + }); + if ( + result.tierIII.minimum !== counts.tierIII + || result.tierIII.maximum !== counts.tierIII + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + return result; +} + +function hydrology(value: unknown): GreaterRealmHydrologyCounts { + const row = exactRecord(value, [ + 'majorOceanSeaBodies', + 'majorRivers', + 'minorStreams', + 'lakes', + ]); + return Object.freeze({ + majorOceanSeaBodies: safeInteger(row.majorOceanSeaBodies, 10_000), + majorRivers: safeInteger(row.majorRivers, 10_000), + minorStreams: safeInteger(row.minorStreams, 100_000), + lakes: safeInteger(row.lakes, 100_000), + }); +} + +function geology(value: unknown): GreaterRealmGeologyCounts { + const row = exactRecord(value, [ + 'pseudoTectonicDomains', + 'mountainSystems', + 'watersheds', + ]); + return Object.freeze({ + pseudoTectonicDomains: safeInteger(row.pseudoTectonicDomains, 1_000), + mountainSystems: safeInteger(row.mountainSystems, 10_000), + watersheds: safeInteger(row.watersheds, 100_000), + }); +} + +function topography( + value: unknown, + activeCellCount: number, + landCellCount: number, + waterCellCount: number, +): GreaterRealmTopographyMetrics { + const row = exactRecord(value, [ + 'signedElevationMinimum', + 'signedElevationMaximum', + 'slopeP50', + 'slopeP95', + 'ridgeCellCount', + 'plateauCellCount', + 'basinCellCount', + 'coastCellCount', + ]); + const result = Object.freeze({ + signedElevationMinimum: signedSafeInteger( + row.signedElevationMinimum, + MINIMUM_SIGNED_ELEVATION, + MAXIMUM_SIGNED_ELEVATION, + ), + signedElevationMaximum: signedSafeInteger( + row.signedElevationMaximum, + MINIMUM_SIGNED_ELEVATION, + MAXIMUM_SIGNED_ELEVATION, + ), + slopeP50: safeInteger(row.slopeP50, MAXIMUM_SLOPE), + slopeP95: safeInteger(row.slopeP95, MAXIMUM_SLOPE), + ridgeCellCount: safeInteger(row.ridgeCellCount, landCellCount), + plateauCellCount: safeInteger(row.plateauCellCount, landCellCount), + basinCellCount: safeInteger(row.basinCellCount, landCellCount), + coastCellCount: safeInteger(row.coastCellCount, activeCellCount), + }); + const elevationSpan = result.signedElevationMaximum - result.signedElevationMinimum; + if ( + result.signedElevationMinimum >= result.signedElevationMaximum + || (waterCellCount > 0 && result.signedElevationMinimum > 0) + || (landCellCount > 0 && result.signedElevationMaximum <= 0) + || result.slopeP50 > result.slopeP95 + || result.slopeP95 > elevationSpan + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + return result; +} + +function biomes(value: unknown): GreaterRealmBiomeMetrics { + const row = exactRecord(value, [ + 'visualClassCount', + 'minimumPerRegionVisualClassCount', + 'minimumTierIVisualClassCount', + 'minimumTierIIVisualClassCount', + 'tierIIIVisualClassCount', + 'minimumTierIMajorVisualClassCount', + 'minimumTierITransitionVisualClassCount', + 'minimumTierIIMajorVisualClassCount', + 'tierIIIMajorVisualClassCount', + 'maximumTierISingleBiomeShareBasisPoints', + 'incompatibleVisualAdjacencyCount', + 'incompatibleBiomeLandformPairCount', + ]); + const result = Object.freeze({ + visualClassCount: safeInteger(row.visualClassCount, MAXIMUM_VISUAL_CLASS_COUNT), + minimumPerRegionVisualClassCount: safeInteger( + row.minimumPerRegionVisualClassCount, + MAXIMUM_VISUAL_CLASS_COUNT, + ), + minimumTierIVisualClassCount: safeInteger( + row.minimumTierIVisualClassCount, + MAXIMUM_VISUAL_CLASS_COUNT, + ), + minimumTierIIVisualClassCount: safeInteger( + row.minimumTierIIVisualClassCount, + MAXIMUM_VISUAL_CLASS_COUNT, + ), + tierIIIVisualClassCount: safeInteger( + row.tierIIIVisualClassCount, + MAXIMUM_VISUAL_CLASS_COUNT, + ), + minimumTierIMajorVisualClassCount: safeInteger( + row.minimumTierIMajorVisualClassCount, + MAXIMUM_VISUAL_CLASS_COUNT, + ), + minimumTierITransitionVisualClassCount: safeInteger( + row.minimumTierITransitionVisualClassCount, + MAXIMUM_VISUAL_CLASS_COUNT, + ), + minimumTierIIMajorVisualClassCount: safeInteger( + row.minimumTierIIMajorVisualClassCount, + MAXIMUM_VISUAL_CLASS_COUNT, + ), + tierIIIMajorVisualClassCount: safeInteger( + row.tierIIIMajorVisualClassCount, + MAXIMUM_VISUAL_CLASS_COUNT, + ), + maximumTierISingleBiomeShareBasisPoints: safeInteger( + row.maximumTierISingleBiomeShareBasisPoints, + 10_000, + ), + incompatibleVisualAdjacencyCount: safeInteger( + row.incompatibleVisualAdjacencyCount, + 1_000_000, + ), + incompatibleBiomeLandformPairCount: safeInteger( + row.incompatibleBiomeLandformPairCount, + 1_000_000, + ), + }); + if ( + result.visualClassCount === 0 + || result.minimumPerRegionVisualClassCount === 0 + || result.minimumTierIVisualClassCount === 0 + || result.minimumTierIIVisualClassCount === 0 + || result.tierIIIVisualClassCount === 0 + || result.minimumTierIMajorVisualClassCount === 0 + || result.minimumTierITransitionVisualClassCount === 0 + || result.minimumTierIIMajorVisualClassCount === 0 + || result.tierIIIMajorVisualClassCount === 0 + || result.minimumTierIVisualClassCount > result.visualClassCount + || result.minimumTierIIVisualClassCount > result.visualClassCount + || result.tierIIIVisualClassCount > result.visualClassCount + || result.minimumTierIMajorVisualClassCount > result.minimumTierIVisualClassCount + || result.minimumTierITransitionVisualClassCount > result.minimumTierIVisualClassCount + || result.minimumTierIIMajorVisualClassCount > result.minimumTierIIVisualClassCount + || result.tierIIIMajorVisualClassCount > result.tierIIIVisualClassCount + || result.minimumPerRegionVisualClassCount !== Math.min( + result.minimumTierIVisualClassCount, + result.minimumTierIIVisualClassCount, + result.tierIIIVisualClassCount, + ) + || result.maximumTierISingleBiomeShareBasisPoints === 0 + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + return result; +} + +function quality(value: unknown): GreaterRealmQualityScores { + const row = exactRecord(value, [ + 'naturalnessBasisPoints', + 'axialArtifactBasisPoints', + 'ridgeContinuityBasisPoints', + 'hydrologyCoherenceBasisPoints', + ]); + return Object.freeze({ + naturalnessBasisPoints: safeInteger(row.naturalnessBasisPoints, 10_000), + axialArtifactBasisPoints: safeInteger(row.axialArtifactBasisPoints, 10_000), + ridgeContinuityBasisPoints: safeInteger(row.ridgeContinuityBasisPoints, 10_000), + hydrologyCoherenceBasisPoints: safeInteger(row.hydrologyCoherenceBasisPoints, 10_000), + }); +} + +function proofs(value: unknown): GreaterRealmProofs { + const row = exactRecord(value, GREATER_REALM_PROOF_KEYS); + return Object.freeze(Object.fromEntries(GREATER_REALM_PROOF_KEYS.map(key => { + if (typeof row[key] !== 'boolean') fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + return [key, row[key]]; + }))) as GreaterRealmProofs; +} + +function performance(value: unknown): GreaterRealmGenerationPerformance { + const row = exactRecord(value, [ + 'generationMillisecondsRounded', + 'processPeakMemoryMiBRounded', + ]); + return Object.freeze({ + generationMillisecondsRounded: safeInteger( + row.generationMillisecondsRounded, + MAXIMUM_PERFORMANCE_MILLISECONDS, + ), + processPeakMemoryMiBRounded: safeInteger( + row.processPeakMemoryMiBRounded, + MAXIMUM_PEAK_MEMORY_MIB, + ), + }); +} + +const SOURCE_CANDIDATE_KEYS = Object.freeze([ + 'candidateHandle', + 'eligible', + 'activeCellCount', + 'landCellCount', + 'waterCellCount', + 'tierCellCounts', + 'regionSizeRanges', + 'hydrology', + 'geology', + 'topography', + 'biomes', + 'quality', + 'gateCount', + 'castleSlotCount', + 'proofs', + 'performance', +]); + +const PUBLIC_CANDIDATE_KEYS = Object.freeze([ + ...SOURCE_CANDIDATE_KEYS, + 'insideApprovedRange', + 'landBasisPoints', + 'waterBasisPoints', + 'tierBasisPoints', +]); + +function candidate( + value: unknown, + publicShape: boolean, +): GreaterRealmSanitizedCandidate { + const row = exactRecord(value, publicShape ? PUBLIC_CANDIDATE_KEYS : SOURCE_CANDIDATE_KEYS); + if ( + typeof row.candidateHandle !== 'string' + || !GREATER_REALM_CANDIDATE_HANDLE_PATTERN.test(row.candidateHandle) + || typeof row.eligible !== 'boolean' + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + const activeCellCount = safeInteger(row.activeCellCount, 1_000_000); + if (activeCellCount === 0) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + const landCellCount = safeInteger(row.landCellCount, activeCellCount); + const waterCellCount = safeInteger(row.waterCellCount, activeCellCount); + if (landCellCount + waterCellCount !== activeCellCount) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + const parsedTierCounts = tierCounts(row.tierCellCounts, activeCellCount); + const parsedProofs = proofs(row.proofs); + const parsedTopography = topography( + row.topography, + activeCellCount, + landCellCount, + waterCellCount, + ); + const parsedBiomes = biomes(row.biomes); + const insideApprovedRange = activeCellCount >= GREATER_REALM_MINIMUM_ACTIVE_CELL_COUNT + && activeCellCount <= GREATER_REALM_MAXIMUM_ACTIVE_CELL_COUNT; + const derivedTierBasisPoints = tierBasisPointCounts(parsedTierCounts, activeCellCount); + if (publicShape) { + const providedTierBasisPoints = exactRecord( + row.tierBasisPoints, + ['tierI', 'tierII', 'tierIII'], + ); + if ( + providedTierBasisPoints.tierI !== derivedTierBasisPoints.tierI + || providedTierBasisPoints.tierII !== derivedTierBasisPoints.tierII + || providedTierBasisPoints.tierIII !== derivedTierBasisPoints.tierIII + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + const parsed = Object.freeze({ + candidateHandle: row.candidateHandle, + eligible: row.eligible, + activeCellCount, + landCellCount, + waterCellCount, + tierCellCounts: parsedTierCounts, + regionSizeRanges: regionRanges(row.regionSizeRanges, parsedTierCounts), + hydrology: hydrology(row.hydrology), + geology: geology(row.geology), + topography: parsedTopography, + biomes: parsedBiomes, + quality: quality(row.quality), + gateCount: safeInteger(row.gateCount, 10_000), + castleSlotCount: safeInteger(row.castleSlotCount, 100_000), + proofs: parsedProofs, + performance: performance(row.performance), + insideApprovedRange, + landBasisPoints: basisPoints(landCellCount, activeCellCount), + waterBasisPoints: 10_000 - basisPoints(landCellCount, activeCellCount), + tierBasisPoints: derivedTierBasisPoints, + }); + if (publicShape && ( + row.insideApprovedRange !== parsed.insideApprovedRange + || row.landBasisPoints !== parsed.landBasisPoints + || row.waterBasisPoints !== parsed.waterBasisPoints + )) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + if (parsed.eligible && ( + !parsed.insideApprovedRange + || parsed.landBasisPoints < 6_200 + || parsed.landBasisPoints > 7_200 + || parsed.tierBasisPoints.tierI < 6_800 + || parsed.tierBasisPoints.tierI > 7_400 + || parsed.tierBasisPoints.tierII < 2_200 + || parsed.tierBasisPoints.tierII > 2_700 + || parsed.tierBasisPoints.tierIII < 300 + || parsed.tierBasisPoints.tierIII > 600 + || parsed.regionSizeRanges.tierIII.maximum >= parsed.regionSizeRanges.tierI.minimum + || parsed.regionSizeRanges.tierIII.maximum >= parsed.regionSizeRanges.tierII.minimum + || parsed.gateCount !== GREATER_REALM_REQUIRED_GATE_COUNT + || parsed.castleSlotCount !== GREATER_REALM_REQUIRED_CASTLE_SLOT_COUNT + || Object.values(parsed.proofs).some(result => result !== true) + || parsed.geology.pseudoTectonicDomains < 7 + || parsed.geology.pseudoTectonicDomains > 12 + || parsed.hydrology.majorOceanSeaBodies < 4 + || parsed.hydrology.majorOceanSeaBodies > 6 + || parsed.hydrology.majorRivers < 48 + || parsed.hydrology.majorRivers > 72 + || parsed.hydrology.minorStreams < 120 + || parsed.hydrology.minorStreams > 240 + || parsed.hydrology.lakes < 48 + || parsed.hydrology.lakes > 96 + || parsed.topography.signedElevationMinimum >= 0 + || parsed.topography.signedElevationMaximum <= 0 + || parsed.topography.slopeP50 === 0 + || parsed.topography.slopeP95 <= parsed.topography.slopeP50 + || parsed.topography.ridgeCellCount === 0 + || parsed.topography.plateauCellCount === 0 + || parsed.topography.basinCellCount === 0 + || parsed.topography.coastCellCount === 0 + || parsed.biomes.visualClassCount < 8 + || parsed.biomes.minimumPerRegionVisualClassCount < 3 + || parsed.biomes.minimumTierIVisualClassCount < 6 + || parsed.biomes.minimumTierIIVisualClassCount < 5 + || parsed.biomes.tierIIIVisualClassCount < 3 + || parsed.biomes.minimumTierIMajorVisualClassCount < 4 + || parsed.biomes.minimumTierITransitionVisualClassCount < 2 + || parsed.biomes.minimumTierIIMajorVisualClassCount < 5 + || parsed.biomes.tierIIIMajorVisualClassCount < 3 + || parsed.biomes.maximumTierISingleBiomeShareBasisPoints > 5_500 + || parsed.biomes.incompatibleVisualAdjacencyCount !== 0 + || parsed.biomes.incompatibleBiomeLandformPairCount !== 0 + )) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + return parsed; +} + +function validateCommonReviewFields(row: UnknownRecord) { + const candidateValues = exactArray( + row.candidates, + GREATER_REALM_MINIMUM_CANDIDATE_COUNT, + GREATER_REALM_MAXIMUM_CANDIDATE_COUNT, + ); + if ( + typeof row.generatorVersion !== 'string' + || !GREATER_REALM_GENERATOR_VERSION_PATTERN.test(row.generatorVersion) + || typeof row.sourceCommit !== 'string' + || !GREATER_REALM_SOURCE_COMMIT_PATTERN.test(row.sourceCommit) + || typeof row.reviewBatchHandle !== 'string' + || !GREATER_REALM_REVIEW_BATCH_HANDLE_PATTERN.test(row.reviewBatchHandle) + || (row.selectionStatus !== 'pending' && row.selectionStatus !== 'selected') + || (row.selectedCandidateHandle !== null && ( + typeof row.selectedCandidateHandle !== 'string' + || !GREATER_REALM_CANDIDATE_HANDLE_PATTERN.test(row.selectedCandidateHandle) + )) + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + const candidates: GreaterRealmSanitizedCandidate[] = []; + for (let index = 0; index < candidateValues.length; index += 1) { + candidates.push(candidate(candidateValues[index], 'schema' in row)); + } + const handles = candidates.map(entry => entry.candidateHandle); + if ( + new Set(handles).size !== handles.length + || handles.some((handle, index) => index > 0 && handles[index - 1]! >= handle) + || candidates.filter(entry => entry.eligible).length < GREATER_REALM_MINIMUM_CANDIDATE_COUNT + || (row.selectionStatus === 'pending' && row.selectedCandidateHandle !== null) + || (row.selectionStatus === 'selected' && ( + row.selectedCandidateHandle === null + || !candidates.some(entry => ( + entry.candidateHandle === row.selectedCandidateHandle && entry.eligible + )) + )) + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + return Object.freeze({ + generatorVersion: row.generatorVersion, + sourceCommit: row.sourceCommit, + reviewBatchHandle: row.reviewBatchHandle, + selectionStatus: row.selectionStatus, + selectedCandidateHandle: row.selectedCandidateHandle, + candidates: Object.freeze(candidates), + }) as Omit & { + readonly candidates: readonly GreaterRealmSanitizedCandidate[]; + }; +} + +function canonicalValue(value: JsonValue, depth = 0): JsonValue { + if (depth > MAXIMUM_CANONICAL_DEPTH) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + if (Array.isArray(value)) return value.map(entry => canonicalValue(entry, depth + 1)); + if (value !== null && typeof value === 'object') { + return Object.freeze(Object.fromEntries(Object.entries(value) + .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)) + .map(([key, entry]) => [key, canonicalValue(entry, depth + 1)]))) as JsonValue; + } + if (typeof value === 'number' && !Number.isSafeInteger(value)) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + return value; +} + +function reportWithoutDigest( + common: ReturnType, +): Omit { + return Object.freeze({ + schema: GREATER_REALM_SANITIZED_REVIEW_SCHEMA, + generatorVersion: common.generatorVersion, + sourceCommit: common.sourceCommit, + reviewBatchHandle: common.reviewBatchHandle, + selectionStatus: common.selectionStatus, + selectedCandidateHandle: common.selectedCandidateHandle, + candidateCount: common.candidates.length, + candidates: common.candidates, + privacyBoundary: GREATER_REALM_SANITIZED_PRIVACY_BOUNDARY, + }); +} + +export function greaterRealmSanitizedReviewDigest( + value: Omit, +): string { + const canonical = JSON.stringify(canonicalValue(value as unknown as JsonValue)); + return createHash('sha256').update(canonical, 'utf8').digest('hex'); +} + +/** Construct an aggregate public artifact. Private manifests are never accepted. */ +export function createGreaterRealmSanitizedReview( + value: unknown, +): GreaterRealmSanitizedReview { + assertNoPrivateMaterial(value); + const row = exactRecord(value, [ + 'generatorVersion', + 'sourceCommit', + 'reviewBatchHandle', + 'selectionStatus', + 'selectedCandidateHandle', + 'candidates', + ]); + const sourceCandidates = exactArray( + row.candidates, + GREATER_REALM_MINIMUM_CANDIDATE_COUNT, + GREATER_REALM_MAXIMUM_CANDIDATE_COUNT, + ); + const sorted = Object.freeze([...sourceCandidates].sort((left, right) => { + const leftHandle = exactRecord(left, SOURCE_CANDIDATE_KEYS).candidateHandle; + const rightHandle = exactRecord(right, SOURCE_CANDIDATE_KEYS).candidateHandle; + if (typeof leftHandle !== 'string' || typeof rightHandle !== 'string') { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + return leftHandle < rightHandle ? -1 : leftHandle > rightHandle ? 1 : 0; + })); + const common = validateCommonReviewFields({ ...row, candidates: sorted }); + const body = reportWithoutDigest(common); + return Object.freeze({ ...body, reportDigest: greaterRealmSanitizedReviewDigest(body) }); +} + +/** Parse an already-public artifact using recursive exact-key validation. */ +export function parseGreaterRealmSanitizedReview(value: unknown): GreaterRealmSanitizedReview { + assertNoPrivateMaterial(value); + const row = exactRecord(value, [ + 'schema', + 'generatorVersion', + 'sourceCommit', + 'reviewBatchHandle', + 'selectionStatus', + 'selectedCandidateHandle', + 'candidateCount', + 'candidates', + 'privacyBoundary', + 'reportDigest', + ]); + if ( + row.schema !== GREATER_REALM_SANITIZED_REVIEW_SCHEMA + || row.privacyBoundary !== GREATER_REALM_SANITIZED_PRIVACY_BOUNDARY + || typeof row.reportDigest !== 'string' + || !GREATER_REALM_SHA256_PATTERN.test(row.reportDigest) + ) fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + const common = validateCommonReviewFields(row); + if (row.candidateCount !== common.candidates.length) { + fail('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + } + const body = reportWithoutDigest(common); + if (greaterRealmSanitizedReviewDigest(body) !== row.reportDigest) { + fail('GREATER_REALM_SANITIZED_REVIEW_DIGEST_MISMATCH'); + } + return Object.freeze({ ...body, reportDigest: row.reportDigest }); +} + +export function serializeGreaterRealmSanitizedReview(value: unknown): string { + const report = parseGreaterRealmSanitizedReview(value); + return `${JSON.stringify(canonicalValue(report as unknown as JsonValue), null, 2)}\n`; +} diff --git a/scripts/atlas/greater-realm-terrain.ts b/scripts/atlas/greater-realm-terrain.ts new file mode 100644 index 00000000..dbf41a55 --- /dev/null +++ b/scripts/atlas/greater-realm-terrain.ts @@ -0,0 +1,893 @@ +import { Buffer, constants as bufferConstants } from 'node:buffer'; +import { createHash } from 'node:crypto'; + +export const GREATER_REALM_TERRAIN_CORE_VERSION = 'greater-realm-terrain-v1' as const; + +export type AxialCoordinate = Readonly<{ + q: number; + r: number; +}>; + +/** + * Four private 32-bit words keep the offline candidate seed space at 128 bits. + * + * Candidate generation uses a `Uint32Array` so those words can be overwritten + * after the private package is written. The readonly tuple remains supported + * for small deterministic fixtures. + */ +export type GreaterRealmTerrainSeed = + | number + | readonly [number, number, number, number] + | Uint32Array; + +type GreaterRealmTerrainSeedWords = Readonly>; + +export const GREATER_REALM_AXIAL_DIRECTIONS = Object.freeze([ + Object.freeze({ q: 1, r: 0 }), + Object.freeze({ q: 1, r: -1 }), + Object.freeze({ q: 0, r: -1 }), + Object.freeze({ q: -1, r: 0 }), + Object.freeze({ q: -1, r: 1 }), + Object.freeze({ q: 0, r: 1 }), +] as const); + +const INT32_MIN = -0x8000_0000; +const INT32_MAX = 0x7fff_ffff; +const UINT32_MAX = 0xffff_ffff; +const UINT64_MAX = 0xffff_ffff_ffff_ffffn; +const UINT16_RANGE = 0x1_0000; +const THERMAL_TRANSFER_SCALE_MAX = 0xffff; +const MAX_OFFLINE_RELAXATION_PASSES = 10_000; +const NEIGHBOR_COUNT = GREATER_REALM_AXIAL_DIRECTIONS.length; + +function fail(code: string): never { + throw new Error(code); +} + +function assertSafeInteger(value: number, code: string): void { + if (!Number.isSafeInteger(value)) fail(code); +} + +function assertInt32(value: number, code: string): void { + assertSafeInteger(value, code); + if (value < INT32_MIN || value > INT32_MAX) fail(code); +} + +function assertUint32(value: number, code: string): void { + assertSafeInteger(value, code); + if (value < 0 || value > UINT32_MAX) fail(code); +} + +function checkedInt32(value: number, code: string): number { + assertInt32(value, code); + return value; +} + +function checkedSafeSum(first: number, second: number, code: string): number { + const sum = first + second; + if (!Number.isSafeInteger(sum)) fail(code); + return sum; +} + +function roundDivide(numerator: number, denominator: number): number { + if (!Number.isSafeInteger(numerator) || !Number.isSafeInteger(denominator) || denominator <= 0) { + fail('GREATER_REALM_INTEGER_DIVISION_INVALID'); + } + const sign = numerator < 0 ? -1 : 1; + const magnitude = Math.abs(numerator); + const quotient = Math.floor(magnitude / denominator); + const remainder = magnitude % denominator; + return sign * (quotient + (remainder * 2 >= denominator ? 1 : 0)); +} + +export function greaterRealmAxialKey(coordinate: AxialCoordinate): string { + assertInt32(coordinate.q, 'GREATER_REALM_AXIAL_Q_INVALID'); + assertInt32(coordinate.r, 'GREATER_REALM_AXIAL_R_INVALID'); + return `${coordinate.q},${coordinate.r}`; +} + +export function greaterRealmHexDistance( + first: AxialCoordinate, + second: AxialCoordinate = { q: 0, r: 0 }, +): number { + assertInt32(first.q, 'GREATER_REALM_AXIAL_Q_INVALID'); + assertInt32(first.r, 'GREATER_REALM_AXIAL_R_INVALID'); + assertInt32(second.q, 'GREATER_REALM_AXIAL_Q_INVALID'); + assertInt32(second.r, 'GREATER_REALM_AXIAL_R_INVALID'); + const q = first.q - second.q; + const r = first.r - second.r; + const s = -q - r; + if (!Number.isSafeInteger(q) || !Number.isSafeInteger(r) || !Number.isSafeInteger(s)) { + fail('GREATER_REALM_AXIAL_DISTANCE_OVERFLOW'); + } + return Math.max(Math.abs(q), Math.abs(r), Math.abs(s)); +} + +export function greaterRealmAxialNeighbors( + coordinate: AxialCoordinate, +): readonly AxialCoordinate[] { + assertInt32(coordinate.q, 'GREATER_REALM_AXIAL_Q_INVALID'); + assertInt32(coordinate.r, 'GREATER_REALM_AXIAL_R_INVALID'); + return GREATER_REALM_AXIAL_DIRECTIONS.map((direction) => Object.freeze({ + q: checkedInt32(coordinate.q + direction.q, 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW'), + r: checkedInt32(coordinate.r + direction.r, 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW'), + })); +} + +export type IndexedAxialGrid = Readonly<{ + cellCount: number; + q: Int32Array; + r: Int32Array; + /** Six canonical neighbor slots per cell; `-1` means outside the active mask. */ + neighbors: Int32Array; + indexOf: (coordinate: AxialCoordinate) => number; + /** Best-effort removal of the private coordinate lookup when an atlas is retired. */ + clearIndex?: () => void; +}>; + +/** + * Canonically index an arbitrary six-connected active mask. + * + * Coordinates are sorted independently of caller order so every downstream + * queue tie-break, digest, and counter-addressed field has one stable index. + */ +export function indexGreaterRealmAxialGrid( + coordinates: readonly AxialCoordinate[], +): IndexedAxialGrid { + if (coordinates.length === 0 || coordinates.length > INT32_MAX) { + fail('GREATER_REALM_AXIAL_GRID_SIZE_INVALID'); + } + const canonical = coordinates.map((coordinate) => { + assertInt32(coordinate.q, 'GREATER_REALM_AXIAL_Q_INVALID'); + assertInt32(coordinate.r, 'GREATER_REALM_AXIAL_R_INVALID'); + return { q: coordinate.q, r: coordinate.r }; + }).sort((first, second) => first.q - second.q || first.r - second.r); + + const q = new Int32Array(canonical.length); + const r = new Int32Array(canonical.length); + const indexByKey = new Map(); + for (let index = 0; index < canonical.length; index += 1) { + const coordinate = canonical[index]!; + const key = `${coordinate.q},${coordinate.r}`; + if (indexByKey.has(key)) fail('GREATER_REALM_AXIAL_COORDINATE_DUPLICATE'); + q[index] = coordinate.q; + r[index] = coordinate.r; + indexByKey.set(key, index); + } + + const neighbors = new Int32Array(canonical.length * NEIGHBOR_COUNT); + neighbors.fill(-1); + for (let index = 0; index < canonical.length; index += 1) { + for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + const direction = GREATER_REALM_AXIAL_DIRECTIONS[directionIndex]!; + const neighborQ = checkedInt32(q[index]! + direction.q, 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW'); + const neighborR = checkedInt32(r[index]! + direction.r, 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW'); + neighbors[index * NEIGHBOR_COUNT + directionIndex] = indexByKey.get( + `${neighborQ},${neighborR}`, + ) ?? -1; + } + } + + return Object.freeze({ + cellCount: canonical.length, + q, + r, + neighbors, + indexOf(coordinate: AxialCoordinate) { + assertInt32(coordinate.q, 'GREATER_REALM_AXIAL_Q_INVALID'); + assertInt32(coordinate.r, 'GREATER_REALM_AXIAL_R_INVALID'); + return indexByKey.get(`${coordinate.q},${coordinate.r}`) ?? -1; + }, + clearIndex() { + indexByKey.clear(); + }, + }); +} + +function avalancheUint32(value: number): number { + let mixed = value >>> 0; + mixed ^= mixed >>> 16; + mixed = Math.imul(mixed, 0x7feb_352d); + mixed ^= mixed >>> 15; + mixed = Math.imul(mixed, 0x846c_a68b); + mixed ^= mixed >>> 16; + return mixed >>> 0; +} + +function terrainSeedWords( + seed: GreaterRealmTerrainSeed, +): GreaterRealmTerrainSeedWords { + if (typeof seed === 'number') { + assertUint32(seed, 'GREATER_REALM_TERRAIN_SEED_INVALID'); + return [seed, 0, 0, 0]; + } + if (seed.length !== 4) fail('GREATER_REALM_TERRAIN_SEED_INVALID'); + for (const word of seed) assertUint32(word, 'GREATER_REALM_TERRAIN_SEED_INVALID'); + return seed; +} + +function rotateLeftUint32(value: number, shift: number): number { + return ((value << shift) | (value >>> (32 - shift))) >>> 0; +} + +function counterRandomFromSeedWords( + seed: GreaterRealmTerrainSeedWords, + channel: number, + q: number, + r: number, + sampleIndex = 0, +): number { + let first = (seed[0] ^ 0x6170_7865 ^ channel) >>> 0; + let second = (seed[1] ^ 0x3320_646e ^ (q >>> 0)) >>> 0; + let third = (seed[2] ^ 0x7962_2d32 ^ (r >>> 0)) >>> 0; + let fourth = (seed[3] ^ 0x6b20_6574 ^ sampleIndex) >>> 0; + const initialFirst = first; + const initialSecond = second; + const initialThird = third; + const initialFourth = fourth; + + // Integer ARX mixing keeps all four seed words live without mutable state. + // This is a reproducibility primitive, not an authentication mechanism. + for (let round = 0; round < 8; round += 1) { + first = (first + second) >>> 0; + fourth = rotateLeftUint32(fourth ^ first, 16); + third = (third + fourth) >>> 0; + second = rotateLeftUint32(second ^ third, 12); + first = (first + second) >>> 0; + fourth = rotateLeftUint32(fourth ^ first, 8); + third = (third + fourth) >>> 0; + second = rotateLeftUint32(second ^ third, 7); + first = (first ^ Math.imul(round + 1, 0x9e37_79b9)) >>> 0; + } + + first = (first + initialFirst) >>> 0; + second = (second + initialSecond) >>> 0; + third = (third + initialThird) >>> 0; + fourth = (fourth + initialFourth) >>> 0; + return avalancheUint32(first ^ second ^ third ^ fourth); +} + +/** Stable FNV-1a identifier for named, independently addressable fields. */ +export function greaterRealmTerrainChannelId(channel: string): number { + if (channel.length === 0) fail('GREATER_REALM_TERRAIN_CHANNEL_INVALID'); + let hash = 0x811c_9dc5; + for (let index = 0; index < channel.length; index += 1) { + hash ^= channel.charCodeAt(index); + hash = Math.imul(hash, 0x0100_0193); + } + return avalancheUint32(hash); +} + +/** + * Stateless integer randomness addressed by seed, channel, coordinate, and sample. + * Adding a generation stage or traversing cells in another order cannot shift it. + */ +export function greaterRealmCounterRandomU32( + seed: GreaterRealmTerrainSeed, + channel: number, + q: number, + r: number, + sampleIndex = 0, +): number { + assertUint32(channel, 'GREATER_REALM_TERRAIN_CHANNEL_INVALID'); + assertInt32(q, 'GREATER_REALM_AXIAL_Q_INVALID'); + assertInt32(r, 'GREATER_REALM_AXIAL_R_INVALID'); + assertUint32(sampleIndex, 'GREATER_REALM_TERRAIN_SAMPLE_INVALID'); + + return counterRandomFromSeedWords(terrainSeedWords(seed), channel, q, r, sampleIndex); +} + +export type IntegerFieldLayer = Readonly<{ + channel: string | number; + amplitude: number; + smoothingPasses: number; + selfWeight?: number; +}>; + +function layerChannelId(channel: string | number): number { + if (typeof channel === 'string') return greaterRealmTerrainChannelId(channel); + assertUint32(channel, 'GREATER_REALM_TERRAIN_CHANNEL_INVALID'); + return channel; +} + +/** + * Build an integer multi-scale field by independently sampling and smoothing + * named layers. Smoothing is synchronous, mask-aware, and symmetric around 0. + */ +export function createGreaterRealmMultiscaleIntegerField( + grid: IndexedAxialGrid, + seed: GreaterRealmTerrainSeed, + layers: readonly IntegerFieldLayer[], +): Int32Array { + const seedWords = terrainSeedWords(seed); + if (layers.length === 0) fail('GREATER_REALM_TERRAIN_LAYERS_EMPTY'); + const output = new Int32Array(grid.cellCount); + + for (const layer of layers) { + assertInt32(layer.amplitude, 'GREATER_REALM_TERRAIN_AMPLITUDE_INVALID'); + if (layer.amplitude < 0) fail('GREATER_REALM_TERRAIN_AMPLITUDE_INVALID'); + assertUint32(layer.smoothingPasses, 'GREATER_REALM_TERRAIN_SMOOTHING_INVALID'); + if (layer.smoothingPasses > MAX_OFFLINE_RELAXATION_PASSES) { + fail('GREATER_REALM_TERRAIN_SMOOTHING_INVALID'); + } + const selfWeight = layer.selfWeight ?? 2; + assertUint32(selfWeight, 'GREATER_REALM_TERRAIN_SELF_WEIGHT_INVALID'); + if (selfWeight === 0) fail('GREATER_REALM_TERRAIN_SELF_WEIGHT_INVALID'); + const channel = layerChannelId(layer.channel); + const span = layer.amplitude * 2 + 1; + if (!Number.isSafeInteger(span) || 0xffff * span > Number.MAX_SAFE_INTEGER) { + fail('GREATER_REALM_TERRAIN_AMPLITUDE_OVERFLOW'); + } + + let current = new Int32Array(grid.cellCount); + for (let index = 0; index < grid.cellCount; index += 1) { + const random = counterRandomFromSeedWords( + seedWords, + channel, + grid.q[index]!, + grid.r[index]!, + ); + const sample = random >>> 16; + current[index] = Math.floor((sample * span) / UINT16_RANGE) - layer.amplitude; + } + + for (let pass = 0; pass < layer.smoothingPasses; pass += 1) { + const next = new Int32Array(grid.cellCount); + for (let index = 0; index < grid.cellCount; index += 1) { + let numerator = current[index]! * selfWeight; + let denominator = selfWeight; + if (!Number.isSafeInteger(numerator)) fail('GREATER_REALM_TERRAIN_SMOOTHING_OVERFLOW'); + for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[index * NEIGHBOR_COUNT + directionIndex]!; + if (neighbor < 0) continue; + numerator = checkedSafeSum( + numerator, + current[neighbor]!, + 'GREATER_REALM_TERRAIN_SMOOTHING_OVERFLOW', + ); + denominator += 1; + } + next[index] = checkedInt32( + roundDivide(numerator, denominator), + 'GREATER_REALM_TERRAIN_SMOOTHING_OVERFLOW', + ); + } + current = next; + } + + for (let index = 0; index < grid.cellCount; index += 1) { + output[index] = checkedInt32( + output[index]! + current[index]!, + 'GREATER_REALM_TERRAIN_FIELD_OVERFLOW', + ); + } + } + + return output; +} + +class StableCellMinHeap { + readonly #cells: number[] = []; + + constructor(private readonly priority: Int32Array) {} + + get size(): number { + return this.#cells.length; + } + + #less(first: number, second: number): boolean { + const priorityDifference = this.priority[first]! - this.priority[second]!; + return priorityDifference < 0 || (priorityDifference === 0 && first < second); + } + + push(cell: number): void { + let cursor = this.#cells.length; + this.#cells.push(cell); + while (cursor > 0) { + const parent = Math.floor((cursor - 1) / 2); + if (!this.#less(cell, this.#cells[parent]!)) break; + this.#cells[cursor] = this.#cells[parent]!; + cursor = parent; + } + this.#cells[cursor] = cell; + } + + pop(): number { + if (this.#cells.length === 0) fail('GREATER_REALM_PRIORITY_QUEUE_EMPTY'); + const root = this.#cells[0]!; + const tail = this.#cells.pop()!; + if (this.#cells.length === 0) return root; + + let cursor = 0; + while (true) { + const left = cursor * 2 + 1; + if (left >= this.#cells.length) break; + const right = left + 1; + let child = left; + if (right < this.#cells.length && this.#less(this.#cells[right]!, this.#cells[left]!)) { + child = right; + } + if (!this.#less(this.#cells[child]!, tail)) break; + this.#cells[cursor] = this.#cells[child]!; + cursor = child; + } + this.#cells[cursor] = tail; + return root; + } +} + +export type GreaterRealmPriorityFlood = Readonly<{ + filledElevation: Int32Array; + floodParent: Int32Array; + /** Pop order by rank. */ + order: Uint32Array; + /** Pop rank by cell index. */ + rank: Uint32Array; + outlets: Uint8Array; +}>; + +function canonicalOutletIndexes( + grid: IndexedAxialGrid, + outletIndexes: readonly number[] | Uint32Array | undefined, +): number[] { + const outlets: number[] = []; + if (outletIndexes === undefined) { + for (let index = 0; index < grid.cellCount; index += 1) { + for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + if (grid.neighbors[index * NEIGHBOR_COUNT + directionIndex] !== -1) continue; + outlets.push(index); + break; + } + } + } else { + for (const outlet of outletIndexes) { + if (!Number.isSafeInteger(outlet) || outlet < 0 || outlet >= grid.cellCount) { + fail('GREATER_REALM_PRIORITY_FLOOD_OUTLET_INVALID'); + } + outlets.push(outlet); + } + } + outlets.sort((first, second) => first - second); + const unique = outlets.filter((outlet, index) => index === 0 || outlet !== outlets[index - 1]); + if (unique.length === 0) fail('GREATER_REALM_PRIORITY_FLOOD_OUTLET_MISSING'); + return unique; +} + +/** Stable Priority-Flood over a six-connected active mask. */ +export function priorityFloodGreaterRealmHexGrid( + grid: IndexedAxialGrid, + elevation: Readonly, + outletIndexes?: readonly number[] | Uint32Array, +): GreaterRealmPriorityFlood { + if (elevation.length !== grid.cellCount) fail('GREATER_REALM_ELEVATION_LENGTH_INVALID'); + if (grid.cellCount > UINT32_MAX) fail('GREATER_REALM_PRIORITY_FLOOD_SIZE_OVERFLOW'); + + const filledElevation = new Int32Array(elevation); + const floodParent = new Int32Array(grid.cellCount); + floodParent.fill(-1); + const order = new Uint32Array(grid.cellCount); + const rank = new Uint32Array(grid.cellCount); + const outlets = new Uint8Array(grid.cellCount); + const discovered = new Uint8Array(grid.cellCount); + const heap = new StableCellMinHeap(filledElevation); + + for (const outlet of canonicalOutletIndexes(grid, outletIndexes)) { + outlets[outlet] = 1; + discovered[outlet] = 1; + heap.push(outlet); + } + + let popCount = 0; + while (heap.size > 0) { + const cell = heap.pop(); + order[popCount] = cell; + rank[cell] = popCount; + popCount += 1; + + for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + directionIndex]!; + if (neighbor < 0 || discovered[neighbor] === 1) continue; + discovered[neighbor] = 1; + floodParent[neighbor] = cell; + if (filledElevation[neighbor]! < filledElevation[cell]!) { + filledElevation[neighbor] = filledElevation[cell]!; + } + heap.push(neighbor); + } + } + + if (popCount !== grid.cellCount) fail('GREATER_REALM_PRIORITY_FLOOD_UNREACHABLE'); + return Object.freeze({ filledElevation, floodParent, order, rank, outlets }); +} + +export type GreaterRealmSingleFlowRouting = Readonly<{ + /** One adjacent receiver per non-outlet; `-1` only for legal outlets. */ + receiver: Int32Array; + order: Uint32Array; + rank: Uint32Array; + outlets: Uint8Array; +}>; + +function betterFlowReceiver( + candidate: number, + currentBest: number, + elevation: Int32Array, + rank: Uint32Array, +): boolean { + if (currentBest < 0) return true; + if (elevation[candidate]! !== elevation[currentBest]!) { + return elevation[candidate]! < elevation[currentBest]!; + } + if (rank[candidate]! !== rank[currentBest]!) return rank[candidate]! < rank[currentBest]!; + return candidate < currentBest; +} + +/** + * Route one flow edge per cell. Lower filled terrain wins; filled flats follow + * strictly decreasing flood rank, so every path terminates without epsilon edits. + */ +export function routeGreaterRealmSingleFlow( + grid: IndexedAxialGrid, + flood: GreaterRealmPriorityFlood, +): GreaterRealmSingleFlowRouting { + if ( + flood.filledElevation.length !== grid.cellCount + || flood.order.length !== grid.cellCount + || flood.rank.length !== grid.cellCount + || flood.outlets.length !== grid.cellCount + ) fail('GREATER_REALM_FLOW_INPUT_LENGTH_INVALID'); + + const receiver = new Int32Array(grid.cellCount); + receiver.fill(-1); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (flood.outlets[cell] === 1) continue; + let best = -1; + for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + directionIndex]!; + if (neighbor < 0 || flood.rank[neighbor]! >= flood.rank[cell]!) continue; + if (flood.filledElevation[neighbor]! > flood.filledElevation[cell]!) continue; + if (betterFlowReceiver(neighbor, best, flood.filledElevation, flood.rank)) best = neighbor; + } + if (best < 0) fail('GREATER_REALM_FLOW_RECEIVER_MISSING'); + receiver[cell] = best; + } + + const routing = Object.freeze({ + receiver, + order: new Uint32Array(flood.order), + rank: new Uint32Array(flood.rank), + outlets: new Uint8Array(flood.outlets), + }); + assertGreaterRealmSingleFlow(grid, flood.filledElevation, routing); + return routing; +} + +/** Throw unless the routing is adjacent, downhill/flat, acyclic, and outlet-complete. */ +export function assertGreaterRealmSingleFlow( + grid: IndexedAxialGrid, + filledElevation: Readonly, + routing: GreaterRealmSingleFlowRouting, +): void { + if ( + filledElevation.length !== grid.cellCount + || routing.receiver.length !== grid.cellCount + || routing.order.length !== grid.cellCount + || routing.rank.length !== grid.cellCount + || routing.outlets.length !== grid.cellCount + ) fail('GREATER_REALM_FLOW_INPUT_LENGTH_INVALID'); + + const seen = new Uint8Array(grid.cellCount); + for (let orderIndex = 0; orderIndex < grid.cellCount; orderIndex += 1) { + const cell = routing.order[orderIndex]!; + if (cell >= grid.cellCount || seen[cell] === 1 || routing.rank[cell] !== orderIndex) { + fail('GREATER_REALM_FLOW_ORDER_INVALID'); + } + seen[cell] = 1; + } + + let outletCount = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const receiver = routing.receiver[cell]!; + if (routing.outlets[cell] === 1) { + outletCount += 1; + if (receiver !== -1) fail('GREATER_REALM_FLOW_OUTLET_INVALID'); + continue; + } + if (receiver < 0 || receiver >= grid.cellCount) fail('GREATER_REALM_FLOW_RECEIVER_INVALID'); + let adjacent = false; + for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + if (grid.neighbors[cell * NEIGHBOR_COUNT + directionIndex] === receiver) { + adjacent = true; + break; + } + } + if (!adjacent) fail('GREATER_REALM_FLOW_RECEIVER_NOT_ADJACENT'); + if (filledElevation[receiver]! > filledElevation[cell]!) fail('GREATER_REALM_FLOW_UPHILL'); + if (routing.rank[receiver]! >= routing.rank[cell]!) fail('GREATER_REALM_FLOW_CYCLE'); + } + if (outletCount === 0) fail('GREATER_REALM_FLOW_OUTLET_MISSING'); +} + +/** Accumulate non-negative local discharge in reverse topological order. */ +export function accumulateGreaterRealmSingleFlow( + grid: IndexedAxialGrid, + filledElevation: Readonly, + routing: GreaterRealmSingleFlowRouting, + localContribution?: Readonly, +): BigUint64Array { + assertGreaterRealmSingleFlow(grid, filledElevation, routing); + if (localContribution !== undefined && localContribution.length !== grid.cellCount) { + fail('GREATER_REALM_FLOW_CONTRIBUTION_LENGTH_INVALID'); + } + const accumulation = new BigUint64Array(grid.cellCount); + let localTotal = 0n; + for (let index = 0; index < grid.cellCount; index += 1) { + const contribution = BigInt(localContribution?.[index] ?? 1); + accumulation[index] = contribution; + localTotal += contribution; + if (localTotal > UINT64_MAX) fail('GREATER_REALM_FLOW_ACCUMULATION_OVERFLOW'); + } + + for (let orderIndex = grid.cellCount - 1; orderIndex >= 0; orderIndex -= 1) { + const cell = routing.order[orderIndex]!; + const receiver = routing.receiver[cell]!; + if (receiver < 0) continue; + const sum = accumulation[receiver]! + accumulation[cell]!; + if (sum > UINT64_MAX) fail('GREATER_REALM_FLOW_ACCUMULATION_OVERFLOW'); + accumulation[receiver] = sum; + } + + let outletTotal = 0n; + for (let index = 0; index < grid.cellCount; index += 1) { + if (routing.outlets[index] === 1) outletTotal += accumulation[index]!; + } + if (outletTotal !== localTotal) fail('GREATER_REALM_FLOW_ACCUMULATION_MISMATCH'); + return accumulation; +} + +export type SynchronousThermalErosionOptions = Readonly<{ + iterations: number; + /** Maximum stable edge drop, globally or per cell. Per-edge uses the larger value. */ + talus: number | Readonly; + transferNumerator?: number; + transferDenominator?: number; +}>; + +export type SynchronousThermalErosionResult = Readonly<{ + elevation: Int32Array; + initialMass: bigint; + finalMass: bigint; + movedMaterial: bigint; +}>; + +function elevationMass(elevation: Readonly): bigint { + let mass = 0n; + for (const value of elevation) mass += BigInt(value); + return mass; +} + +function talusAt( + talus: number | Readonly, + index: number, +): number { + return typeof talus === 'number' ? talus : talus[index]!; +} + +/** + * Conservative two-pass thermal relaxation. Every edge flux is calculated + * from the same snapshot, then all deltas are committed together. + */ +export function erodeGreaterRealmThermally( + grid: IndexedAxialGrid, + elevation: Readonly, + options: SynchronousThermalErosionOptions, +): SynchronousThermalErosionResult { + if (elevation.length !== grid.cellCount) fail('GREATER_REALM_ELEVATION_LENGTH_INVALID'); + assertUint32(options.iterations, 'GREATER_REALM_THERMAL_ITERATIONS_INVALID'); + if (options.iterations > MAX_OFFLINE_RELAXATION_PASSES) { + fail('GREATER_REALM_THERMAL_ITERATIONS_INVALID'); + } + if (typeof options.talus === 'number') { + assertInt32(options.talus, 'GREATER_REALM_THERMAL_TALUS_INVALID'); + if (options.talus < 0) fail('GREATER_REALM_THERMAL_TALUS_INVALID'); + } else { + if (options.talus.length !== grid.cellCount) fail('GREATER_REALM_THERMAL_TALUS_LENGTH_INVALID'); + for (const value of options.talus) { + if (value < 0) fail('GREATER_REALM_THERMAL_TALUS_INVALID'); + } + } + const transferNumerator = options.transferNumerator ?? 1; + const transferDenominator = options.transferDenominator ?? 16; + assertUint32(transferNumerator, 'GREATER_REALM_THERMAL_TRANSFER_INVALID'); + assertUint32(transferDenominator, 'GREATER_REALM_THERMAL_TRANSFER_INVALID'); + if ( + transferNumerator === 0 + || transferDenominator === 0 + || transferNumerator > THERMAL_TRANSFER_SCALE_MAX + || transferDenominator > THERMAL_TRANSFER_SCALE_MAX + || transferNumerator * NEIGHBOR_COUNT > transferDenominator + ) fail('GREATER_REALM_THERMAL_TRANSFER_INVALID'); + + const initialMass = elevationMass(elevation); + let movedMaterial = 0n; + let current = new Int32Array(elevation); + for (let iteration = 0; iteration < options.iterations; iteration += 1) { + const delta = new Float64Array(grid.cellCount); + let movedThisIteration = 0; + for (let first = 0; first < grid.cellCount; first += 1) { + for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + const second = grid.neighbors[first * NEIGHBOR_COUNT + directionIndex]!; + if (second <= first) continue; + const difference = current[first]! - current[second]!; + const magnitude = Math.abs(difference); + const stableDrop = Math.max(talusAt(options.talus, first), talusAt(options.talus, second)); + if (magnitude <= stableDrop) continue; + const transfer = Math.floor( + ((magnitude - stableDrop) * transferNumerator) / transferDenominator, + ); + if (transfer <= 0) continue; + const high = difference > 0 ? first : second; + const low = difference > 0 ? second : first; + delta[high] -= transfer; + delta[low] += transfer; + if (!Number.isSafeInteger(delta[high]) || !Number.isSafeInteger(delta[low])) { + fail('GREATER_REALM_THERMAL_DELTA_OVERFLOW'); + } + movedThisIteration = checkedSafeSum( + movedThisIteration, + transfer, + 'GREATER_REALM_THERMAL_TRANSFER_OVERFLOW', + ); + } + } + + const next = new Int32Array(grid.cellCount); + for (let index = 0; index < grid.cellCount; index += 1) { + next[index] = checkedInt32( + current[index]! + delta[index]!, + 'GREATER_REALM_THERMAL_ELEVATION_OVERFLOW', + ); + } + movedMaterial += BigInt(movedThisIteration); + current = next; + } + + const finalMass = elevationMass(current); + if (finalMass !== initialMass) fail('GREATER_REALM_THERMAL_MASS_MISMATCH'); + return Object.freeze({ elevation: current, initialMass, finalMass, movedMaterial }); +} + +export type IntegerTerrainArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | BigInt64Array + | BigUint64Array; + +function updateLengthPrefixedText( + digest: ReturnType, + value: string, +): void { + const encoded = Buffer.from(value, 'utf8'); + const size = Buffer.allocUnsafe(4); + try { + size.writeUInt32LE(encoded.length); + digest.update(size); + digest.update(encoded); + } finally { + size.fill(0); + encoded.fill(0); + } +} + +function encodeIntegerArray(array: IntegerTerrainArray): Readonly<{ + type: string; + bytes: Buffer; +}> { + let type: string; + let width: number; + let write: (buffer: Buffer, offset: number, index: number) => void; + if (array instanceof Int8Array) { + type = 'i8'; + width = 1; + write = (buffer, offset, index) => buffer.writeInt8(array[index]!, offset); + } else if (array instanceof Uint8ClampedArray) { + type = 'u8c'; + width = 1; + write = (buffer, offset, index) => buffer.writeUInt8(array[index]!, offset); + } else if (array instanceof Uint8Array) { + type = 'u8'; + width = 1; + write = (buffer, offset, index) => buffer.writeUInt8(array[index]!, offset); + } else if (array instanceof Int16Array) { + type = 'i16'; + width = 2; + write = (buffer, offset, index) => buffer.writeInt16LE(array[index]!, offset); + } else if (array instanceof Uint16Array) { + type = 'u16'; + width = 2; + write = (buffer, offset, index) => buffer.writeUInt16LE(array[index]!, offset); + } else if (array instanceof Int32Array) { + type = 'i32'; + width = 4; + write = (buffer, offset, index) => buffer.writeInt32LE(array[index]!, offset); + } else if (array instanceof Uint32Array) { + type = 'u32'; + width = 4; + write = (buffer, offset, index) => buffer.writeUInt32LE(array[index]!, offset); + } else if (array instanceof BigInt64Array) { + type = 'i64'; + width = 8; + write = (buffer, offset, index) => buffer.writeBigInt64LE(array[index]!, offset); + } else if (array instanceof BigUint64Array) { + type = 'u64'; + width = 8; + write = (buffer, offset, index) => buffer.writeBigUInt64LE(array[index]!, offset); + } else { + fail('GREATER_REALM_STAGE_DIGEST_ARRAY_INVALID'); + } + if (array.length > UINT32_MAX || array.length * width > bufferConstants.MAX_LENGTH) { + fail('GREATER_REALM_STAGE_DIGEST_ARRAY_TOO_LARGE'); + } + const bytes = Buffer.allocUnsafe(array.length * width); + try { + for (let index = 0; index < array.length; index += 1) write(bytes, index * width, index); + } catch (error) { + bytes.fill(0); + throw error; + } + return Object.freeze({ type, bytes }); +} + +/** + * SHA-256 over canonical coordinates and sorted integer fields. Values are + * explicitly little-endian so evidence is not host-endian dependent. + */ +export function digestGreaterRealmTerrainStage( + stage: string, + grid: IndexedAxialGrid, + fields: Readonly>, +): string { + if (stage.length === 0) fail('GREATER_REALM_STAGE_DIGEST_NAME_INVALID'); + const digest = createHash('sha256'); + updateLengthPrefixedText(digest, GREATER_REALM_TERRAIN_CORE_VERSION); + updateLengthPrefixedText(digest, stage); + const count = Buffer.allocUnsafe(4); + try { + count.writeUInt32LE(grid.cellCount); + digest.update(count); + + if (Object.keys(fields).some((name) => name.length === 0 || name.startsWith('@'))) { + fail('GREATER_REALM_STAGE_DIGEST_FIELD_INVALID'); + } + + for (const [name, array] of [ + ['@q', grid.q] as const, + ['@r', grid.r] as const, + ...Object.entries(fields).sort(([first], [second]) => ( + first < second ? -1 : first > second ? 1 : 0 + )), + ]) { + const encoded = encodeIntegerArray(array); + let length: Buffer | undefined; + try { + updateLengthPrefixedText(digest, name); + updateLengthPrefixedText(digest, encoded.type); + length = Buffer.allocUnsafe(4); + length.writeUInt32LE(array.length); + digest.update(length); + digest.update(encoded.bytes); + } finally { + length?.fill(0); + encoded.bytes.fill(0); + } + } + return digest.digest('hex'); + } finally { + count.fill(0); + } +} diff --git a/scripts/atlas/greater-realm-toolchain-bootstrap.mjs b/scripts/atlas/greater-realm-toolchain-bootstrap.mjs new file mode 100644 index 00000000..81d78c82 --- /dev/null +++ b/scripts/atlas/greater-realm-toolchain-bootstrap.mjs @@ -0,0 +1,786 @@ +import { createHash } from 'node:crypto'; +import { spawnSync } from 'node:child_process'; +import { createRequire } from 'node:module'; +import { + closeSync, + constants, + existsSync, + fstatSync, + lstatSync, + openSync, + readSync, + readdirSync, + realpathSync, +} from 'node:fs'; +import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = resolve(import.meta.dirname, '..', '..'); +const TOOLCHAIN_LOCK_PATH = resolve( + ROOT, + 'scripts', + 'atlas', + 'greater-realm-toolchain-lock.json', +); +const CLI_PATH = resolve(ROOT, 'scripts', 'atlas', 'greater-realm-cli.ts'); +const MAXIMUM_CONTROL_BYTES = 16 * 1024 * 1024; +const MAXIMUM_PACKAGE_FILE_BYTES = 256 * 1024 * 1024; +const MAXIMUM_PACKAGE_FILES = 8_192; +const MAXIMUM_PACKAGE_BYTES = 1024 * 1024 * 1024; +const SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const SRI_PATTERN = /^sha512-[A-Za-z0-9+/]{86}==$/u; +const VERSION_PATTERN = /^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$/u; +const PACKAGE_NAME_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u; +const FORBIDDEN_SECRET_ARGUMENT = /^(?:--)?(?:private-)?(?:atlas-)?(?:seed|seed-hex|seed-material|layout-digest|stage-digest|package-digest)(?:=|$)/iu; +const RESERVED_ENVIRONMENT_KEY = /^WARPKEEP_GREATER_REALM_/u; +const POSSIBLE_SECRET_VALUE = /^(?:[0-9a-f]{64}|[A-Za-z0-9+/]{43}=?)$/u; +const ALLOWED_COMMANDS = Object.freeze([ + 'compare-candidates', + 'export-sanitized-review', + 'generate-candidates', + 'select-candidate', + 'verify-private-package', + 'verify-sanitized-review', +]); +const COMMON_PACKAGE_NAMES = Object.freeze([ + '@img/colour', + 'detect-libc', + 'esbuild', + 'semver', + 'sharp', + 'tsx', + 'typescript', +]); +const LOCKED_PACKAGE_NAMES = Object.freeze([ + '@esbuild/darwin-arm64', + '@esbuild/linux-x64', + '@img/colour', + '@img/sharp-darwin-arm64', + '@img/sharp-libvips-darwin-arm64', + '@img/sharp-libvips-linux-x64', + '@img/sharp-linux-x64', + '@typescript/typescript-darwin-arm64', + '@typescript/typescript-linux-x64', + 'detect-libc', + 'esbuild', + 'fsevents', + 'semver', + 'sharp', + 'tsx', + 'typescript', +]); +const DIRECT_PACKAGE_NAMES = Object.freeze(['sharp', 'tsx', 'typescript']); +const DANGEROUS_CHILD_ENVIRONMENT_KEYS = Object.freeze([ + 'BUN_OPTIONS', + 'DYLD_FRAMEWORK_PATH', + 'DYLD_INSERT_LIBRARIES', + 'DYLD_LIBRARY_PATH', + 'ESBUILD_BINARY_PATH', + 'LD_AUDIT', + 'LD_LIBRARY_PATH', + 'LD_PRELOAD', + 'LIBVIPS_PATH', + 'NODE_COMPILE_CACHE', + 'NODE_ICU_DATA', + 'NODE_OPTIONS', + 'NODE_PATH', + 'NODE_PRESERVE_SYMLINKS', + 'NODE_PRESERVE_SYMLINKS_MAIN', + 'SHARP_FORCE_GLOBAL_LIBVIPS', + 'SHARP_IGNORE_GLOBAL_LIBVIPS', + 'TSX_TSCONFIG_PATH', +]); + +function fail(code) { + throw new Error(code); +} + +function exactRecord(value, keys, code = 'GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID') { + if ( + value === null + || typeof value !== 'object' + || Array.isArray(value) + || ![Object.prototype, null].includes(Object.getPrototypeOf(value)) + ) fail(code); + const ownKeys = Reflect.ownKeys(value); + if (ownKeys.some(key => typeof key !== 'string')) fail(code); + const actual = ownKeys.toSorted(); + const expected = [...keys].toSorted(); + if ( + actual.length !== expected.length + || actual.some((key, index) => key !== expected[index]) + ) fail(code); + const descriptors = Object.getOwnPropertyDescriptors(value); + if (Object.values(descriptors).some(descriptor => !('value' in descriptor))) fail(code); + return value; +} + +function pathInside(parent, candidate) { + const difference = relative(parent, candidate); + return difference === '' || ( + difference !== '..' + && !difference.startsWith(`..${sep}`) + && !isAbsolute(difference) + ); +} + +function sameIdentity(left, right) { + return left.dev === right.dev && left.ino === right.ino; +} + +function comparePortableNames(left, right) { + return left < right ? -1 : left > right ? 1 : 0; +} + +function assertTrustedStatus(status, kind) { + const currentUser = process.getuid?.(); + if ( + (kind === 'directory' ? !status.isDirectory() : !status.isFile()) + || status.isSymbolicLink() + || (currentUser !== undefined && status.uid !== 0 && status.uid !== currentUser) + || (status.mode & 0o022) !== 0 + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); +} + +function readPinnedFile(path, maximumBytes = MAXIMUM_CONTROL_BYTES) { + let descriptor; + let bytes; + try { + const beforePath = lstatSync(path); + assertTrustedStatus(beforePath, 'file'); + if (beforePath.size < 1 || beforePath.size > maximumBytes) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const before = fstatSync(descriptor); + assertTrustedStatus(before, 'file'); + if (!sameIdentity(beforePath, before) || before.size !== beforePath.size) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + bytes = Buffer.alloc(before.size); + let offset = 0; + while (offset < bytes.byteLength) { + const count = readSync(descriptor, bytes, offset, bytes.byteLength - offset, null); + if (count <= 0) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + offset += count; + } + const after = fstatSync(descriptor); + const afterPath = lstatSync(path); + assertTrustedStatus(after, 'file'); + assertTrustedStatus(afterPath, 'file'); + if ( + !sameIdentity(before, after) + || !sameIdentity(after, afterPath) + || before.size !== after.size + || before.mtimeMs !== after.mtimeMs + || before.ctimeMs !== after.ctimeMs + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + const result = bytes; + bytes = undefined; + return result; + } catch (error) { + bytes?.fill(0); + if (error instanceof Error && error.message.startsWith('GREATER_REALM_')) throw error; + return fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } +} + +function readPinnedJson(path) { + const bytes = readPinnedFile(path); + try { + return JSON.parse(bytes.toString('utf8')); + } catch { + return fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + } finally { + bytes.fill(0); + } +} + +function attestDirectory(path, expected) { + let descriptor; + try { + const before = lstatSync(path); + assertTrustedStatus(before, 'directory'); + descriptor = openSync( + path, + constants.O_RDONLY + | (constants.O_DIRECTORY ?? 0) + | (constants.O_NOFOLLOW ?? 0), + ); + const opened = fstatSync(descriptor); + const after = lstatSync(path); + assertTrustedStatus(opened, 'directory'); + assertTrustedStatus(after, 'directory'); + if ( + !sameIdentity(before, opened) + || !sameIdentity(opened, after) + || (expected !== undefined && !sameIdentity(opened, expected)) + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + return Object.freeze({ dev: opened.dev, ino: opened.ino }); + } catch (error) { + if (error instanceof Error && error.message.startsWith('GREATER_REALM_')) throw error; + return fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } finally { + if (descriptor !== undefined) closeSync(descriptor); + } +} + +function assertAuxiliaryNodeModules(path) { + const identity = attestDirectory(path); + const entries = readdirSync(path, { withFileTypes: true }); + if ( + entries.length > 1 + || (entries.length === 1 && (entries[0]?.name !== '.bin' || !entries[0].isDirectory())) + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + if (entries.length === 1) attestDirectory(join(path, '.bin')); + attestDirectory(path, identity); +} + +function hashPinnedPackageFile(path) { + let descriptor; + const digest = createHash('sha256'); + const chunk = Buffer.alloc(64 * 1024); + try { + const beforePath = lstatSync(path); + assertTrustedStatus(beforePath, 'file'); + if (beforePath.size < 0 || beforePath.size > MAXIMUM_PACKAGE_FILE_BYTES) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const before = fstatSync(descriptor); + assertTrustedStatus(before, 'file'); + if (!sameIdentity(beforePath, before) || beforePath.size !== before.size) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + let byteCount = 0; + while (byteCount < before.size) { + const count = readSync( + descriptor, + chunk, + 0, + Math.min(chunk.byteLength, before.size - byteCount), + null, + ); + if (count <= 0) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + digest.update(chunk.subarray(0, count)); + byteCount += count; + } + const after = fstatSync(descriptor); + const afterPath = lstatSync(path); + assertTrustedStatus(after, 'file'); + assertTrustedStatus(afterPath, 'file'); + if ( + !sameIdentity(before, after) + || !sameIdentity(after, afterPath) + || before.size !== after.size + || before.mtimeMs !== after.mtimeMs + || before.ctimeMs !== after.ctimeMs + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + return Object.freeze({ + byteCount, + executable: (after.mode & 0o111) !== 0, + sha256: digest.digest('hex'), + }); + } catch (error) { + if (error instanceof Error && error.message.startsWith('GREATER_REALM_')) throw error; + return fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } finally { + chunk.fill(0); + if (descriptor !== undefined) closeSync(descriptor); + } +} + +export function computeGreaterRealmPackageTree(packageRoot) { + const canonicalRoot = realpathSync(packageRoot); + const records = []; + let byteCount = 0; + const visit = (directory, relativeDirectory = '') => { + const directoryIdentity = attestDirectory(directory); + const entries = readdirSync(directory, { withFileTypes: true }) + .toSorted((left, right) => comparePortableNames(left.name, right.name)); + for (const entry of entries) { + if ( + entry.name.includes('\0') + || entry.name.normalize('NFC') !== entry.name + || entry.name === '.' + || entry.name === '..' + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + const relativePath = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name; + const path = join(directory, entry.name); + if (relativeDirectory === '' && entry.name === 'node_modules') { + assertAuxiliaryNodeModules(path); + continue; + } + if (entry.isSymbolicLink()) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + if (entry.isDirectory()) { + visit(path, relativePath); + continue; + } + if (!entry.isFile()) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + const file = hashPinnedPackageFile(path); + byteCount += file.byteCount; + records.push(Object.freeze({ + path: relativePath, + byteCount: file.byteCount, + executable: file.executable, + sha256: file.sha256, + })); + if ( + records.length > MAXIMUM_PACKAGE_FILES + || !Number.isSafeInteger(byteCount) + || byteCount > MAXIMUM_PACKAGE_BYTES + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + attestDirectory(directory, directoryIdentity); + }; + visit(canonicalRoot); + const treeSha256 = createHash('sha256') + .update('warpkeep.greater-realm.package-tree.v1\0', 'utf8') + .update(JSON.stringify(records), 'utf8') + .digest('hex'); + return Object.freeze({ + byteCount, + fileCount: records.length, + treeSha256, + }); +} + +function packageRecord(value, expectedName) { + const record = exactRecord( + value, + ['byteCount', 'fileCount', 'integrity', 'name', 'treeSha256', 'version'], + ); + if ( + record.name !== expectedName + || !VERSION_PATTERN.test(record.version) + || !SRI_PATTERN.test(record.integrity) + || !SHA256_PATTERN.test(record.treeSha256) + || !Number.isSafeInteger(record.fileCount) + || record.fileCount < 1 + || record.fileCount > MAXIMUM_PACKAGE_FILES + || !Number.isSafeInteger(record.byteCount) + || record.byteCount < 1 + || record.byteCount > MAXIMUM_PACKAGE_BYTES + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + return record; +} + +function parseToolchainLock(repositoryRoot) { + const path = resolve(repositoryRoot, relative(ROOT, TOOLCHAIN_LOCK_PATH)); + const bytes = readPinnedFile(path); + try { + const value = JSON.parse(bytes.toString('utf8')); + const lock = exactRecord( + value, + [ + 'configuredNodeEngine', + 'configuredPackageManager', + 'kind', + 'packages', + 'profiles', + ], + ); + if ( + lock.kind !== 'warpkeep.greater-realm.trusted-toolchain.v1' + || lock.configuredNodeEngine !== '>=22.13 <23' + || lock.configuredPackageManager !== 'npm@10.9.8' + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + const packages = exactRecord(lock.packages, LOCKED_PACKAGE_NAMES); + const parsedPackages = new Map(); + for (const [name, record] of Object.entries(packages)) { + if (!PACKAGE_NAME_PATTERN.test(name)) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + parsedPackages.set(name, packageRecord(record, name)); + } + const profiles = exactRecord(lock.profiles, ['darwin-arm64', 'linux-x64']); + const parsedProfiles = new Map(); + for (const [name, value] of Object.entries(profiles)) { + if (!/^[a-z0-9]+-[a-z0-9]+(?:-[a-z0-9]+)?$/u.test(name)) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + } + const profile = exactRecord( + value, + [ + 'esbuildPackage', + 'libvipsPackage', + 'sharpPackage', + 'tsxOptionalPackages', + 'typescriptPackage', + ], + ); + const packageNames = [ + profile.esbuildPackage, + profile.libvipsPackage, + profile.sharpPackage, + profile.typescriptPackage, + ]; + if ( + packageNames.some(packageName => ( + typeof packageName !== 'string' || !parsedPackages.has(packageName) + )) + || !Array.isArray(profile.tsxOptionalPackages) + || profile.tsxOptionalPackages.some(packageName => ( + typeof packageName !== 'string' || !parsedPackages.has(packageName) + )) + || new Set(profile.tsxOptionalPackages).size !== profile.tsxOptionalPackages.length + || ( + name === 'darwin-arm64' + ? profile.tsxOptionalPackages.length !== 1 + || profile.tsxOptionalPackages[0] !== 'fsevents' + : profile.tsxOptionalPackages.length !== 0 + ) + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + parsedProfiles.set(name, Object.freeze({ + esbuildPackage: profile.esbuildPackage, + libvipsPackage: profile.libvipsPackage, + sharpPackage: profile.sharpPackage, + tsxOptionalPackages: Object.freeze([...profile.tsxOptionalPackages]), + typescriptPackage: profile.typescriptPackage, + })); + } + return Object.freeze({ + configuredNodeEngine: lock.configuredNodeEngine, + configuredPackageManager: lock.configuredPackageManager, + manifestSha256: createHash('sha256').update(bytes).digest('hex'), + packages: parsedPackages, + profiles: parsedProfiles, + }); + } catch (error) { + if (error instanceof Error && error.message.startsWith('GREATER_REALM_')) throw error; + return fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + } finally { + bytes.fill(0); + } +} + +function parsePackageMetadata(packageRoot, expectedName, expectedVersion) { + const bytes = readPinnedFile(resolve(packageRoot, 'package.json'), 1024 * 1024); + try { + const value = JSON.parse(bytes.toString('utf8')); + if ( + value === null + || typeof value !== 'object' + || Array.isArray(value) + || value.name !== expectedName + || value.version !== expectedVersion + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_PACKAGE_INVALID'); + } catch (error) { + if (error instanceof Error && error.message.startsWith('GREATER_REALM_')) throw error; + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_PACKAGE_INVALID'); + } finally { + bytes.fill(0); + } +} + +function assertPackageLockBoundary(repositoryRoot, toolchainLock) { + const packageJson = readPinnedJson(resolve(repositoryRoot, 'package.json')); + const packageLock = readPinnedJson(resolve(repositoryRoot, 'package-lock.json')); + const rootPackage = packageLock?.packages?.['']; + if ( + packageJson?.packageManager !== toolchainLock.configuredPackageManager + || packageJson?.engines?.node !== toolchainLock.configuredNodeEngine + || packageLock?.lockfileVersion !== 3 + || rootPackage?.packageManager !== undefined + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_LOCK_MISMATCH'); + for (const name of DIRECT_PACKAGE_NAMES) { + const expected = toolchainLock.packages.get(name); + if ( + expected === undefined + || packageJson?.devDependencies?.[name] !== expected.version + || rootPackage?.devDependencies?.[name] !== expected.version + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_LOCK_MISMATCH'); + } + for (const [name, expected] of toolchainLock.packages) { + const locked = packageLock?.packages?.[`node_modules/${name}`]; + if ( + locked?.version !== expected.version + || locked?.integrity !== expected.integrity + || typeof locked?.resolved !== 'string' + || !locked.resolved.startsWith('https://registry.npmjs.org/') + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_LOCK_MISMATCH'); + } +} + +function chooseProfile(toolchainLock, platform, architecture, sharpEnvironment) { + const prefix = `${platform}-${architecture}`; + const candidates = [...toolchainLock.profiles] + .filter(([name, profile]) => name === prefix || name.startsWith(`${prefix}-`)) + .filter(([, profile]) => ( + existsSync(resolve(sharpEnvironment, ...profile.sharpPackage.split('/'))) + && existsSync(resolve(sharpEnvironment, ...profile.libvipsPackage.split('/'))) + )); + if (candidates.length !== 1) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_PROFILE_INVALID'); + return Object.freeze({ name: candidates[0][0], packages: candidates[0][1] }); +} + +function verifyPackage(alias, expected, expectedName) { + const beforeCanonical = realpathSync(alias.alias); + if (beforeCanonical !== alias.canonical) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + const observed = computeGreaterRealmPackageTree(alias.canonical); + if ( + observed.treeSha256 !== expected.treeSha256 + || observed.fileCount !== expected.fileCount + || observed.byteCount !== expected.byteCount + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_PACKAGE_TAMPERED'); + parsePackageMetadata(alias.canonical, expectedName, expected.version); + if (realpathSync(alias.alias) !== alias.canonical) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + return Object.freeze({ + canonicalRoot: alias.canonical, + name: expectedName, + treeSha256: observed.treeSha256, + version: expected.version, + }); +} + +function assertResolverBinding(importer, packageName, expectedRoot) { + let resolved; + try { + resolved = realpathSync(createRequire(importer).resolve(packageName)); + } catch (error) { + if ( + expectedRoot === undefined + && error !== null + && typeof error === 'object' + && error.code === 'MODULE_NOT_FOUND' + ) return; + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_RESOLUTION_INVALID'); + } + if ( + expectedRoot === undefined + || resolved === expectedRoot + || !pathInside(expectedRoot, resolved) + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_RESOLUTION_INVALID'); +} + +function assertRuntimeNode(version, configuredRange) { + const match = /^(\d+)\.(\d+)\.(\d+)$/u.exec(version); + if ( + configuredRange !== '>=22.13 <23' + || match === null + || Number(match[1]) !== 22 + || Number(match[2]) < 13 + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_NODE_INVALID'); +} + +export function verifyGreaterRealmTrustedToolchain(input = {}) { + const requestedRepositoryRoot = resolve(input.repositoryRoot ?? ROOT); + let repositoryRoot; + let nodeModulesRoot; + try { + repositoryRoot = realpathSync(requestedRepositoryRoot); + attestDirectory(repositoryRoot); + const requestedNodeModulesRoot = resolve(repositoryRoot, 'node_modules'); + if (!existsSync(requestedNodeModulesRoot)) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_PACKAGE_MISSING'); + } + nodeModulesRoot = realpathSync(requestedNodeModulesRoot); + if (nodeModulesRoot !== requestedNodeModulesRoot) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + attestDirectory(nodeModulesRoot); + } catch (error) { + if (error instanceof Error && error.message.startsWith('GREATER_REALM_')) throw error; + return fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + const toolchainLock = parseToolchainLock(repositoryRoot); + assertRuntimeNode(input.runtimeNode ?? process.versions.node, toolchainLock.configuredNodeEngine); + assertPackageLockBoundary(repositoryRoot, toolchainLock); + + const resolveAlias = (base, name) => { + const alias = resolve(base, ...name.split('/')); + if (!pathInside(nodeModulesRoot, alias) || !existsSync(alias)) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_PACKAGE_MISSING'); + } + const status = lstatSync(alias); + if (!status.isDirectory() && !status.isSymbolicLink()) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + const canonical = realpathSync(alias); + if (canonical === nodeModulesRoot || !pathInside(nodeModulesRoot, canonical)) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); + } + return Object.freeze({ alias, canonical }); + }; + + const expected = name => { + const record = toolchainLock.packages.get(name); + if (record === undefined) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + return record; + }; + const tsxAlias = resolveAlias(nodeModulesRoot, 'tsx'); + const sharpAlias = resolveAlias(nodeModulesRoot, 'sharp'); + const typescriptAlias = resolveAlias(nodeModulesRoot, 'typescript'); + const tsxEnvironment = dirname(tsxAlias.canonical); + const esbuildAlias = resolveAlias(tsxEnvironment, 'esbuild'); + const sharpEnvironment = dirname(sharpAlias.canonical); + const typescriptEnvironment = dirname(typescriptAlias.canonical); + const profile = chooseProfile( + toolchainLock, + input.platform ?? process.platform, + input.architecture ?? process.arch, + sharpEnvironment, + ); + const aliases = Object.freeze({ + '@img/colour': resolveAlias(sharpEnvironment, '@img/colour'), + 'detect-libc': resolveAlias(sharpEnvironment, 'detect-libc'), + esbuild: esbuildAlias, + semver: resolveAlias(sharpEnvironment, 'semver'), + sharp: sharpAlias, + tsx: tsxAlias, + typescript: typescriptAlias, + [profile.packages.esbuildPackage]: resolveAlias( + dirname(esbuildAlias.canonical), + profile.packages.esbuildPackage, + ), + [profile.packages.sharpPackage]: resolveAlias( + sharpEnvironment, + profile.packages.sharpPackage, + ), + [profile.packages.libvipsPackage]: resolveAlias( + sharpEnvironment, + profile.packages.libvipsPackage, + ), + [profile.packages.typescriptPackage]: resolveAlias( + typescriptEnvironment, + profile.packages.typescriptPackage, + ), + ...Object.fromEntries(profile.packages.tsxOptionalPackages.map(name => [ + name, + resolveAlias(tsxEnvironment, name), + ])), + }); + const tsxImporter = resolve(tsxAlias.canonical, 'dist', 'cli.mjs'); + assertResolverBinding(tsxImporter, 'esbuild', esbuildAlias.canonical); + assertResolverBinding( + tsxImporter, + 'fsevents', + profile.packages.tsxOptionalPackages.includes('fsevents') + ? aliases.fsevents?.canonical + : undefined, + ); + const sharpImporter = resolve(sharpAlias.canonical, 'dist', 'index.cjs'); + for (const name of ['@img/colour', 'detect-libc', 'semver']) { + assertResolverBinding(sharpImporter, name, aliases[name]?.canonical); + } + assertResolverBinding( + sharpImporter, + `${profile.packages.sharpPackage}/sharp.node`, + aliases[profile.packages.sharpPackage]?.canonical, + ); + for (const suffix of ['lib', 'package', 'versions']) { + assertResolverBinding( + sharpImporter, + `${profile.packages.libvipsPackage}/${suffix}`, + aliases[profile.packages.libvipsPackage]?.canonical, + ); + } + assertResolverBinding( + resolve(esbuildAlias.canonical, 'lib', 'main.js'), + `${profile.packages.esbuildPackage}/package.json`, + aliases[profile.packages.esbuildPackage]?.canonical, + ); + assertResolverBinding( + resolve(typescriptAlias.canonical, 'package.json'), + `${profile.packages.typescriptPackage}/package.json`, + aliases[profile.packages.typescriptPackage]?.canonical, + ); + const selectedNames = [ + ...COMMON_PACKAGE_NAMES, + profile.packages.esbuildPackage, + profile.packages.sharpPackage, + profile.packages.libvipsPackage, + profile.packages.typescriptPackage, + ...profile.packages.tsxOptionalPackages, + ].toSorted(); + const verifiedPackages = selectedNames.map(name => { + const alias = aliases[name]; + if (alias === undefined) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + return verifyPackage(alias, expected(name), name); + }); + const tsxCli = resolve(tsxAlias.canonical, 'dist', 'cli.mjs'); + const tsxCliStatus = lstatSync(tsxCli); + assertTrustedStatus(tsxCliStatus, 'file'); + if (!verifiedPackages.some(pkg => pkg.name === 'tsx')) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + } + return Object.freeze({ + manifestSha256: toolchainLock.manifestSha256, + profile: profile.name, + tsxCli, + verifiedPackageCount: verifiedPackages.length, + }); +} + +function assertBootstrapInvocation(arguments_, environment) { + if ( + !Array.isArray(arguments_) + || arguments_.some(argument => ( + typeof argument !== 'string' + || FORBIDDEN_SECRET_ARGUMENT.test(argument) + || POSSIBLE_SECRET_VALUE.test(argument) + )) + || Object.entries(environment).some(([key, value]) => ( + RESERVED_ENVIRONMENT_KEY.test(key) + || (typeof value === 'string' && POSSIBLE_SECRET_VALUE.test(value)) + )) + ) fail('GREATER_REALM_PRIVATE_INVOCATION_REJECTED'); + if (DANGEROUS_CHILD_ENVIRONMENT_KEYS.some(key => environment[key] !== undefined)) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_ENVIRONMENT_INVALID'); + } +} + +function main() { + const arguments_ = process.argv.slice(2); + assertBootstrapInvocation(arguments_, process.env); + const verifyOnly = arguments_[0] === '--verify-only'; + if (verifyOnly && arguments_.length !== 1) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_ARGUMENTS_INVALID'); + if (!verifyOnly && !ALLOWED_COMMANDS.includes(arguments_[0])) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_ARGUMENTS_INVALID'); + } + const receipt = verifyGreaterRealmTrustedToolchain(); + if (verifyOnly) { + process.stdout.write(`${JSON.stringify({ + profile: receipt.profile, + verified: true, + verifiedPackageCount: receipt.verifiedPackageCount, + })}\n`); + return; + } + const environment = { ...process.env }; + for (const key of DANGEROUS_CHILD_ENVIRONMENT_KEYS) delete environment[key]; + environment.PATH = dirname(process.execPath); + environment.WKGR_TOOLCHAIN_PREFLIGHT_RECEIPT = `sha256:${receipt.manifestSha256}`; + environment.WKGR_TOOLCHAIN_PREFLIGHT_PROFILE = receipt.profile; + const result = spawnSync( + process.execPath, + [receipt.tsxCli, CLI_PATH, ...arguments_], + { + cwd: ROOT, + env: environment, + shell: false, + stdio: 'inherit', + windowsHide: true, + }, + ); + if (result.error || result.signal !== null || result.status === null) { + fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_CHILD_FAILED'); + } + process.exitCode = result.status; +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + try { + main(); + } catch (error) { + const code = error instanceof Error && /^[A-Z0-9_:-]{3,160}$/u.test(error.message) + ? error.message + : 'GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FAILED'; + process.stderr.write(`${code}\n`); + process.exitCode = 1; + } +} diff --git a/scripts/atlas/greater-realm-toolchain-lock.json b/scripts/atlas/greater-realm-toolchain-lock.json new file mode 100644 index 00000000..fbe7e3d6 --- /dev/null +++ b/scripts/atlas/greater-realm-toolchain-lock.json @@ -0,0 +1,151 @@ +{ + "kind": "warpkeep.greater-realm.trusted-toolchain.v1", + "configuredNodeEngine": ">=22.13 <23", + "configuredPackageManager": "npm@10.9.8", + "packages": { + "@esbuild/darwin-arm64": { + "name": "@esbuild/darwin-arm64", + "version": "0.28.1", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "byteCount": 10574305, + "fileCount": 3, + "treeSha256": "fecfa61c421dfa1730a897230e395beb14b74200c8cc156d4072f9626828359e" + }, + "@esbuild/linux-x64": { + "name": "@esbuild/linux-x64", + "version": "0.28.1", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "byteCount": 11407985, + "fileCount": 3, + "treeSha256": "5f9c8c0fa09cfc177c08191e701faae424371091e48b9f9d399e00e9312ff7d4" + }, + "@img/colour": { + "name": "@img/colour", + "version": "1.1.0", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "byteCount": 73264, + "fileCount": 6, + "treeSha256": "1dc15072cb0094c80e02ccff911c088beb10260d8814fa7e458f6d8c143a59ac" + }, + "@img/sharp-darwin-arm64": { + "name": "@img/sharp-darwin-arm64", + "version": "0.35.3", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "byteCount": 292231, + "fileCount": 5, + "treeSha256": "027ef5a3ac2c792a24bbb60d87fda2a5902ded7ea050237c1c2f8876ebdc5f90" + }, + "@img/sharp-libvips-darwin-arm64": { + "name": "@img/sharp-libvips-darwin-arm64", + "version": "1.3.2", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "byteCount": 17777811, + "fileCount": 6, + "treeSha256": "8ca735f9fa67541493c37d64942744e648d7a95ab420a5296d3c1d840772d8f1" + }, + "@img/sharp-libvips-linux-x64": { + "name": "@img/sharp-libvips-linux-x64", + "version": "1.3.2", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "byteCount": 18207458, + "fileCount": 6, + "treeSha256": "1fc0aa685ee16615086102bee7f94f8948108d6930ec57dd92ce31654d2899b4" + }, + "@img/sharp-linux-x64": { + "name": "@img/sharp-linux-x64", + "version": "0.35.3", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "byteCount": 427138, + "fileCount": 5, + "treeSha256": "5d7ef18137b930a491f988392406a71a03d548c3c1516b2ac0e57d4efe6506d3" + }, + "@typescript/typescript-darwin-arm64": { + "name": "@typescript/typescript-darwin-arm64", + "version": "7.0.2", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "byteCount": 27498111, + "fileCount": 113, + "treeSha256": "6618c5bde7ce109b3cc6c357a2d0ec7f19228ebedf8a46a3e2e1b3cbc07ac19d" + }, + "@typescript/typescript-linux-x64": { + "name": "@typescript/typescript-linux-x64", + "version": "7.0.2", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "byteCount": 27945990, + "fileCount": 114, + "treeSha256": "bb340112db85b021ce4e30c08cc337d06a44e8c482e6cb3de545f56e16469154" + }, + "detect-libc": { + "name": "detect-libc", + "version": "2.1.2", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "byteCount": 26437, + "fileCount": 8, + "treeSha256": "6922afe744bbe530d9c28b13bd038bc7768246af64763b9c2da3081395363675" + }, + "esbuild": { + "name": "esbuild", + "version": "0.28.1", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "byteCount": 146953, + "fileCount": 7, + "treeSha256": "b8bac9fbb12dda03ef60557fa29614cabba79e16be45c50690be1bd038298e8f" + }, + "fsevents": { + "name": "fsevents", + "version": "2.3.3", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "byteCount": 173224, + "fileCount": 6, + "treeSha256": "1126e74d97957d6c93263688a8e784e5142602db287b971e67b2d8c135a531fc" + }, + "semver": { + "name": "semver", + "version": "7.8.5", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "byteCount": 101065, + "fileCount": 53, + "treeSha256": "321f4c0c164fd2b18292d4bef9c6ee9ceaffe05da856967b8cdc8ab1ace85483" + }, + "sharp": { + "name": "sharp", + "version": "0.35.3", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "byteCount": 958466, + "fileCount": 47, + "treeSha256": "a54e8ed7908f47ee1ff50cfce305e230d7ec2bafc850ab616b0f7a6fdcc55851" + }, + "tsx": { + "name": "tsx", + "version": "4.23.0", + "integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==", + "byteCount": 519973, + "fileCount": 50, + "treeSha256": "32619875eeaaabc43d40dc6fac3e44b3cb8fed416cf81d478b14d38731acbb15" + }, + "typescript": { + "name": "typescript", + "version": "7.0.2", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "byteCount": 2497498, + "fileCount": 416, + "treeSha256": "b3ea816c734f221d2ccf3b7f24eaa2c682393e4acd4ee51996d1be96afa7fdc4" + } + }, + "profiles": { + "darwin-arm64": { + "esbuildPackage": "@esbuild/darwin-arm64", + "sharpPackage": "@img/sharp-darwin-arm64", + "libvipsPackage": "@img/sharp-libvips-darwin-arm64", + "typescriptPackage": "@typescript/typescript-darwin-arm64", + "tsxOptionalPackages": ["fsevents"] + }, + "linux-x64": { + "esbuildPackage": "@esbuild/linux-x64", + "sharpPackage": "@img/sharp-linux-x64", + "libvipsPackage": "@img/sharp-libvips-linux-x64", + "typescriptPackage": "@typescript/typescript-linux-x64", + "tsxOptionalPackages": [] + } + } +} diff --git a/scripts/atlas/greater-realm-topography.ts b/scripts/atlas/greater-realm-topography.ts new file mode 100644 index 00000000..26dcf000 --- /dev/null +++ b/scripts/atlas/greater-realm-topography.ts @@ -0,0 +1,702 @@ +import { + type IndexedAxialGrid, +} from './greater-realm-terrain'; + +const NEIGHBOR_COUNT = 6; +const WATER_DRY = 0; +const WATER_OCEAN = 1; +const WATER_LAKE = 2; +const WATER_RIVER = 3; +const WATER_STREAM = 4; +const WATER_SEA = 5; + +export type GreaterRealmTopographyMetrics = Readonly<{ + elevationMinimum: number; + elevationMaximum: number; + slopeP50: number; + slopeP95: number; + ridgeCells: number; + plateauCells: number; + basinCells: number; + coastCells: number; +}>; + +export type GreaterRealmBiomeMetrics = Readonly<{ + visualBiomeClassCount: number; + minimumRegionVisualBiomeClassCount: number; + minimumTierIVisualBiomeClassCount: number; + minimumTierIIVisualBiomeClassCount: number; + tierIIIVisualBiomeClassCount: number; + minimumTierIMajorVisualBiomeClassCount: number; + minimumTierITransitionVisualBiomeClassCount: number; + minimumTierIIMajorVisualBiomeClassCount: number; + tierIIIMajorVisualBiomeClassCount: number; + maximumTierISingleBiomeShareBasisPoints: number; + incompatibleVisualBiomeAdjacencyCount: number; + incompatibleBiomeLandformPairCount: number; +}>; + +export type GreaterRealmDerivedTopography = Readonly<{ + slope: Uint16Array; + aspect: Uint8Array; + profileCurvature: Int32Array; + planCurvature: Int32Array; + wetnessIndex: Uint16Array; + exposure: Int32Array; + distanceToCoast: Uint16Array; + distanceToFreshwater: Uint16Array; + watershedId: Int32Array; + ridgeId: Int32Array; + temperature: Int32Array; + moisture: Int32Array; + biomeId: Uint8Array; + landformId: Uint8Array; + topographyMetrics: GreaterRealmTopographyMetrics; + biomeMetrics: GreaterRealmBiomeMetrics; +}>; + +function fail(code: string): never { + throw new Error(code); +} + +function clamp(value: number, minimum: number, maximum: number): number { + return Math.max(minimum, Math.min(maximum, value)); +} + +function assertLengths(grid: IndexedAxialGrid, fields: readonly ArrayLike[]): void { + if (fields.some(field => field.length !== grid.cellCount)) { + fail('GREATER_REALM_TOPOGRAPHY_INPUT_LENGTH_INVALID'); + } +} + +function distanceFromMask(grid: IndexedAxialGrid, starts: Uint8Array): Uint16Array { + const distance = new Uint16Array(grid.cellCount); + distance.fill(0xffff); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (starts[cell] !== 1) continue; + distance[cell] = 0; + queue[tail++] = cell; + } + if (tail === 0) fail('GREATER_REALM_TOPOGRAPHY_DISTANCE_SOURCE_MISSING'); + while (head < tail) { + const cell = queue[head++]!; + const nextDistance = distance[cell]! + 1; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || distance[neighbor]! <= nextDistance) continue; + distance[neighbor] = nextDistance; + queue[tail++] = neighbor; + } + } + return distance; +} + +function watershedIds(grid: IndexedAxialGrid, receiver: Int32Array): Int32Array { + const rootByCell = new Int32Array(grid.cellCount); + rootByCell.fill(-2); + for (let start = 0; start < grid.cellCount; start += 1) { + if (rootByCell[start] >= 0) continue; + const chain: number[] = []; + let cursor = start; + while (rootByCell[cursor] === -2) { + rootByCell[cursor] = -3; + chain.push(cursor); + const downstream = receiver[cursor]!; + if (downstream === -1) break; + if (downstream < 0 || downstream >= grid.cellCount) { + fail('GREATER_REALM_TOPOGRAPHY_FLOW_INVALID'); + } + cursor = downstream; + } + if (rootByCell[cursor] === -3 && chain.at(-1) !== cursor) { + fail('GREATER_REALM_TOPOGRAPHY_FLOW_CYCLE'); + } + const root = receiver[cursor] === -1 + ? cursor + : rootByCell[cursor]!; + if (root < 0) fail('GREATER_REALM_TOPOGRAPHY_FLOW_CYCLE'); + for (const cell of chain) rootByCell[cell] = root; + } + const rootSet = new Set(); + for (const root of rootByCell) rootSet.add(root); + const roots = [...rootSet].sort((first, second) => first - second); + const idByRoot = new Map(roots.map((root, index) => [root, index + 1] as const)); + const result = new Int32Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + result[cell] = idByRoot.get(rootByCell[cell]!) ?? 0; + } + return result; +} + +function connectedRidgeIds(grid: IndexedAxialGrid, ridgeMask: Uint8Array): Int32Array { + const result = new Int32Array(grid.cellCount); + const seen = new Uint8Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + let nextId = 1; + for (let start = 0; start < grid.cellCount; start += 1) { + if (ridgeMask[start] !== 1 || seen[start] === 1) continue; + let head = 0; + let tail = 0; + queue[tail++] = start; + seen[start] = 1; + const cells: number[] = []; + while (head < tail) { + const cell = queue[head++]!; + cells.push(cell); + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || ridgeMask[neighbor] !== 1 || seen[neighbor] === 1) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + } + } + if (cells.length < 8) continue; + for (const cell of cells) result[cell] = nextId; + nextId += 1; + } + return result; +} + +function percentile(sorted: readonly number[], basisPoints: number): number { + if (sorted.length === 0) return 0; + return sorted[Math.floor(((sorted.length - 1) * basisPoints) / 10_000)]!; +} + +// A biome and its landform are one visual classification. Keeping the allowed +// pairs explicit makes it possible to prove that smoothing did not create a +// combination which no classifier branch can emit. The additional Lowlands +// pairs are the frozen legacy surface vocabulary and must remain valid even +// when the surrounding continent evolves. +const DRY_LANDFORMS_BY_BIOME: Readonly>> = Object.freeze({ + 1: new Set([3, 4]), + 2: new Set([3, 5]), + 3: new Set([1]), + 4: new Set([3, 5]), + 5: new Set([1, 3, 5, 15]), + 6: new Set([7, 14]), + 7: new Set([6, 7, 14]), + 8: new Set([3, 4, 14]), + 9: new Set([3, 4]), + 10: new Set([3, 4]), + 11: new Set([9, 13]), + 12: new Set([6, 9, 13]), + 13: new Set([9]), + 14: new Set([7, 12]), + 15: new Set([3, 4, 7, 12]), + 16: new Set([12]), + 17: new Set([12]), + 18: new Set([11]), + 19: new Set([5, 6, 7]), + 23: new Set([0, 17]), +}); + +function isCompatibleBiomeLandformPair( + waterRegime: number, + biome: number, + landform: number, +): boolean { + if (waterRegime === WATER_OCEAN || waterRegime === WATER_SEA) { + return biome === 20 && landform === 16; + } + if (waterRegime === WATER_LAKE) return biome === 21 && landform === 10; + if (waterRegime === WATER_RIVER || waterRegime === WATER_STREAM) { + return biome === 22 && landform === 2; + } + if (waterRegime !== WATER_DRY) return false; + return DRY_LANDFORMS_BY_BIOME[biome]?.has(landform) === true; +} + +/** + * Derive private, fixed-point terrain authority after final water routing. + * These fields are offline candidate evidence; they do not activate runtime + * topography or expose hidden regions to a client. + */ +export function deriveGreaterRealmTopography(input: Readonly<{ + grid: IndexedAxialGrid; + elevation: Int32Array; + flowReceiver: Int32Array; + flowAccumulation: BigUint64Array; + waterRegime: Uint8Array; + geologyId: Uint8Array; + tectonicUplift: Int32Array; + rockResistance: Int32Array; + regionId: Uint8Array; + tierId: Uint8Array; + legacyProtectedCell: Uint8Array; + protectedBiomeId: Uint8Array; + protectedLandformId: Uint8Array; + geomorphicTemperature: Int32Array; + geomorphicMoisture: Int32Array; + geomorphicGlacialMask: Uint8Array; + geomorphicAridMask: Uint8Array; + geomorphicVolcanicMask: Uint8Array; + geomorphicCoastalClass: Uint8Array; +}>): GreaterRealmDerivedTopography { + const { + grid, + elevation, + flowReceiver, + flowAccumulation, + waterRegime, + geologyId, + tectonicUplift, + rockResistance, + regionId, + tierId, + legacyProtectedCell, + protectedBiomeId, + protectedLandformId, + geomorphicTemperature, + geomorphicMoisture, + geomorphicGlacialMask, + geomorphicAridMask, + geomorphicVolcanicMask, + geomorphicCoastalClass, + } = input; + assertLengths(grid, [ + elevation, + flowReceiver, + flowAccumulation, + waterRegime, + geologyId, + tectonicUplift, + rockResistance, + regionId, + tierId, + legacyProtectedCell, + protectedBiomeId, + protectedLandformId, + geomorphicTemperature, + geomorphicMoisture, + geomorphicGlacialMask, + geomorphicAridMask, + geomorphicVolcanicMask, + geomorphicCoastalClass, + ]); + + const coastMask = new Uint8Array(grid.cellCount); + const saltwaterMask = new Uint8Array(grid.cellCount); + const freshwaterMask = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (waterRegime[cell] === WATER_OCEAN || waterRegime[cell] === WATER_SEA) { + saltwaterMask[cell] = 1; + } + if ( + waterRegime[cell] === WATER_LAKE + || waterRegime[cell] === WATER_RIVER + || waterRegime[cell] === WATER_STREAM + ) freshwaterMask[cell] = 1; + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (waterRegime[cell] !== WATER_DRY) continue; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && saltwaterMask[neighbor] === 1) { + coastMask[cell] = 1; + break; + } + } + } + const distanceToCoast = distanceFromMask(grid, saltwaterMask); + const distanceToFreshwater = distanceFromMask(grid, freshwaterMask); + const slope = new Uint16Array(grid.cellCount); + const aspect = new Uint8Array(grid.cellCount); + aspect.fill(6); + const profileCurvature = new Int32Array(grid.cellCount); + const planCurvature = new Int32Array(grid.cellCount); + const wetnessIndex = new Uint16Array(grid.cellCount); + const exposure = new Int32Array(grid.cellCount); + const ridgeMask = new Uint8Array(grid.cellCount); + const landSlopes: number[] = []; + let plateauCells = 0; + let basinCells = 0; + + for (let cell = 0; cell < grid.cellCount; cell += 1) { + let maximumDrop = 0; + let steepestDirection = 6; + let neighborSum = 0; + let neighborCount = 0; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0) continue; + neighborCount += 1; + neighborSum += elevation[neighbor]!; + const drop = elevation[cell]! - elevation[neighbor]!; + if (drop > maximumDrop || (drop === maximumDrop && direction < steepestDirection)) { + maximumDrop = drop; + steepestDirection = direction; + } + } + const meanNeighbor = neighborCount === 0 ? elevation[cell]! : Math.round(neighborSum / neighborCount); + const maximumAbsoluteDrop = (() => { + let result = 0; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0) result = Math.max(result, Math.abs(elevation[cell]! - elevation[neighbor]!)); + } + return result; + })(); + slope[cell] = clamp(maximumAbsoluteDrop, 0, 0xffff); + aspect[cell] = maximumDrop > 0 ? steepestDirection : 6; + exposure[cell] = clamp(elevation[cell]! - meanNeighbor, -0x7fff_ffff, 0x7fff_ffff); + planCurvature[cell] = clamp( + elevation[cell]! * neighborCount - neighborSum, + -0x7fff_ffff, + 0x7fff_ffff, + ); + const downstream = flowReceiver[cell]!; + if (downstream >= 0) { + const next = flowReceiver[downstream]!; + const firstGradient = elevation[cell]! - elevation[downstream]!; + const secondGradient = next < 0 ? 0 : elevation[downstream]! - elevation[next]!; + profileCurvature[cell] = clamp( + firstGradient - secondGradient, + -0x7fff_ffff, + 0x7fff_ffff, + ); + } + wetnessIndex[cell] = clamp( + Math.floor((Number(flowAccumulation[cell]!) * 4_096) / (250 + slope[cell]!)), + 0, + 0xffff, + ); + if (waterRegime[cell] === WATER_DRY) { + landSlopes.push(slope[cell]!); + if (slope[cell]! <= 550 && elevation[cell]! >= 4_000 && exposure[cell]! >= -120) { + plateauCells += 1; + } + if (planCurvature[cell]! < -1_200 && exposure[cell]! < -250) basinCells += 1; + if ( + elevation[cell]! >= 5_500 + && slope[cell]! >= 900 + && (planCurvature[cell]! > 900 || tectonicUplift[cell]! > 4_000) + ) ridgeMask[cell] = 1; + } + } + landSlopes.sort((first, second) => first - second); + const ridgeId = connectedRidgeIds(grid, ridgeMask); + const watershedId = watershedIds(grid, flowReceiver); + + const temperature = new Int32Array(geomorphicTemperature); + const moisture = new Int32Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + moisture[cell] = clamp( + geomorphicMoisture[cell]! + + Math.max(0, 1_800 - distanceToFreshwater[cell]! * 180) + + Math.max(0, 1_000 - distanceToCoast[cell]! * 70) + + Math.min(4_500, wetnessIndex[cell]! * 3), + -10_000, + 16_000, + ); + } + + const biomeId = new Uint8Array(grid.cellCount); + const landformId = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (legacyProtectedCell[cell] === 1) { + biomeId[cell] = protectedBiomeId[cell]!; + landformId[cell] = protectedLandformId[cell]!; + continue; + } + const regime = waterRegime[cell]!; + if (regime !== WATER_DRY) { + const saltwater = regime === WATER_OCEAN || regime === WATER_SEA; + biomeId[cell] = saltwater ? 20 : regime === WATER_LAKE ? 21 : 22; + landformId[cell] = saltwater ? 16 : regime === WATER_LAKE ? 10 : 2; + continue; + } + const coastClass = geomorphicCoastalClass[cell]!; + const cold = temperature[cell]! < 2_000; + const arid = moisture[cell]! < -1_200; + const saturated = moisture[cell]! > 4_300 || wetnessIndex[cell]! > 5_000; + const high = elevation[cell]! > 13_500; + const steep = slope[cell]! > 1_500; + const volcanic = tectonicUplift[cell]! > 6_500 + && geologyId[cell]! % 3 === 0 + && rockResistance[cell]! > 4_000; + const nearCoast = distanceToCoast[cell]! <= 1; + const nearFreshwater = distanceToFreshwater[cell]! <= 2; + const delta = nearCoast && nearFreshwater && flowAccumulation[cell]! >= 96n && slope[cell]! < 700; + if (coastClass === 3 || delta) { + biomeId[cell] = 18; + landformId[cell] = 11; + } else if (coastClass === 4 || geomorphicGlacialMask[cell] === 1) { + biomeId[cell] = 5; + landformId[cell] = 15; + } else if (coastClass === 2) { + biomeId[cell] = 23; + landformId[cell] = 17; + } else if (geomorphicVolcanicMask[cell] === 1) { + biomeId[cell] = moisture[cell]! < 500 ? 15 : 14; + landformId[cell] = steep ? 7 : 12; + } else if (geomorphicAridMask[cell] === 1) { + biomeId[cell] = slope[cell]! < 500 ? 11 : slope[cell]! > 1_400 ? 13 : 12; + landformId[cell] = slope[cell]! < 500 ? 13 : slope[cell]! > 1_400 ? 9 : 6; + } else if (cold && high) { + biomeId[cell] = temperature[cell]! < 500 ? 6 : 7; + landformId[cell] = steep ? 7 : 14; + } else if (volcanic) { + biomeId[cell] = moisture[cell]! < 500 ? 15 : 14; + landformId[cell] = steep ? 7 : 12; + } else if (arid) { + biomeId[cell] = slope[cell]! < 500 ? 11 : slope[cell]! > 1_400 ? 13 : 12; + landformId[cell] = slope[cell]! < 500 ? 13 : slope[cell]! > 1_400 ? 9 : 6; + } else if (saturated && nearFreshwater && slope[cell]! < 650) { + biomeId[cell] = nearCoast ? 17 : 16; + landformId[cell] = 12; + } else if (high || steep) { + biomeId[cell] = 19; + landformId[cell] = high ? 7 : 6; + } else if (moisture[cell]! > 2_800) { + // Humid country is not one blanket forest. Temperature and shelter + // split it into cool forest, sheltered woodland, and open wet forest + // using continuous climate/topography authority rather than region IDs. + biomeId[cell] = temperature[cell]! < 3_500 ? 5 : exposure[cell]! < -45 ? 3 : 2; + landformId[cell] = exposure[cell]! < -45 ? 1 : 5; + } else if (temperature[cell]! > 6_500 && moisture[cell]! < 700) { + biomeId[cell] = moisture[cell]! < -200 ? 9 : 10; + landformId[cell] = slope[cell]! < 550 ? 3 : 4; + } else if (coastClass === 1 || nearCoast) { + biomeId[cell] = 23; + landformId[cell] = steep ? 17 : 0; + } else if ( + distanceToFreshwater[cell]! <= 2 + && slope[cell]! < 550 + && moisture[cell]! > 800 + ) { + // Broad riparian meadows follow the actual drainage network. They also + // break up otherwise dominant mesic plains without quota-painting by + // strategic region. + biomeId[cell] = 4; + landformId[cell] = moisture[cell]! > 1_900 ? 5 : 3; + } else if (moisture[cell]! > 800 && exposure[cell]! < -55) { + biomeId[cell] = 3; + landformId[cell] = 1; + } else if (moisture[cell]! > 1_900 || wetnessIndex[cell]! > 20) { + biomeId[cell] = 2; + landformId[cell] = 5; + } else if ( + moisture[cell]! > 800 + && exposure[cell]! >= 170 + && exposure[cell]! < 240 + ) { + // A narrow, naturally occurring exposed-steppe ecotone supplies visual + // transition without introducing a synthetic checkerboard field. + biomeId[cell] = 10; + landformId[cell] = slope[cell]! < 550 ? 3 : 4; + } else if (moisture[cell]! > 800 && exposure[cell]! >= 55) { + biomeId[cell] = 8; + landformId[cell] = slope[cell]! < 550 ? 3 : 4; + } else { + biomeId[cell] = moisture[cell]! > 800 ? 1 : 8; + landformId[cell] = slope[cell]! < 550 ? 3 : 4; + } + } + + // A cold summit directly touching a hot arid process cell is an impossible + // visual jump. Glacial process cells were already classified as biome 5; + // soften only ordinary cold/high cells into a cool-forest transition. + const hotAridBiomes = new Set([11, 12, 13, 15]); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (waterRegime[cell] !== WATER_DRY || (biomeId[cell] !== 6 && biomeId[cell] !== 7)) continue; + let touchesHotArid = false; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && hotAridBiomes.has(biomeId[neighbor]!)) { + touchesHotArid = true; + break; + } + } + if (touchesHotArid) { + biomeId[cell] = 5; + landformId[cell] = 5; + } + } + + const smoothingLocked = (cell: number): boolean => ( + waterRegime[cell] !== WATER_DRY + || legacyProtectedCell[cell] === 1 + || landformId[cell] === 7 + || landformId[cell] === 9 + || landformId[cell] === 11 + || landformId[cell] === 15 + || landformId[cell] === 17 + || geomorphicGlacialMask[cell] === 1 + || geomorphicAridMask[cell] === 1 + || geomorphicVolcanicMask[cell] === 1 + || geomorphicCoastalClass[cell] !== 0 + ); + + // Remove isolated visual speckles without moving water, protected Lowlands, + // climate extremes, or distinctive geomorphology. Biome and landform move as + // one pair: smoothing only the biome can synthesize a visual combination + // which the deterministic classifier itself would never emit. + for (let pass = 0; pass < 2; pass += 1) { + const nextBiome = new Uint8Array(biomeId); + const nextLandform = new Uint8Array(landformId); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (smoothingLocked(cell)) continue; + // Preserve the established biome-majority rule, then choose the most + // common compatible landform carried by neighbors of that biome. This + // prevents a biome split across two valid landforms from disabling the + // smoothing which would previously have occurred. + const biomeCounts = new Uint8Array(24); + const pairCounts = new Uint8Array(24 * 32); + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || waterRegime[neighbor] !== WATER_DRY) continue; + if (!isCompatibleBiomeLandformPair( + WATER_DRY, + biomeId[neighbor]!, + landformId[neighbor]!, + )) continue; + biomeCounts[biomeId[neighbor]!] += 1; + pairCounts[biomeId[neighbor]! * 32 + landformId[neighbor]!] += 1; + } + let bestBiome = biomeId[cell]!; + let bestCount = biomeCounts[bestBiome]!; + for (let biome = 0; biome < biomeCounts.length; biome += 1) { + if (biomeCounts[biome]! > bestCount) { + bestBiome = biome; + bestCount = biomeCounts[biome]!; + } + } + if (bestCount >= 4) { + let bestLandform = biomeId[cell] === bestBiome ? landformId[cell]! : 0; + let bestPairCount = biomeId[cell] === bestBiome + ? pairCounts[bestBiome * 32 + bestLandform]! + : -1; + for (let landform = 0; landform < 32; landform += 1) { + const count = pairCounts[bestBiome * 32 + landform]!; + if (count > bestPairCount) { + bestLandform = landform; + bestPairCount = count; + } + } + nextBiome[cell] = bestBiome; + nextLandform[cell] = bestLandform; + } + } + biomeId.set(nextBiome); + landformId.set(nextLandform); + } + + let incompatibleBiomeLandformPairCount = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (!isCompatibleBiomeLandformPair( + waterRegime[cell]!, + biomeId[cell]!, + landformId[cell]!, + )) incompatibleBiomeLandformPairCount += 1; + } + + const regionBiomeCounts = Array.from({ length: 10 }, () => new Uint32Array(24)); + const regionLandCounts = new Uint32Array(10); + const visualBiomes = new Set(); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (waterRegime[cell] !== WATER_DRY) continue; + const region = regionId[cell]!; + const biome = biomeId[cell]!; + if (region >= regionBiomeCounts.length || biome >= 24 || tierId[cell]! < 1 || tierId[cell]! > 3) { + fail('GREATER_REALM_TOPOGRAPHY_CLASSIFICATION_INVALID'); + } + regionBiomeCounts[region]![biome] += 1; + regionLandCounts[region] += 1; + visualBiomes.add(biome); + } + const perRegionBiomeClasses = regionBiomeCounts.map(counts => ( + [...counts].filter(count => count > 0).length + )); + const perRegionMajorBiomeClasses = regionBiomeCounts.map((counts, region) => ( + [...counts].filter(count => ( + regionLandCounts[region]! > 0 + && count * 10_000 >= regionLandCounts[region]! * 500 + )).length + )); + const perRegionTransitionBiomeClasses = regionBiomeCounts.map((counts, region) => ( + [...counts].filter(count => ( + regionLandCounts[region]! > 0 + && count * 10_000 >= regionLandCounts[region]! * 50 + && count * 10_000 < regionLandCounts[region]! * 500 + )).length + )); + let maximumTierISingleBiomeShareBasisPoints = 0; + for (let region = 0; region < 6; region += 1) { + const maximum = Math.max(...regionBiomeCounts[region]!); + const landCount = regionLandCounts[region]!; + maximumTierISingleBiomeShareBasisPoints = Math.max( + maximumTierISingleBiomeShareBasisPoints, + landCount === 0 ? 10_000 : Math.round((maximum * 10_000) / landCount), + ); + } + let ridgeCells = 0; + for (const id of ridgeId) ridgeCells += id > 0 ? 1 : 0; + const frozenBiomes = new Set([6, 7]); + let incompatibleVisualBiomeAdjacencyCount = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (waterRegime[cell] !== WATER_DRY) continue; + for (let direction = 0; direction < 3; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || waterRegime[neighbor] !== WATER_DRY) continue; + if ( + (frozenBiomes.has(biomeId[cell]!) && hotAridBiomes.has(biomeId[neighbor]!)) + || (hotAridBiomes.has(biomeId[cell]!) && frozenBiomes.has(biomeId[neighbor]!)) + ) incompatibleVisualBiomeAdjacencyCount += 1; + } + } + let elevationMinimum = 0x7fff_ffff; + let elevationMaximum = -0x8000_0000; + for (const value of elevation) { + elevationMinimum = Math.min(elevationMinimum, value); + elevationMaximum = Math.max(elevationMaximum, value); + } + return Object.freeze({ + slope, + aspect, + profileCurvature, + planCurvature, + wetnessIndex, + exposure, + distanceToCoast, + distanceToFreshwater, + watershedId, + ridgeId, + temperature, + moisture, + biomeId, + landformId, + topographyMetrics: Object.freeze({ + elevationMinimum, + elevationMaximum, + slopeP50: percentile(landSlopes, 5_000), + slopeP95: percentile(landSlopes, 9_500), + ridgeCells, + plateauCells, + basinCells, + coastCells: coastMask.reduce((total, value) => total + value, 0), + }), + biomeMetrics: Object.freeze({ + visualBiomeClassCount: visualBiomes.size, + minimumRegionVisualBiomeClassCount: Math.min(...perRegionBiomeClasses), + minimumTierIVisualBiomeClassCount: Math.min(...perRegionBiomeClasses.slice(0, 6)), + minimumTierIIVisualBiomeClassCount: Math.min(...perRegionBiomeClasses.slice(6, 9)), + tierIIIVisualBiomeClassCount: perRegionBiomeClasses[9]!, + minimumTierIMajorVisualBiomeClassCount: Math.min( + ...perRegionMajorBiomeClasses.slice(0, 6), + ), + minimumTierITransitionVisualBiomeClassCount: Math.min( + ...perRegionTransitionBiomeClasses.slice(0, 6), + ), + minimumTierIIMajorVisualBiomeClassCount: Math.min( + ...perRegionMajorBiomeClasses.slice(6, 9), + ), + tierIIIMajorVisualBiomeClassCount: perRegionMajorBiomeClasses[9]!, + maximumTierISingleBiomeShareBasisPoints, + incompatibleVisualBiomeAdjacencyCount, + incompatibleBiomeLandformPairCount, + }), + }); +} diff --git a/scripts/atlas/verify-public-boundary.mjs b/scripts/atlas/verify-public-boundary.mjs new file mode 100644 index 00000000..2b1d2327 --- /dev/null +++ b/scripts/atlas/verify-public-boundary.mjs @@ -0,0 +1,1441 @@ +import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { + constants, + fstatSync, + lstatSync, + openSync, + closeSync, + readSync, + readdirSync, + realpathSync, + statSync, +} from 'node:fs'; +import { basename, delimiter, dirname, join, relative, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { + GREATER_REALM_PRIVATE_MARKER_OVERLAP_BYTES, + GREATER_REALM_PRIVATE_MARKER_TEXT, + containsGreaterRealmPrivateMarker, +} from './greater-realm-private-markers.mjs'; + +const MAXIMUM_GIT_OUTPUT_BYTES = 16 * 1024 * 1024; +const MAXIMUM_GIT_BLOB_BATCH_BYTES = 16 * 1024 * 1024; +const MAXIMUM_SCANNED_TEXT_BYTES = 16 * 1024 * 1024; +const MAXIMUM_SCANNED_BINARY_BYTES = 128 * 1024 * 1024; +const BINARY_SCAN_CHUNK_BYTES = 64 * 1024; +const MAXIMUM_TREE_ENTRIES = 250_000; +const TRUSTED_GIT_CANDIDATES = Object.freeze({ + aix: Object.freeze(['/usr/bin/git', '/usr/local/bin/git']), + darwin: Object.freeze(['/usr/bin/git']), + freebsd: Object.freeze(['/usr/local/bin/git', '/usr/bin/git']), + linux: Object.freeze(['/usr/bin/git']), + netbsd: Object.freeze(['/usr/pkg/bin/git', '/usr/local/bin/git', '/usr/bin/git']), + openbsd: Object.freeze(['/usr/local/bin/git', '/usr/bin/git']), + sunos: Object.freeze(['/usr/bin/git', '/usr/local/bin/git']), + win32: Object.freeze([ + 'C:\\Program Files\\Git\\cmd\\git.exe', + 'C:\\Program Files\\Git\\bin\\git.exe', + 'C:\\Program Files (x86)\\Git\\cmd\\git.exe', + 'C:\\Program Files (x86)\\Git\\bin\\git.exe', + ]), +}); +const TRUSTED_GIT_TIMEOUT_MS = 10_000; +const PRIVATE_PATH_COMPONENTS = new Set([ + '.warpkeep-private', + 'greater-realm-private', +]); +const PRIVATE_EXACT_FILENAMES = new Set([ + 'seed.bin', + 'batch-seed.bin', + 'manifest.private.json', + 'batch.private.json', + 'selection.private.json', + 'shortlist.private.json', +]); +const PRIVATE_EXTENSIONS = Object.freeze([ + '.wkgr-atlas', + '.wkgr-checkpoint', + '.wkgr-private', +]); +const OWNER_PREVIEW_EXTENSIONS = new Set([ + '.avif', + '.gif', + '.html', + '.jpeg', + '.jpg', + '.png', + '.svg', + '.webp', +]); +const TEXT_EXTENSIONS = new Set([ + '.bash', + '.cjs', + '.cts', + '.css', + '.example', + '.fish', + '.gql', + '.graphql', + '.html', + '.ini', + '.js', + '.json', + '.jsx', + '.lock', + '.md', + '.mjs', + '.map', + '.mts', + '.properties', + '.py', + '.rb', + '.rs', + '.sh', + '.sql', + '.svelte', + '.template', + '.ts', + '.tsx', + '.toml', + '.txt', + '.vue', + '.xml', + '.yaml', + '.yml', + '.zsh', +]); +const TEXT_EXACT_FILENAMES = new Set([ + 'cmakelists.txt', + 'dockerfile', + 'gemfile', + 'justfile', + 'makefile', + 'procfile', +]); +const PRIVATE_TEXT_FIELD = /(?:["'](?:privateSeedHex|seedMaterial|seedBytes|hiddenCellPayload|privateCanvasDescriptor)["']|(?:^|[^\p{ID_Continue}$])(?:privateSeedHex|seedMaterial|seedBytes|hiddenCellPayload|privateCanvasDescriptor))\s*:/mu; +const PRIVATE_EVIDENCE_FIELD = /["'](?:layoutDigest|stageDigest|packageDigest)["']\s*:/u; +const PRIVATE_IDENTIFIER_STRING_VALUE = /(?:(?:\b(?:seed|private)[A-Za-z0-9_$]*\b)|(?:\b[A-Za-z_$][A-Za-z0-9_$]*(?:seed|private)[A-Za-z0-9_$]*\b)|(?:\\?["'`][A-Za-z0-9_$.-]*(?:seed|private)[A-Za-z0-9_$.-]*\\?["'`]))\s*(?::|=)\s*\\?["'`]([A-Za-z0-9+/_=-]{43,64})\\?["'`]/giu; +const INLINE_DATA_SOURCE_MAP = /sourceMappingURL\s*=\s*data:/iu; +// This runtime-only mirror intentionally fails closed when the public review +// contract changes. Evidence cannot enter either the staged index or worktree +// until this exact schema and its focused parity tests are updated together. +const SANITIZED_REVIEW_EVIDENCE_PATH = + /^docs\/evidence\/greater-realm\/[a-z0-9][a-z0-9._-]{0,126}\.json$/u; +const SANITIZED_REVIEW_EVIDENCE_PREFIX = 'docs/evidence/greater-realm/'; +const SANITIZED_REVIEW_EVIDENCE_README = + 'docs/evidence/greater-realm/README.md'; +const SANITIZED_REVIEW_EVIDENCE_README_BYTES = 1_371; +const SANITIZED_REVIEW_EVIDENCE_README_SHA256 = + '77261c47d7511547ca5265b7b358e5676d939a69aa9a15766d7906a0282d0450'; +const SANITIZED_REVIEW_MAXIMUM_BYTES = 4 * 1024 * 1024; +const SANITIZED_REVIEW_SCHEMA = 'warpkeep.greater-realm.candidate-review.v1'; +const SANITIZED_REVIEW_PRIVACY_BOUNDARY = + 'aggregate-only-no-private-generation-material-v1'; +const SANITIZED_REVIEW_PROOF_KEYS = Object.freeze([ + 'activeMaskConnected', + 'advancedGeomorphology', + 'approvedCellRange', + 'barriersHaveNoBypass', + 'biomeCoherence', + 'biomeDiversity', + 'castleCapacity', + 'deepOceanBoundary', + 'dormantThroneAnchor', + 'gateApproaches', + 'gateGraph', + 'geologicalHighlandBarriers', + 'hydrologyAcyclic', + 'hydrologySurfaceConsistency', + 'legacyLowlandsPreserved', + 'naturalLandmassTopology', + 'naturalStrategicRegions', + 'naturalOuterBoundary', + 'regionPassableLand', + 'regionLandCoherence', + 'regionGraph', +]); +const SANITIZED_REVIEW_CANDIDATE_KEYS = Object.freeze([ + 'candidateHandle', + 'eligible', + 'activeCellCount', + 'landCellCount', + 'waterCellCount', + 'tierCellCounts', + 'regionSizeRanges', + 'hydrology', + 'geology', + 'topography', + 'biomes', + 'quality', + 'gateCount', + 'castleSlotCount', + 'proofs', + 'performance', + 'insideApprovedRange', + 'landBasisPoints', + 'waterBasisPoints', + 'tierBasisPoints', +]); +const SANITIZED_REVIEW_FORBIDDEN_KEY = + /(?:^|_)(?:q|r|x|y|z)(?:$|_)|coord|latitude|longitude|seed|transform|translation|rotation|chunk|layoutdigest|stagedigest|packagedigest|preview|screenshot|thumbnail|image|filepath|pathname|url/iu; +const SANITIZED_REVIEW_FORBIDDEN_STRING = + /(?:data:image\/|(?:^|[\\/])[^\r\n]*\.(?:avif|gif|jpe?g|png|svg|webp)(?:$|[?#])|WKGR[_-]PRIVATE|warpkeep\.greater-realm\.private)/iu; +const SANITIZED_REVIEW_CANDIDATE_HANDLE = /^GR-A-[A-Z2-7]{16}$/u; +const SANITIZED_REVIEW_BATCH_HANDLE = /^GR-B-[A-Z2-7]{16}$/u; +const SANITIZED_REVIEW_SOURCE_COMMIT = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/u; +const SANITIZED_REVIEW_GENERATOR_VERSION = + /^[a-z0-9](?:[a-z0-9.-]{0,62}[a-z0-9])?$/u; +const SANITIZED_REVIEW_SHA256 = /^[0-9a-f]{64}$/u; + +function privateMarkerText(index) { + const marker = GREATER_REALM_PRIVATE_MARKER_TEXT[index]; + if (marker === undefined) fail('GREATER_REALM_PUBLIC_BOUNDARY_SOURCE_ALLOWANCE_INVALID'); + return marker; +} + +function sourceAllowance(fragment, count = 1) { + return Object.freeze({ fragment, count }); +} + +function markerConstAllowance(index) { + return sourceAllowance(`'${privateMarkerText(index)}' as const`); +} + +function privateFieldName(first, second) { + return `${first}${second}`; +} + +// Generator source necessarily declares a small private vocabulary. Allow only +// the exact reviewed source fragments and exact occurrence counts below. The +// rest of each file is scanned normally, so appending a seed, marker, manifest, +// or preview payload to one of these files cannot hide behind a file allowlist. +const TRACKED_PRIVATE_SOURCE_ALLOWANCES = new Map([ + ['scripts/atlas/greater-realm-candidate-generator.ts', Object.freeze([ + markerConstAllowance(2), + markerConstAllowance(5), + sourceAllowance(`${privateFieldName('seed', 'Material')}: Buffer;`), + sourceAllowance(`${privateFieldName('seed', 'Material')}: Uint8Array,`), + ])], + ['scripts/atlas/greater-realm-candidate-package.ts', Object.freeze([ + markerConstAllowance(3), + markerConstAllowance(11), + markerConstAllowance(12), + sourceAllowance(`.update('${privateMarkerText(13)}\\0', 'utf8')`), + sourceAllowance(`${privateFieldName('seed', 'Material')}: Uint8Array;`), + sourceAllowance(`${privateFieldName('seed', 'Material')}: input.candidate.${privateFieldName('seed', 'Material')},`), + sourceAllowance(`${privateFieldName('seed', 'Material')}: derivedSeed,`), + ])], + ['scripts/atlas/greater-realm-cli.ts', Object.freeze([ + sourceAllowance(`kind: '${privateMarkerText(7)}',`), + sourceAllowance(`row.kind !== '${privateMarkerText(7)}'`), + sourceAllowance(`kind: '${privateMarkerText(8)}',`), + sourceAllowance(`row.kind !== '${privateMarkerText(8)}'`), + sourceAllowance(`kind: '${privateMarkerText(9)}' as const,`), + ])], + ['scripts/atlas/greater-realm-legacy-lowlands.ts', Object.freeze([ + markerConstAllowance(10), + ])], + ['scripts/atlas/greater-realm-private-seed.ts', Object.freeze([ + sourceAllowance(`const PRIVATE_SEED_MARKER = '${privateMarkerText(4)}' as const;`), + ])], + ['scripts/atlas/greater-realm-private-markers.mjs', Object.freeze([ + ...GREATER_REALM_PRIVATE_MARKER_TEXT.map(marker => sourceAllowance(`'${marker}',`)), + ])], + ['tests/greaterRealmCandidateGenerator.test.ts', Object.freeze([ + sourceAllowance( + `${privateFieldName('seed', 'Material')}: Buffer.from(candidate.${privateFieldName('seed', 'Material')}),`, + ), + ])], +]); + +export class GreaterRealmPublicBoundaryError extends Error { + constructor(code) { + super(code); + this.name = 'GreaterRealmPublicBoundaryError'; + this.code = code; + } +} + +function fail(code) { + throw new GreaterRealmPublicBoundaryError(code); +} + +function extension(path) { + const name = basename(path).toLowerCase(); + const index = name.lastIndexOf('.'); + return index < 0 ? '' : name.slice(index); +} + +function knownTextPath(path) { + const name = basename(path).toLowerCase(); + return TEXT_EXTENSIONS.has(extension(path)) || TEXT_EXACT_FILENAMES.has(name); +} + +function normalizedRelativePath(path) { + if ( + typeof path !== 'string' + || path.length === 0 + || path.length > 4_096 + || path.includes('\0') + || path.normalize('NFC') !== path + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_PATH_INVALID'); + const normalized = path.replaceAll('\\', '/'); + const components = normalized.split('/'); + if (components.some(component => ( + component.length === 0 + || component === '.' + || component === '..' + || component.normalize('NFC') !== component + ))) fail('GREATER_REALM_PUBLIC_BOUNDARY_PATH_INVALID'); + return normalized; +} + +function privateArtifactPath(relativePath) { + const normalized = normalizedRelativePath(relativePath); + const components = normalized.toLowerCase().split('/'); + const name = components.at(-1); + return components.some(component => PRIVATE_PATH_COMPONENTS.has(component)) + || PRIVATE_EXACT_FILENAMES.has(name) + || PRIVATE_EXTENSIONS.some(suffix => name.endsWith(suffix)) + || /(?:^|[._-])private-preview(?:[._-]|$)/u.test(name) + || /(?:^|[._-])atlas-checkpoint(?:[._-]|$)/u.test(name); +} + +function ownerPreviewEvidencePath(relativePath) { + const normalized = normalizedRelativePath(relativePath).toLowerCase(); + if (!normalized.startsWith('docs/evidence/greater-realm/')) return false; + return OWNER_PREVIEW_EXTENSIONS.has(extension(normalized)); +} + +function statFingerprint(status) { + return Object.freeze({ + ctimeMs: status.ctimeMs, + dev: status.dev, + gid: status.gid, + ino: status.ino, + mode: status.mode, + mtimeMs: status.mtimeMs, + size: status.size, + uid: status.uid, + }); +} + +function sameStatFingerprint(left, right) { + return Object.keys(left).every(key => left[key] === right[key]); +} + +function attestTrustedGitCandidate(candidate) { + try { + if (resolve(candidate) !== candidate) return undefined; + let current = candidate; + let executableStatus; + while (true) { + const status = lstatSync(current); + if (status.isSymbolicLink()) return undefined; + if (current === candidate) { + if (!status.isFile()) return undefined; + executableStatus = status; + if (process.platform !== 'win32' && ( + status.uid !== 0 + || (status.mode & 0o022) !== 0 + || (status.mode & 0o111) === 0 + || (status.mode & 0o6000) !== 0 + )) return undefined; + } else if (!status.isDirectory()) { + return undefined; + } else if (process.platform !== 'win32' && ( + status.uid !== 0 + || (status.mode & 0o022) !== 0 + )) { + return undefined; + } + const parent = dirname(current); + if (parent === current) break; + current = parent; + } + const canonicalCandidate = realpathSync.native(candidate); + if ( + executableStatus === undefined + || (process.platform === 'win32' + ? canonicalCandidate.toLowerCase() !== candidate.toLowerCase() + : canonicalCandidate !== candidate) + ) { + return undefined; + } + return Object.freeze({ + path: candidate, + fingerprint: statFingerprint(executableStatus), + }); + } catch { + return undefined; + } +} + +function resolveTrustedGitBinary() { + // Never fall back to PATH. These are OS/package-manager protected locations; + // unsupported layouts fail closed with GIT_UNTRUSTED instead of executing an + // arbitrary developer-controlled binary. Windows intentionally permits only + // the standard Git for Windows installation on the system drive. + const candidates = TRUSTED_GIT_CANDIDATES[process.platform]; + if (candidates === undefined) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_UNTRUSTED'); + for (const candidate of candidates) { + const attestation = attestTrustedGitCandidate(candidate); + if (attestation !== undefined) return attestation; + } + fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_UNTRUSTED'); +} + +function hardenedGitEnvironment(attestation, repositoryRoot) { + const windows = process.platform === 'win32'; + let canonicalRepositoryRoot; + try { + canonicalRepositoryRoot = realpathSync.native(repositoryRoot); + } catch { + fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + } + const environment = { + GIT_ATTR_NOSYSTEM: '1', + GIT_CEILING_DIRECTORIES: canonicalRepositoryRoot, + GIT_CONFIG_GLOBAL: windows ? 'NUL' : '/dev/null', + GIT_CONFIG_NOSYSTEM: '1', + GIT_DISCOVERY_ACROSS_FILESYSTEM: '0', + GIT_NO_REPLACE_OBJECTS: '1', + GIT_OPTIONAL_LOCKS: '0', + GIT_TERMINAL_PROMPT: '0', + LANG: 'C', + LC_ALL: 'C', + PATH: windows + ? [dirname(attestation.path), 'C:\\Windows\\System32'].join(delimiter) + : '/usr/bin:/bin', + }; + if (windows) { + environment.SystemRoot = 'C:\\Windows'; + environment.WINDIR = 'C:\\Windows'; + } + return environment; +} + +function hardenedGitArguments(repositoryRoot, commandArguments) { + const nullPath = process.platform === 'win32' ? 'NUL' : '/dev/null'; + return [ + '--no-pager', + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + `--work-tree=${repositoryRoot}`, + '-c', 'core.bare=false', + '-c', 'core.fsmonitor=false', + '-c', `core.hooksPath=${nullPath}`, + '-c', 'core.untrackedCache=false', + '-c', 'core.useReplaceRefs=false', + '-c', 'maintenance.auto=false', + '-c', 'gc.auto=0', + '-c', 'protocol.allow=never', + ...commandArguments, + ]; +} + +function invokeTrustedGit( + attestation, + repositoryRoot, + commandArguments, + maxBuffer, + input, +) { + const result = spawnSync( + attestation.path, + hardenedGitArguments(repositoryRoot, commandArguments), + { + cwd: repositoryRoot, + encoding: 'buffer', + env: hardenedGitEnvironment(attestation, repositoryRoot), + killSignal: 'SIGKILL', + maxBuffer, + shell: false, + timeout: TRUSTED_GIT_TIMEOUT_MS, + windowsHide: true, + ...(input === undefined ? {} : { input }), + }, + ); + const after = attestTrustedGitCandidate(attestation.path); + if ( + after === undefined + || !sameStatFingerprint(attestation.fingerprint, after.fingerprint) + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_UNTRUSTED'); + if ( + result.error + || result.status !== 0 + || result.signal !== null + || !Buffer.isBuffer(result.stdout) + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + return result.stdout; +} + +function enumerateTrackedIndex(repositoryRoot) { + const attestation = resolveTrustedGitBinary(); + const versionBytes = invokeTrustedGit( + attestation, + repositoryRoot, + ['--version'], + 1_024, + ); + const version = decodeUtf8Text(versionBytes); + if (version === undefined || !/^git version [^\0\r\n]{1,160}\r?\n?$/u.test(version)) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_UNTRUSTED'); + } + const topLevelBytes = invokeTrustedGit( + attestation, + repositoryRoot, + ['rev-parse', '--show-toplevel'], + 8_192, + ); + const topLevelText = decodeUtf8Text(topLevelBytes); + if (topLevelText === undefined || topLevelText.includes('\0')) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + } + const topLevel = topLevelText.replace(/\r?\n$/u, ''); + try { + const actual = realpathSync.native(topLevel); + const expected = realpathSync.native(repositoryRoot); + if ((process.platform === 'win32' ? actual.toLowerCase() : actual) + !== (process.platform === 'win32' ? expected.toLowerCase() : expected)) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + } + } catch (error) { + if (error instanceof GreaterRealmPublicBoundaryError) throw error; + fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + } + const objectFormatBytes = invokeTrustedGit( + attestation, + repositoryRoot, + ['rev-parse', '--show-object-format'], + 1_024, + ); + const objectFormatText = decodeUtf8Text(objectFormatBytes); + const objectFormat = objectFormatText?.trim(); + const objectIdLength = objectFormat === 'sha1' + ? 40 + : objectFormat === 'sha256' ? 64 : 0; + if (objectIdLength === 0) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_UNTRUSTED'); + const trackedBytes = invokeTrustedGit( + attestation, + repositoryRoot, + ['ls-files', '--cached', '--stage', '--full-name', '-z', '--'], + MAXIMUM_GIT_OUTPUT_BYTES, + ); + const trackedText = decodeUtf8Text(trackedBytes, true); + if (trackedText === undefined) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + const entries = []; + const seenPaths = new Set(); + for (const record of trackedText.split('\0').filter(Boolean)) { + const tab = record.indexOf('\t'); + if (tab < 0) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + const metadata = record.slice(0, tab); + const match = /^([0-7]{6}) ([0-9a-f]+) ([0-3])$/u.exec(metadata); + if (match === null || match[2].length !== objectIdLength) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + } + const path = normalizedRelativePath(record.slice(tab + 1)); + if (seenPaths.has(path)) fail('GREATER_REALM_PUBLIC_BOUNDARY_SPECIAL_ENTRY'); + seenPaths.add(path); + if ( + !['100644', '100755'].includes(match[1]) + || match[3] !== '0' + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_SPECIAL_ENTRY'); + entries.push(Object.freeze({ + objectId: match[2], + path, + })); + } + return Object.freeze({ + attestation, + entries: Object.freeze(entries), + objectIdLength, + }); +} + +function exactOccurrenceCount(text, fragment) { + if (fragment.length === 0) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_SOURCE_ALLOWANCE_INVALID'); + } + let count = 0; + let offset = 0; + while (offset <= text.length - fragment.length) { + const found = text.indexOf(fragment, offset); + if (found < 0) break; + count += 1; + offset = found + fragment.length; + } + return count; +} + +function containsPrivateIdentifierSecretValue(text) { + for (const match of text.matchAll(PRIVATE_IDENTIFIER_STRING_VALUE)) { + const value = match[1]; + if (value === undefined) continue; + if (/^[0-9a-f]{64}$/iu.test(value)) return true; + if ( + /^[A-Za-z0-9+/_-]{43}=?$/u.test(value) + && /[A-Z]/u.test(value) + && /[a-z]/u.test(value) + && /[0-9]/u.test(value) + ) return true; + } + return false; +} + +function scrubExpectedPrivateSourceLiterals(text, relativePath) { + const allowances = TRACKED_PRIVATE_SOURCE_ALLOWANCES.get(relativePath); + if (allowances === undefined) return text; + let scrubbed = text; + for (const allowance of allowances) { + if ( + typeof allowance.fragment !== 'string' + || allowance.fragment.length === 0 + || !Number.isSafeInteger(allowance.count) + || allowance.count < 1 + || exactOccurrenceCount(text, allowance.fragment) !== allowance.count + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_SOURCE_ALLOWANCE_INVALID'); + scrubbed = scrubbed.split(allowance.fragment).join(' '.repeat(allowance.fragment.length)); + } + return scrubbed; +} + +function decodeAsciiEscape(_match, braced, fixed, byte) { + const encoded = braced ?? fixed ?? byte; + const codePoint = Number.parseInt(encoded, 16); + return Number.isSafeInteger(codePoint) && codePoint > 0 && codePoint <= 0x7f + ? String.fromCodePoint(codePoint) + : _match; +} + +function normalizePrivacyScanText(text) { + let normalized = text.normalize('NFKC'); + for (let pass = 0; pass < 4; pass += 1) { + const decoded = normalized.replace( + /\\+u\{([0-9a-f]{1,6})\}|\\+u([0-9a-f]{4})|\\+x([0-9a-f]{2})/giu, + decodeAsciiEscape, + ); + if (decoded === normalized) break; + normalized = decoded; + } + for (let pass = 0; pass < 8; pass += 1) { + const flattened = normalized.replace(/\\*["'`]\s*\+\s*\\*["'`]/gu, ''); + if (flattened === normalized) break; + normalized = flattened; + } + return normalized; +} + +function invalidSanitizedReview() { + fail('GREATER_REALM_PUBLIC_BOUNDARY_SANITIZED_REVIEW_INVALID'); +} + +function exactSanitizedRecord(value, expectedKeys) { + if ( + value === null + || typeof value !== 'object' + || Array.isArray(value) + || Object.getPrototypeOf(value) !== Object.prototype + ) invalidSanitizedReview(); + const keys = Object.keys(value).sort(); + const expected = [...expectedKeys].sort(); + if ( + keys.length !== expected.length + || keys.some((key, index) => key !== expected[index]) + ) invalidSanitizedReview(); + return value; +} + +function sanitizedInteger(value, maximum, minimum = 0) { + if ( + !Number.isSafeInteger(value) + || value < minimum + || value > maximum + ) invalidSanitizedReview(); + return value; +} + +function sanitizedBasisPoints(numerator, denominator) { + if (denominator <= 0 || numerator < 0 || numerator > denominator) { + invalidSanitizedReview(); + } + return Math.round((numerator * 10_000) / denominator); +} + +function assertNoSanitizedPrivateMaterial(value, path = [], depth = 0) { + if (depth > 32) invalidSanitizedReview(); + if (typeof value === 'string') { + if (value.includes('\0') || SANITIZED_REVIEW_FORBIDDEN_STRING.test(value)) { + invalidSanitizedReview(); + } + return; + } + if (value === null || typeof value !== 'object') return; + for (const [key, entry] of Object.entries(value)) { + const normalizedKey = key + .replace(/([a-z0-9])([A-Z])/gu, '$1_$2') + .replaceAll('-', '_'); + const digestAllowed = path.length === 0 && key === 'reportDigest'; + if (!digestAllowed && SANITIZED_REVIEW_FORBIDDEN_KEY.test(normalizedKey)) { + invalidSanitizedReview(); + } + assertNoSanitizedPrivateMaterial(entry, [...path, key], depth + 1); + } +} + +function sanitizedTierCounts(value, activeCellCount) { + const row = exactSanitizedRecord(value, ['tierI', 'tierII', 'tierIII']); + const result = { + tierI: sanitizedInteger(row.tierI, activeCellCount), + tierII: sanitizedInteger(row.tierII, activeCellCount), + tierIII: sanitizedInteger(row.tierIII, activeCellCount), + }; + if (result.tierI + result.tierII + result.tierIII !== activeCellCount) { + invalidSanitizedReview(); + } + return result; +} + +function sanitizedRegionRange(value, tierCount, regionCount) { + const row = exactSanitizedRecord(value, ['minimum', 'maximum']); + const result = { + minimum: sanitizedInteger(row.minimum, tierCount), + maximum: sanitizedInteger(row.maximum, tierCount), + }; + if ( + result.minimum === 0 + || result.minimum > result.maximum + || result.minimum * regionCount > tierCount + || result.maximum * regionCount < tierCount + ) invalidSanitizedReview(); + return result; +} + +function sanitizedRegionRanges(value, tierCounts) { + const row = exactSanitizedRecord(value, ['tierI', 'tierII', 'tierIII']); + const result = { + tierI: sanitizedRegionRange(row.tierI, tierCounts.tierI, 6), + tierII: sanitizedRegionRange(row.tierII, tierCounts.tierII, 3), + tierIII: sanitizedRegionRange(row.tierIII, tierCounts.tierIII, 1), + }; + if ( + result.tierIII.minimum !== tierCounts.tierIII + || result.tierIII.maximum !== tierCounts.tierIII + ) invalidSanitizedReview(); + return result; +} + +function sanitizedHydrology(value) { + const row = exactSanitizedRecord(value, [ + 'majorOceanSeaBodies', + 'majorRivers', + 'minorStreams', + 'lakes', + ]); + return { + majorOceanSeaBodies: sanitizedInteger(row.majorOceanSeaBodies, 10_000), + majorRivers: sanitizedInteger(row.majorRivers, 10_000), + minorStreams: sanitizedInteger(row.minorStreams, 100_000), + lakes: sanitizedInteger(row.lakes, 100_000), + }; +} + +function sanitizedGeology(value) { + const row = exactSanitizedRecord(value, [ + 'pseudoTectonicDomains', + 'mountainSystems', + 'watersheds', + ]); + return { + pseudoTectonicDomains: sanitizedInteger(row.pseudoTectonicDomains, 1_000), + mountainSystems: sanitizedInteger(row.mountainSystems, 10_000), + watersheds: sanitizedInteger(row.watersheds, 100_000), + }; +} + +function sanitizedTopography(value, activeCellCount, landCellCount, waterCellCount) { + const row = exactSanitizedRecord(value, [ + 'signedElevationMinimum', + 'signedElevationMaximum', + 'slopeP50', + 'slopeP95', + 'ridgeCellCount', + 'plateauCellCount', + 'basinCellCount', + 'coastCellCount', + ]); + const result = { + signedElevationMinimum: sanitizedInteger( + row.signedElevationMinimum, + 1_000_000, + -1_000_000, + ), + signedElevationMaximum: sanitizedInteger( + row.signedElevationMaximum, + 1_000_000, + -1_000_000, + ), + slopeP50: sanitizedInteger(row.slopeP50, 2_000_000), + slopeP95: sanitizedInteger(row.slopeP95, 2_000_000), + ridgeCellCount: sanitizedInteger(row.ridgeCellCount, landCellCount), + plateauCellCount: sanitizedInteger(row.plateauCellCount, landCellCount), + basinCellCount: sanitizedInteger(row.basinCellCount, landCellCount), + coastCellCount: sanitizedInteger(row.coastCellCount, activeCellCount), + }; + if ( + result.signedElevationMinimum >= result.signedElevationMaximum + || (waterCellCount > 0 && result.signedElevationMinimum > 0) + || (landCellCount > 0 && result.signedElevationMaximum <= 0) + || result.slopeP50 > result.slopeP95 + || result.slopeP95 + > result.signedElevationMaximum - result.signedElevationMinimum + ) invalidSanitizedReview(); + return result; +} + +function sanitizedBiomes(value) { + const row = exactSanitizedRecord(value, [ + 'visualClassCount', + 'minimumPerRegionVisualClassCount', + 'minimumTierIVisualClassCount', + 'minimumTierIIVisualClassCount', + 'tierIIIVisualClassCount', + 'minimumTierIMajorVisualClassCount', + 'minimumTierITransitionVisualClassCount', + 'minimumTierIIMajorVisualClassCount', + 'tierIIIMajorVisualClassCount', + 'maximumTierISingleBiomeShareBasisPoints', + 'incompatibleVisualAdjacencyCount', + 'incompatibleBiomeLandformPairCount', + ]); + const result = { + visualClassCount: sanitizedInteger(row.visualClassCount, 256), + minimumPerRegionVisualClassCount: sanitizedInteger( + row.minimumPerRegionVisualClassCount, + 256, + ), + minimumTierIVisualClassCount: sanitizedInteger(row.minimumTierIVisualClassCount, 256), + minimumTierIIVisualClassCount: sanitizedInteger(row.minimumTierIIVisualClassCount, 256), + tierIIIVisualClassCount: sanitizedInteger(row.tierIIIVisualClassCount, 256), + minimumTierIMajorVisualClassCount: sanitizedInteger( + row.minimumTierIMajorVisualClassCount, + 256, + ), + minimumTierITransitionVisualClassCount: sanitizedInteger( + row.minimumTierITransitionVisualClassCount, + 256, + ), + minimumTierIIMajorVisualClassCount: sanitizedInteger( + row.minimumTierIIMajorVisualClassCount, + 256, + ), + tierIIIMajorVisualClassCount: sanitizedInteger(row.tierIIIMajorVisualClassCount, 256), + maximumTierISingleBiomeShareBasisPoints: sanitizedInteger( + row.maximumTierISingleBiomeShareBasisPoints, + 10_000, + ), + incompatibleVisualAdjacencyCount: sanitizedInteger( + row.incompatibleVisualAdjacencyCount, + 1_000_000, + ), + incompatibleBiomeLandformPairCount: sanitizedInteger( + row.incompatibleBiomeLandformPairCount, + 1_000_000, + ), + }; + if ( + result.visualClassCount === 0 + || result.minimumPerRegionVisualClassCount === 0 + || result.minimumTierIVisualClassCount === 0 + || result.minimumTierIIVisualClassCount === 0 + || result.tierIIIVisualClassCount === 0 + || result.minimumTierIMajorVisualClassCount === 0 + || result.minimumTierITransitionVisualClassCount === 0 + || result.minimumTierIIMajorVisualClassCount === 0 + || result.tierIIIMajorVisualClassCount === 0 + || result.minimumTierIVisualClassCount > result.visualClassCount + || result.minimumTierIIVisualClassCount > result.visualClassCount + || result.tierIIIVisualClassCount > result.visualClassCount + || result.minimumTierIMajorVisualClassCount > result.minimumTierIVisualClassCount + || result.minimumTierITransitionVisualClassCount > result.minimumTierIVisualClassCount + || result.minimumTierIIMajorVisualClassCount > result.minimumTierIIVisualClassCount + || result.tierIIIMajorVisualClassCount > result.tierIIIVisualClassCount + || result.minimumPerRegionVisualClassCount !== Math.min( + result.minimumTierIVisualClassCount, + result.minimumTierIIVisualClassCount, + result.tierIIIVisualClassCount, + ) + || result.maximumTierISingleBiomeShareBasisPoints === 0 + ) invalidSanitizedReview(); + return result; +} + +function sanitizedQuality(value) { + const row = exactSanitizedRecord(value, [ + 'naturalnessBasisPoints', + 'axialArtifactBasisPoints', + 'ridgeContinuityBasisPoints', + 'hydrologyCoherenceBasisPoints', + ]); + return Object.fromEntries(Object.entries(row).map(([key, entry]) => [ + key, + sanitizedInteger(entry, 10_000), + ])); +} + +function sanitizedProofs(value) { + const row = exactSanitizedRecord(value, SANITIZED_REVIEW_PROOF_KEYS); + for (const key of SANITIZED_REVIEW_PROOF_KEYS) { + if (typeof row[key] !== 'boolean') invalidSanitizedReview(); + } + return row; +} + +function sanitizedPerformance(value) { + const row = exactSanitizedRecord(value, [ + 'generationMillisecondsRounded', + 'processPeakMemoryMiBRounded', + ]); + return { + generationMillisecondsRounded: sanitizedInteger( + row.generationMillisecondsRounded, + 7 * 24 * 60 * 60 * 1_000, + ), + processPeakMemoryMiBRounded: sanitizedInteger(row.processPeakMemoryMiBRounded, 1_048_576), + }; +} + +function validateSanitizedCandidate(value) { + const row = exactSanitizedRecord(value, SANITIZED_REVIEW_CANDIDATE_KEYS); + if ( + typeof row.candidateHandle !== 'string' + || !SANITIZED_REVIEW_CANDIDATE_HANDLE.test(row.candidateHandle) + || typeof row.eligible !== 'boolean' + ) invalidSanitizedReview(); + const activeCellCount = sanitizedInteger(row.activeCellCount, 1_000_000, 1); + const landCellCount = sanitizedInteger(row.landCellCount, activeCellCount); + const waterCellCount = sanitizedInteger(row.waterCellCount, activeCellCount); + if (landCellCount + waterCellCount !== activeCellCount) invalidSanitizedReview(); + const tierCellCounts = sanitizedTierCounts(row.tierCellCounts, activeCellCount); + const tierBasisPoints = { + tierI: sanitizedBasisPoints(tierCellCounts.tierI, activeCellCount), + tierII: sanitizedBasisPoints(tierCellCounts.tierII, activeCellCount), + }; + tierBasisPoints.tierIII = 10_000 - tierBasisPoints.tierI - tierBasisPoints.tierII; + const providedTierBasisPoints = exactSanitizedRecord( + row.tierBasisPoints, + ['tierI', 'tierII', 'tierIII'], + ); + if ( + providedTierBasisPoints.tierI !== tierBasisPoints.tierI + || providedTierBasisPoints.tierII !== tierBasisPoints.tierII + || providedTierBasisPoints.tierIII !== tierBasisPoints.tierIII + ) invalidSanitizedReview(); + const regionSizeRanges = sanitizedRegionRanges(row.regionSizeRanges, tierCellCounts); + const hydrology = sanitizedHydrology(row.hydrology); + const geology = sanitizedGeology(row.geology); + const topography = sanitizedTopography( + row.topography, + activeCellCount, + landCellCount, + waterCellCount, + ); + const biomes = sanitizedBiomes(row.biomes); + sanitizedQuality(row.quality); + const proofs = sanitizedProofs(row.proofs); + sanitizedPerformance(row.performance); + const gateCount = sanitizedInteger(row.gateCount, 10_000); + const castleSlotCount = sanitizedInteger(row.castleSlotCount, 100_000); + const insideApprovedRange = activeCellCount >= 100_000 && activeCellCount <= 150_000; + const landBasisPoints = sanitizedBasisPoints(landCellCount, activeCellCount); + if ( + row.insideApprovedRange !== insideApprovedRange + || row.landBasisPoints !== landBasisPoints + || row.waterBasisPoints !== 10_000 - landBasisPoints + ) invalidSanitizedReview(); + if (row.eligible && ( + !insideApprovedRange + || landBasisPoints < 6_200 + || landBasisPoints > 7_200 + || tierBasisPoints.tierI < 6_800 + || tierBasisPoints.tierI > 7_400 + || tierBasisPoints.tierII < 2_200 + || tierBasisPoints.tierII > 2_700 + || tierBasisPoints.tierIII < 300 + || tierBasisPoints.tierIII > 600 + || regionSizeRanges.tierIII.maximum >= regionSizeRanges.tierI.minimum + || regionSizeRanges.tierIII.maximum >= regionSizeRanges.tierII.minimum + || gateCount !== 18 + || castleSlotCount !== 600 + || Object.values(proofs).some(result => result !== true) + || geology.pseudoTectonicDomains < 7 + || geology.pseudoTectonicDomains > 12 + || hydrology.majorOceanSeaBodies < 4 + || hydrology.majorOceanSeaBodies > 6 + || hydrology.majorRivers < 48 + || hydrology.majorRivers > 72 + || hydrology.minorStreams < 120 + || hydrology.minorStreams > 240 + || hydrology.lakes < 48 + || hydrology.lakes > 96 + || topography.signedElevationMinimum >= 0 + || topography.signedElevationMaximum <= 0 + || topography.slopeP50 === 0 + || topography.slopeP95 <= topography.slopeP50 + || topography.ridgeCellCount === 0 + || topography.plateauCellCount === 0 + || topography.basinCellCount === 0 + || topography.coastCellCount === 0 + || biomes.visualClassCount < 8 + || biomes.minimumPerRegionVisualClassCount < 3 + || biomes.minimumTierIVisualClassCount < 6 + || biomes.minimumTierIIVisualClassCount < 5 + || biomes.tierIIIVisualClassCount < 3 + || biomes.minimumTierIMajorVisualClassCount < 4 + || biomes.minimumTierITransitionVisualClassCount < 2 + || biomes.minimumTierIIMajorVisualClassCount < 5 + || biomes.tierIIIMajorVisualClassCount < 3 + || biomes.maximumTierISingleBiomeShareBasisPoints > 5_500 + || biomes.incompatibleVisualAdjacencyCount !== 0 + || biomes.incompatibleBiomeLandformPairCount !== 0 + )) invalidSanitizedReview(); + return Object.freeze({ candidateHandle: row.candidateHandle, eligible: row.eligible }); +} + +function canonicalSanitizedValue(value, depth = 0) { + if (depth > 32) invalidSanitizedReview(); + if (Array.isArray(value)) { + return value.map(entry => canonicalSanitizedValue(entry, depth + 1)); + } + if (value !== null && typeof value === 'object') { + return Object.fromEntries(Object.entries(value) + .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)) + .map(([key, entry]) => [key, canonicalSanitizedValue(entry, depth + 1)])); + } + if (typeof value === 'number' && !Number.isSafeInteger(value)) invalidSanitizedReview(); + return value; +} + +function validateSanitizedReviewEvidence(text) { + let value; + try { + value = JSON.parse(text); + } catch { + return invalidSanitizedReview(); + } + assertNoSanitizedPrivateMaterial(value); + const row = exactSanitizedRecord(value, [ + 'schema', + 'generatorVersion', + 'sourceCommit', + 'reviewBatchHandle', + 'selectionStatus', + 'selectedCandidateHandle', + 'candidateCount', + 'candidates', + 'privacyBoundary', + 'reportDigest', + ]); + if ( + row.schema !== SANITIZED_REVIEW_SCHEMA + || row.privacyBoundary !== SANITIZED_REVIEW_PRIVACY_BOUNDARY + || typeof row.generatorVersion !== 'string' + || !SANITIZED_REVIEW_GENERATOR_VERSION.test(row.generatorVersion) + || typeof row.sourceCommit !== 'string' + || !SANITIZED_REVIEW_SOURCE_COMMIT.test(row.sourceCommit) + || typeof row.reviewBatchHandle !== 'string' + || !SANITIZED_REVIEW_BATCH_HANDLE.test(row.reviewBatchHandle) + || (row.selectionStatus !== 'pending' && row.selectionStatus !== 'selected') + || (row.selectedCandidateHandle !== null && ( + typeof row.selectedCandidateHandle !== 'string' + || !SANITIZED_REVIEW_CANDIDATE_HANDLE.test(row.selectedCandidateHandle) + )) + || typeof row.reportDigest !== 'string' + || !SANITIZED_REVIEW_SHA256.test(row.reportDigest) + || !Array.isArray(row.candidates) + || row.candidates.length < 8 + || row.candidates.length > 16 + || row.candidateCount !== row.candidates.length + ) invalidSanitizedReview(); + const candidates = row.candidates.map(validateSanitizedCandidate); + const handles = candidates.map(candidate => candidate.candidateHandle); + if ( + new Set(handles).size !== handles.length + || handles.some((handle, index) => index > 0 && handles[index - 1] >= handle) + || candidates.filter(candidate => candidate.eligible).length < 8 + || (row.selectionStatus === 'pending' && row.selectedCandidateHandle !== null) + || (row.selectionStatus === 'selected' && ( + row.selectedCandidateHandle === null + || !candidates.some(candidate => ( + candidate.candidateHandle === row.selectedCandidateHandle && candidate.eligible + )) + )) + ) invalidSanitizedReview(); + const { reportDigest, ...body } = row; + const digest = createHash('sha256') + .update(JSON.stringify(canonicalSanitizedValue(body)), 'utf8') + .digest('hex'); + if (digest !== reportDigest) invalidSanitizedReview(); + const canonicalText = `${JSON.stringify(canonicalSanitizedValue(row), null, 2)}\n`; + if (text !== canonicalText) invalidSanitizedReview(); +} + +function validateSanitizedReviewEvidenceReadme(bytes) { + if ( + bytes.length !== SANITIZED_REVIEW_EVIDENCE_README_BYTES + || createHash('sha256').update(bytes).digest('hex') + !== SANITIZED_REVIEW_EVIDENCE_README_SHA256 + ) invalidSanitizedReview(); +} + +function assertSanitizedReviewEvidencePath(relativePath) { + if (!relativePath.startsWith(SANITIZED_REVIEW_EVIDENCE_PREFIX)) return; + if ( + relativePath !== SANITIZED_REVIEW_EVIDENCE_README + && !SANITIZED_REVIEW_EVIDENCE_PATH.test(relativePath) + ) invalidSanitizedReview(); +} + +function decodeUtf8Text(bytes, allowNul = false) { + if (!allowNul && bytes.indexOf(0) !== -1) return undefined; + try { + return new TextDecoder('utf-8', { fatal: true }).decode(bytes); + } catch { + return undefined; + } +} + +function scanText(text, relativePath) { + const scrubbed = normalizePrivacyScanText( + scrubExpectedPrivateSourceLiterals(text, relativePath), + ); + if (GREATER_REALM_PRIVATE_MARKER_TEXT.some(marker => scrubbed.includes(marker))) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + } + if ( + PRIVATE_TEXT_FIELD.test(scrubbed) + || containsPrivateIdentifierSecretValue(scrubbed) + || INLINE_DATA_SOURCE_MAP.test(scrubbed) + || ( + relativePath.toLowerCase().startsWith('docs/evidence/greater-realm/') + && PRIVATE_EVIDENCE_FIELD.test(scrubbed) + ) + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_FIELD'); +} + +function scanLoadedBytes(bytes, relativePath, knownText) { + if (relativePath === SANITIZED_REVIEW_EVIDENCE_README) { + validateSanitizedReviewEvidenceReadme(bytes); + } + const text = decodeUtf8Text(bytes); + if (knownText && text === undefined) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_TEXT_ENCODING_INVALID'); + } + if (text !== undefined) { + scanText(text, relativePath); + if (SANITIZED_REVIEW_EVIDENCE_PATH.test(relativePath)) { + if (bytes.length > SANITIZED_REVIEW_MAXIMUM_BYTES) invalidSanitizedReview(); + validateSanitizedReviewEvidence(text); + } + } else if (containsGreaterRealmPrivateMarker(bytes)) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + } +} + +function scanGitBlob(bytes, relativePath) { + assertSanitizedReviewEvidencePath(relativePath); + const knownText = knownTextPath(relativePath); + if (knownText && bytes.length > MAXIMUM_SCANNED_TEXT_BYTES) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_TEXT_LIMIT'); + } + if (!knownText && bytes.length > MAXIMUM_SCANNED_BINARY_BYTES) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_BINARY_LIMIT'); + } + if (knownText || bytes.length <= MAXIMUM_SCANNED_TEXT_BYTES) { + scanLoadedBytes(bytes, relativePath, knownText); + } else if (containsGreaterRealmPrivateMarker(bytes)) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + } +} + +function scanFile(path, relativePath) { + assertSanitizedReviewEvidencePath(relativePath); + const status = lstatSync(path); + if (!status.isFile() || status.isSymbolicLink()) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_SPECIAL_ENTRY'); + } + const knownText = knownTextPath(path); + if (!Number.isSafeInteger(status.size) || status.size < 0) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_FILE_LIMIT'); + } + if (knownText && status.size > MAXIMUM_SCANNED_TEXT_BYTES) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_TEXT_LIMIT'); + } + if (!knownText && status.size > MAXIMUM_SCANNED_BINARY_BYTES) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_BINARY_LIMIT'); + } + const descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + try { + const opened = fstatSync(descriptor); + if ( + !opened.isFile() + || opened.dev !== status.dev + || opened.ino !== status.ino + || opened.size !== status.size + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_FILE_CHANGED'); + if (knownText || opened.size <= MAXIMUM_SCANNED_TEXT_BYTES) { + const bytes = Buffer.alloc(opened.size); + let offset = 0; + while (offset < bytes.byteLength) { + const count = readSync(descriptor, bytes, offset, bytes.byteLength - offset, null); + if (count <= 0) fail('GREATER_REALM_PUBLIC_BOUNDARY_READ_FAILED'); + offset += count; + } + scanLoadedBytes(bytes, relativePath, knownText); + } else { + let carry = Buffer.alloc(0); + let remaining = opened.size; + while (remaining > 0) { + const chunk = Buffer.alloc(Math.min(BINARY_SCAN_CHUNK_BYTES, remaining)); + let offset = 0; + while (offset < chunk.length) { + const count = readSync(descriptor, chunk, offset, chunk.length - offset, null); + if (count <= 0) fail('GREATER_REALM_PUBLIC_BOUNDARY_READ_FAILED'); + offset += count; + } + remaining -= chunk.length; + const window = carry.length === 0 ? chunk : Buffer.concat([carry, chunk]); + if (containsGreaterRealmPrivateMarker(window)) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + } + carry = Buffer.from(window.subarray(Math.max( + 0, + window.length - GREATER_REALM_PRIVATE_MARKER_OVERLAP_BYTES, + ))); + } + } + const after = fstatSync(descriptor); + const current = lstatSync(path); + if ( + after.dev !== opened.dev + || after.ino !== opened.ino + || after.size !== opened.size + || after.mtimeMs !== opened.mtimeMs + || after.ctimeMs !== opened.ctimeMs + || current.isSymbolicLink() + || !current.isFile() + || current.dev !== opened.dev + || current.ino !== opened.ino + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_FILE_CHANGED'); + } finally { + closeSync(descriptor); + } +} + +function trackedObjectPaths(entries) { + const pathsByObjectId = new Map(); + for (const entry of entries) { + const paths = pathsByObjectId.get(entry.objectId); + if (paths === undefined) pathsByObjectId.set(entry.objectId, [entry.path]); + else paths.push(entry.path); + } + return pathsByObjectId; +} + +function checkedTrackedObjects(trackedIndex, repositoryRoot, pathsByObjectId) { + const objectIds = [...pathsByObjectId.keys()]; + if (objectIds.length === 0) return []; + const input = Buffer.from(`${objectIds.join('\n')}\n`, 'ascii'); + const output = invokeTrustedGit( + trackedIndex.attestation, + repositoryRoot, + ['cat-file', '--batch-check=%(objectname) %(objecttype) %(objectsize)'], + MAXIMUM_GIT_OUTPUT_BYTES, + input, + ); + const text = decodeUtf8Text(output); + if (text === undefined) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + const lines = text.split('\n'); + if (lines.at(-1) === '') lines.pop(); + if (lines.length !== objectIds.length) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + return lines.map((line, index) => { + const match = /^([0-9a-f]+) blob (0|[1-9][0-9]*)$/u.exec(line); + const expectedObjectId = objectIds[index]; + if ( + match === null + || match[1] !== expectedObjectId + || match[1].length !== trackedIndex.objectIdLength + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + const size = Number(match[2]); + if (!Number.isSafeInteger(size) || size < 0) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_FILE_LIMIT'); + } + for (const relativePath of pathsByObjectId.get(expectedObjectId) ?? []) { + if (knownTextPath(relativePath) && size > MAXIMUM_SCANNED_TEXT_BYTES) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_TEXT_LIMIT'); + } + if (!knownTextPath(relativePath) && size > MAXIMUM_SCANNED_BINARY_BYTES) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_BINARY_LIMIT'); + } + } + return Object.freeze({ objectId: expectedObjectId, size }); + }); +} + +function scanTrackedObjectBatch( + trackedIndex, + repositoryRoot, + pathsByObjectId, + objects, +) { + const input = Buffer.from(`${objects.map(object => object.objectId).join('\n')}\n`, 'ascii'); + const expectedBytes = objects.reduce((total, object) => total + object.size, 0); + const maximumOutputBytes = expectedBytes + + objects.length * (trackedIndex.objectIdLength + 64) + + 1_024; + const output = invokeTrustedGit( + trackedIndex.attestation, + repositoryRoot, + ['cat-file', '--batch'], + maximumOutputBytes, + input, + ); + let offset = 0; + for (const object of objects) { + const newline = output.indexOf(0x0a, offset); + if (newline < 0) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + const header = decodeUtf8Text(output.subarray(offset, newline)); + const match = header === undefined + ? null + : /^([0-9a-f]+) blob (0|[1-9][0-9]*)$/u.exec(header); + if ( + match === null + || match[1] !== object.objectId + || Number(match[2]) !== object.size + ) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + const bodyStart = newline + 1; + const bodyEnd = bodyStart + object.size; + if (bodyEnd >= output.length || output[bodyEnd] !== 0x0a) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); + } + const bytes = output.subarray(bodyStart, bodyEnd); + for (const relativePath of pathsByObjectId.get(object.objectId) ?? []) { + scanGitBlob(bytes, relativePath); + } + offset = bodyEnd + 1; + } + if (offset !== output.length) fail('GREATER_REALM_PUBLIC_BOUNDARY_GIT_FAILED'); +} + +function scanTrackedIndexBlobs(trackedIndex, repositoryRoot) { + const pathsByObjectId = trackedObjectPaths(trackedIndex.entries); + const objects = checkedTrackedObjects(trackedIndex, repositoryRoot, pathsByObjectId); + let batch = []; + let batchBytes = 0; + const flush = () => { + if (batch.length === 0) return; + scanTrackedObjectBatch( + trackedIndex, + repositoryRoot, + pathsByObjectId, + batch, + ); + batch = []; + batchBytes = 0; + }; + for (const object of objects) { + if ( + batch.length > 0 + && batchBytes + object.size > MAXIMUM_GIT_BLOB_BATCH_BYTES + ) flush(); + batch.push(object); + batchBytes += object.size; + } + flush(); +} + +function scanTree(root, repositoryRoot) { + const rootStatus = lstatSync(root); + if (!rootStatus.isDirectory() || rootStatus.isSymbolicLink()) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_SPECIAL_ENTRY'); + } + let entries = 0; + const visit = directory => { + for (const entry of readdirSync(directory, { withFileTypes: true }) + .sort((left, right) => left.name.localeCompare(right.name))) { + entries += 1; + if (entries > MAXIMUM_TREE_ENTRIES) fail('GREATER_REALM_PUBLIC_BOUNDARY_TREE_LIMIT'); + const path = join(directory, entry.name); + const status = lstatSync(path); + if (status.isSymbolicLink()) fail('GREATER_REALM_PUBLIC_BOUNDARY_SPECIAL_ENTRY'); + const relativePath = relative(repositoryRoot, path).replaceAll('\\', '/'); + if (privateArtifactPath(relativePath) || ownerPreviewEvidencePath(relativePath)) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_PATH'); + } + if (status.isDirectory()) visit(path); + else if (status.isFile()) scanFile(path, relativePath); + else fail('GREATER_REALM_PUBLIC_BOUNDARY_SPECIAL_ENTRY'); + } + }; + visit(root); + return entries; +} + +export function verifyGreaterRealmPublicBoundary(input = {}) { + const repositoryRoot = resolve(input.repositoryRoot ?? resolve(import.meta.dirname, '..', '..')); + const rootStatus = statSync(repositoryRoot, { throwIfNoEntry: false }); + if (!rootStatus?.isDirectory()) fail('GREATER_REALM_PUBLIC_BOUNDARY_ROOT_INVALID'); + const trackedIndex = input.trackedPaths === undefined || input.trackedPaths === null + ? enumerateTrackedIndex(repositoryRoot) + : undefined; + const trackedPaths = input.trackedPaths + ?? trackedIndex.entries.map(entry => entry.path); + if (!Array.isArray(trackedPaths) || trackedPaths.length > MAXIMUM_TREE_ENTRIES) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_TRACKED_PATHS_INVALID'); + } + const trackedFiles = []; + for (const path of trackedPaths) { + if (privateArtifactPath(path) || ownerPreviewEvidencePath(path)) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_PATH'); + } + const trackedFile = resolve(repositoryRoot, path); + const status = lstatSync(trackedFile, { throwIfNoEntry: false }); + if (!status?.isFile() || status.isSymbolicLink()) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_SPECIAL_ENTRY'); + } + trackedFiles.push(Object.freeze({ path, trackedFile })); + } + // The index is a separate public surface from the working tree. Scan its + // exact blob bytes first, then independently scan every tracked worktree file. + if (trackedIndex !== undefined) scanTrackedIndexBlobs(trackedIndex, repositoryRoot); + for (const { path, trackedFile } of trackedFiles) { + scanFile(trackedFile, path); + } + const scanRoots = input.scanRoots ?? ['public', 'src', 'dist', 'docs']; + if (!Array.isArray(scanRoots) || scanRoots.length > 16) { + fail('GREATER_REALM_PUBLIC_BOUNDARY_SCAN_ROOTS_INVALID'); + } + let scannedEntries = 0; + for (const relativeRoot of scanRoots) { + const normalized = normalizedRelativePath(relativeRoot); + if (normalized.includes('/')) fail('GREATER_REALM_PUBLIC_BOUNDARY_SCAN_ROOTS_INVALID'); + const root = resolve(repositoryRoot, normalized); + const status = statSync(root, { throwIfNoEntry: false }); + if (status === undefined) continue; + scannedEntries += scanTree(root, repositoryRoot); + } + return Object.freeze({ + trackedPathCount: trackedPaths.length, + scannedEntryCount: scannedEntries, + }); +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const result = verifyGreaterRealmPublicBoundary(); + console.log( + `Greater Realm public boundary passed (${result.trackedPathCount} tracked paths; ${result.scannedEntryCount} scanned entries).`, + ); +} diff --git a/scripts/qa-observer/local-vite-fs-deny.mjs b/scripts/qa-observer/local-vite-fs-deny.mjs index ad1e5eaa..687dc94b 100644 --- a/scripts/qa-observer/local-vite-fs-deny.mjs +++ b/scripts/qa-observer/local-vite-fs-deny.mjs @@ -1,6 +1,28 @@ -import { lstatSync, readdirSync } from 'node:fs'; +import { Buffer } from 'node:buffer'; +import { + closeSync, + constants, + fstatSync, + lstatSync, + openSync, + readSync, + readdirSync, +} from 'node:fs'; import { join } from 'node:path'; +import { + GREATER_REALM_PRIVATE_MARKER_OVERLAP_BYTES, + containsGreaterRealmPrivateMarker, +} from '../atlas/greater-realm-private-markers.mjs'; + +const MAXIMUM_PUBLIC_TREE_ENTRIES = 250_000; +const MAXIMUM_PUBLIC_FILE_BYTES = 128 * 1024 * 1024; +const PUBLIC_SCAN_CHUNK_BYTES = 64 * 1024; +const PUBLIC_BOUNDARY_PROHIBITED = + 'Warpkeep public directory contains a prohibited local artifact.'; +const PUBLIC_BOUNDARY_ATTESTATION_FAILED = + 'Warpkeep could not attest the public directory boundary.'; + /** * Vite replaces, rather than extends, its default deny list when `server.fs.deny` * is configured. Keep one shared contract for manual and automated local QA. @@ -28,6 +50,16 @@ export const WARPKEEP_LOCAL_VITE_FS_DENY = Object.freeze([ '**/.cache/**', '**/.wrangler/**', '**/.secrets/**', + '**/.warpkeep-private/**', + '**/greater-realm-private/**', + 'seed.bin', + 'batch-seed.bin', + 'manifest.private.json', + 'batch.private.json', + 'selection.private.json', + 'shortlist.private.json', + '*private-preview*', + '*.{wkgr-atlas,wkgr-checkpoint,wkgr-private}', ]); const SENSITIVE_PUBLIC_EXACT_NAMES = new Set([ @@ -37,12 +69,20 @@ const SENSITIVE_PUBLIC_EXACT_NAMES = new Set([ 'credentials.json', 'secret.json', 'secrets.json', + 'seed.bin', + 'batch-seed.bin', + 'manifest.private.json', + 'batch.private.json', + 'selection.private.json', + 'shortlist.private.json', ]); const SENSITIVE_PUBLIC_DIRECTORIES = new Set([ '.git', '.cache', '.wrangler', '.secrets', + '.warpkeep-private', + 'greater-realm-private', ]); const SENSITIVE_PUBLIC_SUFFIXES = Object.freeze([ '.crt', @@ -71,6 +111,9 @@ const SENSITIVE_PUBLIC_SUFFIXES = Object.freeze([ '.tar.gz', '.tgz', '.7z', + '.wkgr-atlas', + '.wkgr-checkpoint', + '.wkgr-private', ]); function sensitivePublicEntryName(name) { @@ -82,9 +125,25 @@ function sensitivePublicEntryName(name) { || lower.startsWith('admin-secret') || lower.startsWith('id_rsa') || lower.startsWith('id_ed25519') + || /(?:^|[._-])private-preview(?:[._-]|$)/u.test(lower) || SENSITIVE_PUBLIC_SUFFIXES.some((suffix) => lower.endsWith(suffix)); } +function publicBoundaryProhibited() { + throw new Error(PUBLIC_BOUNDARY_PROHIBITED); +} + +function publicBoundaryAttestationFailed() { + throw new Error(PUBLIC_BOUNDARY_ATTESTATION_FAILED); +} + +function recognizedPublicBoundaryError(error) { + return error instanceof Error && ( + error.message === PUBLIC_BOUNDARY_PROHIBITED + || error.message === PUBLIC_BOUNDARY_ATTESTATION_FAILED + ); +} + function readPublicEntryStats(path, allowMissing) { try { return lstatSync(path); @@ -96,7 +155,7 @@ function readPublicEntryStats(path, allowMissing) { && 'code' in error && error.code === 'ENOENT' ) return undefined; - throw new Error('Warpkeep could not attest the public directory boundary.'); + publicBoundaryAttestationFailed(); } } @@ -104,26 +163,137 @@ function unsafePublicEntry(stats) { return stats.isSymbolicLink() || (!stats.isDirectory() && !stats.isFile()); } -function assertSafePublicTree(directory, allowMissing = true) { +function publicFileFingerprint(stats) { + return Object.freeze({ + ctimeMs: stats.ctimeMs, + dev: stats.dev, + ino: stats.ino, + mode: stats.mode, + mtimeMs: stats.mtimeMs, + size: stats.size, + }); +} + +function samePublicFileFingerprint(left, right) { + return Object.keys(left).every(key => left[key] === right[key]); +} + +function scanRegularPublicFile(path, expectedStats) { + if ( + !expectedStats.isFile() + || expectedStats.isSymbolicLink() + || !Number.isSafeInteger(expectedStats.size) + || expectedStats.size < 0 + || expectedStats.size > MAXIMUM_PUBLIC_FILE_BYTES + ) publicBoundaryProhibited(); + let descriptor; + try { + descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)); + const opened = fstatSync(descriptor); + if ( + !opened.isFile() + || opened.dev !== expectedStats.dev + || opened.ino !== expectedStats.ino + || opened.size !== expectedStats.size + ) publicBoundaryProhibited(); + + let carry = Buffer.alloc(0); + let remaining = opened.size; + try { + while (remaining > 0) { + const chunk = Buffer.alloc(Math.min(PUBLIC_SCAN_CHUNK_BYTES, remaining)); + let window; + let nextCarry = Buffer.alloc(0); + try { + let offset = 0; + while (offset < chunk.length) { + const count = readSync(descriptor, chunk, offset, chunk.length - offset, null); + if (count <= 0) publicBoundaryAttestationFailed(); + offset += count; + } + remaining -= chunk.length; + window = carry.length === 0 ? chunk : Buffer.concat([carry, chunk]); + if (containsGreaterRealmPrivateMarker(window)) publicBoundaryProhibited(); + nextCarry = Buffer.from(window.subarray(Math.max( + 0, + window.length - GREATER_REALM_PRIVATE_MARKER_OVERLAP_BYTES, + ))); + } finally { + carry.fill(0); + chunk.fill(0); + if (window !== undefined && window !== chunk) window.fill(0); + } + carry = nextCarry; + } + } finally { + carry.fill(0); + } + + const after = fstatSync(descriptor); + const current = readPublicEntryStats(path, false); + if ( + !current.isFile() + || current.isSymbolicLink() + || !samePublicFileFingerprint( + publicFileFingerprint(opened), + publicFileFingerprint(after), + ) + || !samePublicFileFingerprint( + publicFileFingerprint(after), + publicFileFingerprint(current), + ) + ) publicBoundaryProhibited(); + closeSync(descriptor); + descriptor = undefined; + return publicFileFingerprint(after); + } catch (error) { + if (recognizedPublicBoundaryError(error)) throw error; + return publicBoundaryAttestationFailed(); + } finally { + if (descriptor !== undefined) { + try { closeSync(descriptor); } catch { /* Preserve the fixed boundary diagnostic. */ } + } + } +} + +function assertSafePublicTree( + directory, + allowMissing = true, + state = { entryCount: 0, fileFingerprints: new Map() }, +) { const directoryStats = readPublicEntryStats(directory, allowMissing); - if (directoryStats === undefined) return; + if (directoryStats === undefined) return state.fileFingerprints; if (!directoryStats.isDirectory() || directoryStats.isSymbolicLink()) { - throw new Error('Warpkeep public directory contains a prohibited local artifact.'); + publicBoundaryProhibited(); } + const directoryFingerprint = publicFileFingerprint(directoryStats); let entries; try { entries = readdirSync(directory, { withFileTypes: true }); } catch { - throw new Error('Warpkeep could not attest the public directory boundary.'); + publicBoundaryAttestationFailed(); } for (const entry of entries) { + state.entryCount += 1; + if (state.entryCount > MAXIMUM_PUBLIC_TREE_ENTRIES) publicBoundaryProhibited(); const entryPath = join(directory, entry.name); const entryStats = readPublicEntryStats(entryPath, false); if (sensitivePublicEntryName(entry.name) || unsafePublicEntry(entryStats)) { - throw new Error('Warpkeep public directory contains a prohibited local artifact.'); + publicBoundaryProhibited(); } - if (entryStats.isDirectory()) assertSafePublicTree(entryPath, false); + if (entryStats.isDirectory()) assertSafePublicTree(entryPath, false, state); + else state.fileFingerprints.set(entryPath, scanRegularPublicFile(entryPath, entryStats)); } + const currentDirectoryStats = readPublicEntryStats(directory, false); + if ( + !currentDirectoryStats.isDirectory() + || currentDirectoryStats.isSymbolicLink() + || !samePublicFileFingerprint( + directoryFingerprint, + publicFileFingerprint(currentDirectoryStats), + ) + ) publicBoundaryProhibited(); + return state.fileFingerprints; } function requestPublicSegments(requestUrl, base) { @@ -144,7 +314,12 @@ function requestPublicSegments(requestUrl, base) { } } -function requestTargetsUnsafePublicEntry(publicDirectory, requestUrl, base) { +function requestTargetsUnsafePublicEntry( + publicDirectory, + requestUrl, + base, + fileFingerprints, +) { try { const segments = requestPublicSegments(requestUrl, base); if (segments === undefined || segments.some(sensitivePublicEntryName)) return true; @@ -159,6 +334,16 @@ function requestTargetsUnsafePublicEntry(publicDirectory, requestUrl, base) { if (stats === undefined) return false; if (unsafePublicEntry(stats)) return true; if (index < segments.length - 1 && !stats.isDirectory()) return false; + if (index === segments.length - 1 && stats.isFile()) { + const fingerprint = publicFileFingerprint(stats); + const attested = fileFingerprints.get(current); + if ( + attested === undefined + || !samePublicFileFingerprint(attested, fingerprint) + ) { + fileFingerprints.set(current, scanRegularPublicFile(current, stats)); + } + } } return false; } catch { @@ -174,19 +359,30 @@ function requestTargetsUnsafePublicEntry(publicDirectory, requestUrl, base) { export function warpkeepLocalPublicBoundaryPlugin() { let publicDirectory; let base = '/'; + let fileFingerprints = new Map(); return { name: 'warpkeep-local-public-boundary', enforce: 'pre', configResolved(config) { publicDirectory = config.publicDir || undefined; base = config.base; - if (publicDirectory) assertSafePublicTree(publicDirectory); + fileFingerprints = publicDirectory + ? assertSafePublicTree(publicDirectory) + : new Map(); }, configureServer(server) { server.middlewares.use((request, response, next) => { if ( request.url - && (!publicDirectory || !requestTargetsUnsafePublicEntry(publicDirectory, request.url, base)) + && ( + !publicDirectory + || !requestTargetsUnsafePublicEntry( + publicDirectory, + request.url, + base, + fileFingerprints, + ) + ) ) { next(); return; diff --git a/scripts/qa/agent.mjs b/scripts/qa/agent.mjs index 35d558cb..86ea75fc 100644 --- a/scripts/qa/agent.mjs +++ b/scripts/qa/agent.mjs @@ -119,7 +119,7 @@ const QA_AGENT_SCRIPT_CONTRACT = Object.freeze([ Object.freeze({ path: 'package.json', scripts: Object.freeze({ - build: 'tsc -b && node scripts/verify-runtime-assets.mjs && node scripts/verify-hegemony-gold-mine-candidates.mjs && node scripts/verify-hegemony-gold-mine-runtime.mjs && node scripts/verify-hegemony-wheat-farm-runtime-assets.mjs && node scripts/verify-hegemony-logging-camp-runtime-assets.mjs && node scripts/verify-hegemony-stone-quarry-runtime-assets.mjs && node scripts/verify-hegemony-supply-wagon-assets.mjs && node scripts/verify-hegemony-tree-runtime-assets.mjs && vite build && node scripts/verify-production-dist-exclusions.mjs && node scripts/verify-farcaster-miniapp.mjs', + build: 'tsc -b && node scripts/verify-runtime-assets.mjs && node scripts/verify-hegemony-gold-mine-candidates.mjs && node scripts/verify-hegemony-gold-mine-runtime.mjs && node scripts/verify-hegemony-wheat-farm-runtime-assets.mjs && node scripts/verify-hegemony-logging-camp-runtime-assets.mjs && node scripts/verify-hegemony-stone-quarry-runtime-assets.mjs && node scripts/verify-hegemony-supply-wagon-assets.mjs && node scripts/verify-hegemony-tree-runtime-assets.mjs && vite build && node scripts/verify-production-dist-exclusions.mjs && node scripts/atlas/verify-public-boundary.mjs && node scripts/verify-farcaster-miniapp.mjs', 'stdb:verify-additive-migration': 'node scripts/verify-spacetime-additive-migration.mjs', 'stdb:verify-bindings': 'node scripts/verify-spacetime-bindings.mjs', 'stdb:verify-worker-migration': 'node scripts/verify-castle-worker-additive-migration.mjs', diff --git a/tests/farcasterMiniAppContract.test.ts b/tests/farcasterMiniAppContract.test.ts index 6663265a..95eb1901 100644 --- a/tests/farcasterMiniAppContract.test.ts +++ b/tests/farcasterMiniAppContract.test.ts @@ -61,7 +61,7 @@ describe('Farcaster Mini App release contract', () => { readFileSync(resolve(process.cwd(), 'package-lock.json'), 'utf8') ) as { packages?: Record }; - expect(packageJson.engines?.node).toBe('>=22.11 <23'); + expect(packageJson.engines?.node).toBe('>=22.13 <23'); expect(packageLock.packages?.['']?.engines?.node).toBe( packageJson.engines?.node ); diff --git a/tests/greaterRealmAdvancedInvariants.test.ts b/tests/greaterRealmAdvancedInvariants.test.ts new file mode 100644 index 00000000..d745b83c --- /dev/null +++ b/tests/greaterRealmAdvancedInvariants.test.ts @@ -0,0 +1,1382 @@ +import { createHash } from 'node:crypto'; + +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +import { + clearGreaterRealmCandidateSecret, + generateGreaterRealmCandidate, + greaterRealmPrivateCanvas, + type GreaterRealmPrivateCandidate, +} from '../scripts/atlas/greater-realm-candidate-generator'; +import { + GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1, + GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1, + transformLegacyLowlandsToGlobal, +} from '../scripts/atlas/greater-realm-legacy-lowlands'; + +const PINNED_ROOT_LABEL = 'greater-realm-ordinary-parent-a'; +const PINNED_ORDINAL = 9; +const PRIVATE_CANVAS_RADIUS = 270; +const HEX_NEIGHBOR_COUNT = 6; +const REGION_COUNT = 10; +const TIER_III_REGION_INDEX = 9; +const DISTANCE_UNREACHED = 0xffff; +const EXPECTED_SEALED_GATE_COUNT = 18; +const EXPECTED_MAJOR_RIVER_DISCHARGE = 144n; + +const AXIAL_DIRECTIONS = Object.freeze([ + Object.freeze({ q: 1, r: 0 }), + Object.freeze({ q: 1, r: -1 }), + Object.freeze({ q: 0, r: -1 }), + Object.freeze({ q: -1, r: 0 }), + Object.freeze({ q: -1, r: 1 }), + Object.freeze({ q: 0, r: 1 }), +] as const); + +type ComponentAudit = Readonly<{ + componentId: Int32Array; + sizes: readonly number[]; + touchesBoundary: readonly boolean[]; +}>; + +type PassableRegionAudit = Readonly<{ + componentId: Int32Array; + componentSizes: readonly number[]; + passableCounts: readonly number[]; + largestCounts: readonly number[]; + largestSharesBasisPoints: readonly number[]; +}>; + +type RobustRegionAudit = Readonly<{ + articulation: Uint8Array; + componentId: Int32Array; + componentSizes: readonly number[]; + componentCells: readonly (readonly number[])[]; +}>; + +type BoundaryAudit = Readonly<{ + boundaryCells: readonly number[]; + maximumRadius: number; + maximumRadiusShareBasisPoints: number; + rotationalSimilarityBasisPoints: number; + maximumAlignedBoundaryRun: number; + minimumBoundaryLandDistance: number; + saltwaterBoundaryBasisPoints: number; +}>; + +let pinned: GreaterRealmPrivateCandidate | undefined; + +function pinnedRoot(): Uint8Array { + return Uint8Array.from(createHash('sha256') + .update(`${PINNED_ROOT_LABEL}\0`, 'utf8') + .digest()); +} + +function requirePinned(): GreaterRealmPrivateCandidate { + if (!pinned) throw new Error('GREATER_REALM_ADVANCED_FIXTURE_MISSING'); + return pinned; +} + +function axialDistance(q: number, r: number, otherQ = 0, otherR = 0): number { + const deltaQ = q - otherQ; + const deltaR = r - otherR; + return Math.max(Math.abs(deltaQ), Math.abs(deltaR), Math.abs(-deltaQ - deltaR)); +} + +function coordinateKey(q: number, r: number): string { + return `${q},${r}`; +} + +function auditComponents( + candidate: GreaterRealmPrivateCandidate, + included: Uint8Array, + group?: Uint8Array, +): ComponentAudit { + const componentId = new Int32Array(candidate.grid.cellCount); + componentId.fill(-1); + const sizes: number[] = []; + const touchesBoundary: boolean[] = []; + const queue = new Uint32Array(candidate.grid.cellCount); + + for (let start = 0; start < candidate.grid.cellCount; start += 1) { + if (included[start] !== 1 || componentId[start] !== -1) continue; + const id = sizes.length; + const expectedGroup = group?.[start]; + let head = 0; + let tail = 0; + let size = 0; + let reachesBoundary = false; + componentId[start] = id; + queue[tail++] = start; + while (head < tail) { + const cell = queue[head++]!; + size += 1; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0) { + reachesBoundary = true; + continue; + } + if ( + included[neighbor] !== 1 + || componentId[neighbor] !== -1 + || (group && group[neighbor] !== expectedGroup) + ) continue; + componentId[neighbor] = id; + queue[tail++] = neighbor; + } + } + sizes.push(size); + touchesBoundary.push(reachesBoundary); + } + + return Object.freeze({ + componentId, + sizes: Object.freeze(sizes), + touchesBoundary: Object.freeze(touchesBoundary), + }); +} + +function distanceFromMask( + candidate: GreaterRealmPrivateCandidate, + starts: Uint8Array, +): Uint16Array { + const distance = new Uint16Array(candidate.grid.cellCount); + distance.fill(DISTANCE_UNREACHED); + const queue = new Uint32Array(candidate.grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if (starts[cell] !== 1) continue; + distance[cell] = 0; + queue[tail++] = cell; + } + while (head < tail) { + const cell = queue[head++]!; + const nextDistance = distance[cell]! + 1; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || distance[neighbor] !== DISTANCE_UNREACHED) continue; + distance[neighbor] = nextDistance; + queue[tail++] = neighbor; + } + } + return distance; +} + +function auditPassableRegions(candidate: GreaterRealmPrivateCandidate): PassableRegionAudit { + const passable = new Uint8Array(candidate.grid.cellCount); + const passableCounts = Array(REGION_COUNT).fill(0); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if ( + ![0, 3, 4].includes(candidate.waterRegime[cell]!) + || candidate.barrier[cell] !== 0 + ) continue; + passable[cell] = 1; + passableCounts[candidate.regionId[cell]!] += 1; + } + const components = auditComponents(candidate, passable, candidate.regionId); + const largestCounts = Array(REGION_COUNT).fill(0); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const component = components.componentId[cell]!; + if (component < 0) continue; + const region = candidate.regionId[cell]!; + largestCounts[region] = Math.max(largestCounts[region]!, components.sizes[component]!); + } + const largestSharesBasisPoints = passableCounts.map((count, region) => ( + count === 0 ? 0 : Math.round((largestCounts[region]! * 10_000) / count) + )); + return Object.freeze({ + componentId: components.componentId, + componentSizes: components.sizes, + passableCounts: Object.freeze(passableCounts), + largestCounts: Object.freeze(largestCounts), + largestSharesBasisPoints: Object.freeze(largestSharesBasisPoints), + }); +} + +function auditRobustRegions(candidate: GreaterRealmPrivateCandidate): RobustRegionAudit { + const passable = new Uint8Array(candidate.grid.cellCount); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if ( + [0, 3, 4].includes(candidate.waterRegime[cell]!) + && candidate.barrier[cell] === 0 + ) passable[cell] = 1; + } + const discovery = new Int32Array(candidate.grid.cellCount); + const low = new Int32Array(candidate.grid.cellCount); + const parent = new Int32Array(candidate.grid.cellCount); + const nextNeighbor = new Uint8Array(candidate.grid.cellCount); + const childCount = new Uint8Array(candidate.grid.cellCount); + const articulation = new Uint8Array(candidate.grid.cellCount); + const edgeStack: Array = []; + const blocks: number[][] = []; + discovery.fill(-1); + parent.fill(-1); + let clock = 0; + for (let root = 0; root < candidate.grid.cellCount; root += 1) { + if (passable[root] !== 1 || discovery[root] >= 0) continue; + const stack = [root]; + discovery[root] = clock; + low[root] = clock; + clock += 1; + while (stack.length > 0) { + const cell = stack[stack.length - 1]!; + const direction = nextNeighbor[cell]!; + if (direction < HEX_NEIGHBOR_COUNT) { + nextNeighbor[cell] = direction + 1; + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || candidate.regionId[neighbor] !== candidate.regionId[cell] + || passable[neighbor] !== 1 + ) continue; + if (discovery[neighbor] < 0) { + parent[neighbor] = cell; + childCount[cell] += 1; + edgeStack.push(Object.freeze([cell, neighbor] as const)); + discovery[neighbor] = clock; + low[neighbor] = clock; + clock += 1; + stack.push(neighbor); + } else if (neighbor !== parent[cell] && discovery[neighbor]! < discovery[cell]!) { + edgeStack.push(Object.freeze([cell, neighbor] as const)); + low[cell] = Math.min(low[cell]!, discovery[neighbor]!); + } + continue; + } + stack.pop(); + const ancestor = parent[cell]!; + if (ancestor < 0) { + if (childCount[cell]! > 1) articulation[cell] = 1; + if (childCount[cell] === 0) blocks.push([cell]); + expect(edgeStack, `root ${cell} leaves no unassigned DFS edge`).toHaveLength(0); + continue; + } + low[ancestor] = Math.min(low[ancestor]!, low[cell]!); + if (low[cell]! < discovery[ancestor]!) continue; + if (parent[ancestor]! >= 0) articulation[ancestor] = 1; + const vertices = new Set(); + let foundTreeEdge = false; + while (edgeStack.length > 0) { + const [first, second] = edgeStack.pop()!; + vertices.add(first); + vertices.add(second); + if (first === ancestor && second === cell) { + foundTreeEdge = true; + break; + } + } + expect(foundTreeEdge, `tree edge ${ancestor}:${cell} closes one block`).toBe(true); + blocks.push([...vertices].sort((first, second) => first - second)); + } + } + + // Articulation vertices may occur in several Tarjan blocks. Mirror the + // authority contract independently: the largest incident block owns the + // vertex, stable discovery order breaks ties, and incomplete blocks are + // discarded instead of becoming one-pass path fragments. + const articulationOwner = new Int32Array(candidate.grid.cellCount); + articulationOwner.fill(-1); + for (let block = 0; block < blocks.length; block += 1) { + for (const cell of blocks[block]!) { + if (articulation[cell] !== 1) continue; + const current = articulationOwner[cell]!; + if ( + current < 0 + || blocks[block]!.length > blocks[current]!.length + || (blocks[block]!.length === blocks[current]!.length && block < current) + ) articulationOwner[cell] = block; + } + } + const componentId = new Int32Array(candidate.grid.cellCount); + componentId.fill(-1); + const componentSizes: number[] = []; + const componentCells: Array = []; + for (let block = 0; block < blocks.length; block += 1) { + const cells = blocks[block]!; + if (cells.some(cell => ( + articulation[cell] === 1 && articulationOwner[cell] !== block + ))) continue; + const id = componentSizes.length; + for (const cell of cells) { + expect(componentId[cell], `robust core overlap at cell ${cell}`).toBe(-1); + componentId[cell] = id; + } + componentSizes.push(cells.length); + componentCells.push(Object.freeze([...cells])); + } + return Object.freeze({ + articulation, + componentId, + componentSizes: Object.freeze(componentSizes), + componentCells: Object.freeze(componentCells), + }); +} + +function inducedCoreArticulationCounts( + neighbors: Int32Array, + audit: RobustRegionAudit, + minimumSize: number, +): Uint32Array { + const cellCount = audit.componentId.length; + const discovery = new Int32Array(cellCount); + const low = new Int32Array(cellCount); + const parent = new Int32Array(cellCount); + const nextNeighbor = new Uint8Array(cellCount); + const childCount = new Uint8Array(cellCount); + const articulation = new Uint8Array(cellCount); + discovery.fill(-1); + parent.fill(-1); + let clock = 0; + const accepted = (cell: number) => { + const component = audit.componentId[cell]!; + return component >= 0 && audit.componentSizes[component]! >= minimumSize; + }; + for (let root = 0; root < cellCount; root += 1) { + if (!accepted(root) || discovery[root] >= 0) continue; + const rootComponent = audit.componentId[root]!; + const stack = [root]; + discovery[root] = clock; + low[root] = clock; + clock += 1; + while (stack.length > 0) { + const cell = stack[stack.length - 1]!; + const direction = nextNeighbor[cell]!; + if (direction < HEX_NEIGHBOR_COUNT) { + nextNeighbor[cell] = direction + 1; + const neighbor = neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || audit.componentId[neighbor] !== rootComponent) continue; + if (discovery[neighbor] < 0) { + parent[neighbor] = cell; + childCount[cell] += 1; + discovery[neighbor] = clock; + low[neighbor] = clock; + clock += 1; + stack.push(neighbor); + } else if (neighbor !== parent[cell]) { + low[cell] = Math.min(low[cell]!, discovery[neighbor]!); + } + continue; + } + stack.pop(); + const ancestor = parent[cell]!; + if (ancestor < 0) { + if (childCount[cell]! > 1) articulation[cell] = 1; + continue; + } + low[ancestor] = Math.min(low[ancestor]!, low[cell]!); + if (parent[ancestor]! >= 0 && low[cell]! >= discovery[ancestor]!) { + articulation[ancestor] = 1; + } + } + } + const counts = new Uint32Array(audit.componentSizes.length); + for (let cell = 0; cell < cellCount; cell += 1) { + const component = audit.componentId[cell]!; + if (component >= 0 && articulation[cell] === 1) counts[component] += 1; + } + return counts; +} + +function largestComponentSize( + candidate: GreaterRealmPrivateCandidate, + included: Uint8Array, +): number { + return Math.max(0, ...auditComponents(candidate, included).sizes); +} + +function auditBoundary(candidate: GreaterRealmPrivateCandidate): BoundaryAudit { + const boundaryCells: number[] = []; + const boundaryByMissingDirection = Array.from( + { length: HEX_NEIGHBOR_COUNT }, + () => new Uint8Array(candidate.grid.cellCount), + ); + const dryLand = new Uint8Array(candidate.grid.cellCount); + const radiusCounts = new Map(); + const coordinateKeys = new Set(); + + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const q = candidate.grid.q[cell]!; + const r = candidate.grid.r[cell]!; + coordinateKeys.add(coordinateKey(q, r)); + if (candidate.waterRegime[cell] === 0) dryLand[cell] = 1; + let isBoundary = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction] !== -1) continue; + boundaryByMissingDirection[direction]![cell] = 1; + isBoundary = true; + } + if (!isBoundary) continue; + boundaryCells.push(cell); + const radius = axialDistance(q, r); + radiusCounts.set(radius, (radiusCounts.get(radius) ?? 0) + 1); + } + + let rotatedIntersection = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const rotatedQ = -candidate.grid.r[cell]!; + const rotatedR = candidate.grid.q[cell]! + candidate.grid.r[cell]!; + if (coordinateKeys.has(coordinateKey(rotatedQ, rotatedR))) rotatedIntersection += 1; + } + const distanceToLand = distanceFromMask(candidate, dryLand); + const maximumRadiusCount = Math.max(0, ...radiusCounts.values()); + const saltwaterBoundaryCells = boundaryCells.filter( + cell => candidate.waterRegime[cell] === 1, + ).length; + let maximumAlignedBoundaryRun = 0; + for (const directionBoundary of boundaryByMissingDirection) { + maximumAlignedBoundaryRun = Math.max( + maximumAlignedBoundaryRun, + largestComponentSize(candidate, directionBoundary), + ); + } + + return Object.freeze({ + boundaryCells: Object.freeze(boundaryCells), + maximumRadius: Math.max(...boundaryCells.map( + cell => axialDistance(candidate.grid.q[cell]!, candidate.grid.r[cell]!), + )), + maximumRadiusShareBasisPoints: boundaryCells.length === 0 + ? 10_000 + : Math.round((maximumRadiusCount * 10_000) / boundaryCells.length), + rotationalSimilarityBasisPoints: Math.round( + (rotatedIntersection * 10_000) + / (candidate.grid.cellCount * 2 - rotatedIntersection), + ), + maximumAlignedBoundaryRun, + minimumBoundaryLandDistance: Math.min( + ...boundaryCells.map(cell => distanceToLand[cell]!), + ), + saltwaterBoundaryBasisPoints: boundaryCells.length === 0 + ? 0 + : Math.round((saltwaterBoundaryCells * 10_000) / boundaryCells.length), + }); +} + +function enclosedInactiveCanvasCellCount(candidate: GreaterRealmPrivateCandidate): number { + const sideLength = PRIVATE_CANVAS_RADIUS * 2 + 1; + const encodedLength = sideLength * sideLength; + const active = new Uint8Array(encodedLength); + const exterior = new Uint8Array(encodedLength); + const queue = new Uint32Array(encodedLength); + const encode = (q: number, r: number) => ( + (q + PRIVATE_CANVAS_RADIUS) * sideLength + r + PRIVATE_CANVAS_RADIUS + ); + let head = 0; + let tail = 0; + + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const q = candidate.grid.q[cell]!; + const r = candidate.grid.r[cell]!; + expect(axialDistance(q, r)).toBeLessThanOrEqual(PRIVATE_CANVAS_RADIUS); + active[encode(q, r)] = 1; + } + for (let q = -PRIVATE_CANVAS_RADIUS; q <= PRIVATE_CANVAS_RADIUS; q += 1) { + const minimumR = Math.max(-PRIVATE_CANVAS_RADIUS, -q - PRIVATE_CANVAS_RADIUS); + const maximumR = Math.min(PRIVATE_CANVAS_RADIUS, -q + PRIVATE_CANVAS_RADIUS); + for (let r = minimumR; r <= maximumR; r += 1) { + if (axialDistance(q, r) !== PRIVATE_CANVAS_RADIUS) continue; + const encoded = encode(q, r); + if (active[encoded] === 1 || exterior[encoded] === 1) continue; + exterior[encoded] = 1; + queue[tail++] = encoded; + } + } + while (head < tail) { + const encoded = queue[head++]!; + const q = Math.floor(encoded / sideLength) - PRIVATE_CANVAS_RADIUS; + const r = encoded % sideLength - PRIVATE_CANVAS_RADIUS; + for (const direction of AXIAL_DIRECTIONS) { + const neighborQ = q + direction.q; + const neighborR = r + direction.r; + if (axialDistance(neighborQ, neighborR) > PRIVATE_CANVAS_RADIUS) continue; + const neighbor = encode(neighborQ, neighborR); + if (active[neighbor] === 1 || exterior[neighbor] === 1) continue; + exterior[neighbor] = 1; + queue[tail++] = neighbor; + } + } + + let enclosed = 0; + for (let q = -PRIVATE_CANVAS_RADIUS; q <= PRIVATE_CANVAS_RADIUS; q += 1) { + const minimumR = Math.max(-PRIVATE_CANVAS_RADIUS, -q - PRIVATE_CANVAS_RADIUS); + const maximumR = Math.min(PRIVATE_CANVAS_RADIUS, -q + PRIVATE_CANVAS_RADIUS); + for (let r = minimumR; r <= maximumR; r += 1) { + const encoded = encode(q, r); + if (active[encoded] !== 1 && exterior[encoded] !== 1) enclosed += 1; + } + } + return enclosed; +} + +beforeAll(() => { + const root = pinnedRoot(); + try { + pinned = generateGreaterRealmCandidate({ + rootSeed: root, + candidateOrdinal: PINNED_ORDINAL, + }); + } finally { + root.fill(0); + } +}, 60_000); + +afterAll(() => { + if (pinned) clearGreaterRealmCandidateSecret(pinned); +}); + +describe('Greater Realm advanced authority invariants', () => { + it('has no enclosed inactive holes anywhere inside the radius-270 authority canvas', () => { + expect(enclosedInactiveCanvasCellCount(requirePinned())).toBe(0); + }); + + it('independently derives a natural, saltwater, deeply buffered outer boundary', () => { + const candidate = requirePinned(); + const boundary = auditBoundary(candidate); + const naturalBoundary = boundary.boundaryCells.length > 0 + && boundary.maximumRadiusShareBasisPoints < 1_800 + && boundary.rotationalSimilarityBasisPoints < 9_300 + && boundary.maximumAlignedBoundaryRun <= 96; + const deepOceanBoundary = boundary.maximumRadius <= PRIVATE_CANVAS_RADIUS - 8 + && boundary.minimumBoundaryLandDistance >= 8 + && boundary.saltwaterBoundaryBasisPoints === 10_000; + + expect(boundary.boundaryCells.length).toBeGreaterThan(0); + expect(boundary.maximumAlignedBoundaryRun).toBeLessThanOrEqual(96); + expect(boundary.minimumBoundaryLandDistance).toBeGreaterThanOrEqual(8); + expect(boundary.saltwaterBoundaryBasisPoints).toBe(10_000); + expect(boundary.maximumRadius).toBeLessThanOrEqual(PRIVATE_CANVAS_RADIUS - 8); + expect(candidate.privateMetrics).toMatchObject({ + activeBoundaryCells: boundary.boundaryCells.length, + maximumBoundaryRadiusShareBasisPoints: boundary.maximumRadiusShareBasisPoints, + rotationalSimilarityBasisPoints: boundary.rotationalSimilarityBasisPoints, + maximumAlignedBoundaryRun: boundary.maximumAlignedBoundaryRun, + minimumBoundaryLandDistance: boundary.minimumBoundaryLandDistance, + saltwaterBoundaryBasisPoints: boundary.saltwaterBoundaryBasisPoints, + }); + expect(candidate.aggregate.proofs.naturalOuterBoundary).toBe(naturalBoundary); + expect(candidate.aggregate.proofs.deepOceanBoundary).toBe(deepOceanBoundary); + expect(naturalBoundary).toBe(true); + expect(deepOceanBoundary).toBe(true); + }); + + it('independently rejects radial tiers and requires geological highland barriers', () => { + const candidate = requirePinned(); + let tierThreeQ = 0; + let tierThreeR = 0; + let tierThreeCells = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if (candidate.tierId[cell] !== 3) continue; + tierThreeQ += candidate.grid.q[cell]!; + tierThreeR += candidate.grid.r[cell]!; + tierThreeCells += 1; + } + const roundedRatio = (numerator: number) => numerator >= 0 + ? Math.floor((numerator * 2 + tierThreeCells) / (tierThreeCells * 2)) + : -Math.floor((-numerator * 2 + tierThreeCells) / (tierThreeCells * 2)); + const centerQ = roundedRatio(tierThreeQ); + const centerR = roundedRatio(tierThreeR); + const radialTiers = new Map(); + let tierOneBoundaryEdges = 0; + let radialTierOneBoundaryEdges = 0; + const centerX = 2 * centerQ + centerR; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const radius = axialDistance(candidate.grid.q[cell]!, candidate.grid.r[cell]!, centerQ, centerR); + const tiers = radialTiers.get(radius) ?? [0, 0, 0]; + tiers[candidate.tierId[cell]! - 1] += 1; + radialTiers.set(radius, tiers); + if (candidate.tierId[cell] !== 1) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor <= cell + || candidate.tierId[neighbor] !== 1 + || candidate.regionId[neighbor] === candidate.regionId[cell] + ) continue; + tierOneBoundaryEdges += 1; + const midpointX = (2 * candidate.grid.q[cell]! + candidate.grid.r[cell]!) + + (2 * candidate.grid.q[neighbor]! + candidate.grid.r[neighbor]!) + - 2 * centerX; + const midpointR = candidate.grid.r[cell]! + candidate.grid.r[neighbor]! - 2 * centerR; + const deltaQ = candidate.grid.q[neighbor]! - candidate.grid.q[cell]!; + const deltaR = candidate.grid.r[neighbor]! - candidate.grid.r[cell]!; + const edgeX = 2 * deltaQ + deltaR; + const dot = midpointX * edgeX + 3 * midpointR * deltaR; + const radialNorm = midpointX * midpointX + 3 * midpointR * midpointR; + const edgeNorm = edgeX * edgeX + 3 * deltaR * deltaR; + if ( + BigInt(dot) * BigInt(dot) * 100n + <= BigInt(radialNorm) * BigInt(edgeNorm) * 16n + ) radialTierOneBoundaryEdges += 1; + } + } + const tierRadialAgreementBasisPoints = Math.round(( + [...radialTiers.values()].reduce((total, tiers) => total + Math.max(...tiers), 0) + * 10_000 + ) / candidate.grid.cellCount); + const radialTierOneBoundaryShareBasisPoints = Math.round( + (radialTierOneBoundaryEdges * 10_000) / tierOneBoundaryEdges, + ); + + const landCells = Array.from({ length: candidate.grid.cellCount }, (_, cell) => cell) + .filter(cell => ( + [0, 3, 4].includes(candidate.waterRegime[cell]!) + && candidate.elevation[cell]! > 0 + && candidate.legacyLowlandsProtectedCell[cell] !== 1 + )); + const elevations = landCells.map(cell => candidate.elevation[cell]!) + .sort((first, second) => first - second); + const uplifts = landCells.map(cell => candidate.tectonicUplift[cell]!) + .sort((first, second) => first - second); + const elevationThreshold = elevations[Math.floor(elevations.length * 0.58)]!; + const upliftThreshold = uplifts[Math.floor(uplifts.length * 0.58)]!; + const barrierCells = Array.from({ length: candidate.grid.cellCount }, (_, cell) => cell) + .filter(cell => candidate.barrier[cell] === 1); + const highlandCells = barrierCells.filter(cell => ( + candidate.elevation[cell]! >= elevationThreshold + || candidate.tectonicUplift[cell]! >= upliftThreshold + )); + const highlandBarrierShareBasisPoints = Math.round( + (highlandCells.length * 10_000) / barrierCells.length, + ); + const mean = (values: readonly number[]) => Math.floor( + values.reduce((total, value) => total + value, 0) / values.length, + ); + const barrierMeanElevationAdvantage = mean( + barrierCells.map(cell => candidate.elevation[cell]!), + ) - mean(elevations); + const barrierMeanUpliftAdvantage = mean( + barrierCells.map(cell => candidate.tectonicUplift[cell]!), + ) - mean(uplifts); + + expect(candidate.privateMetrics).toMatchObject({ + tierRadialAgreementBasisPoints, + radialTierOneBoundaryShareBasisPoints, + highlandBarrierShareBasisPoints, + barrierMeanElevationAdvantage, + barrierMeanUpliftAdvantage, + gateRouteRedundancyProof: true, + }); + expect(candidate.privateMetrics.measuredMinimumBarrierWidth).toBeGreaterThanOrEqual(4); + expect(candidate.privateMetrics.measuredMaximumBarrierWidth).toBeLessThanOrEqual(8); + expect(candidate.privateMetrics.measuredMaximumBarrierWidth) + .toBeGreaterThanOrEqual(candidate.privateMetrics.measuredMinimumBarrierWidth); + expect(tierRadialAgreementBasisPoints).toBeLessThanOrEqual(9_200); + expect(radialTierOneBoundaryShareBasisPoints).toBeLessThanOrEqual(4_500); + expect(highlandBarrierShareBasisPoints).toBeGreaterThanOrEqual(6_500); + expect( + barrierMeanElevationAdvantage >= 300 || barrierMeanUpliftAdvantage >= 100, + ).toBe(true); + expect(candidate.aggregate.proofs.naturalStrategicRegions).toBe(true); + expect(candidate.aggregate.proofs.geologicalHighlandBarriers).toBe(true); + }); + + it('keeps each sealed gate connected to a substantial dry approach in its own region', () => { + const candidate = requirePinned(); + const topology = auditPassableRegions(candidate); + const robust = auditRobustRegions(candidate); + const coreArticulationCounts = inducedCoreArticulationCounts( + candidate.grid.neighbors, + robust, + 64, + ); + let acceptedCoreCount = 0; + for (let component = 0; component < robust.componentSizes.length; component += 1) { + if (robust.componentSizes[component]! < 64) continue; + acceptedCoreCount += 1; + expect( + robust.componentCells[component], + `robust core ${component} has its complete vertex inventory`, + ).toHaveLength(robust.componentSizes[component]); + expect( + coreArticulationCounts[component], + `robust core ${component} is articulation-free in its induced graph`, + ).toBe(0); + } + expect(acceptedCoreCount).toBeGreaterThan(0); + + expect(candidate.gates.length).toBe(EXPECTED_SEALED_GATE_COUNT); + expect(candidate.aggregate.gateCount).toBe(candidate.gates.length); + for (const gate of candidate.gates) { + expect(Array.from({ length: HEX_NEIGHBOR_COUNT }, (_, direction) => ( + candidate.grid.neighbors[gate.firstCell * HEX_NEIGHBOR_COUNT + direction] + ))).toContain(gate.secondCell); + for (const [endpoint, region, primary, alternate] of [ + [ + gate.firstCell, + gate.firstRegion, + gate.firstApproachPath, + gate.firstAlternateApproachPath, + ], + [ + gate.secondCell, + gate.secondRegion, + gate.secondApproachPath, + gate.secondAlternateApproachPath, + ], + ] as const) { + expect(candidate.regionId[endpoint]).toBe(region); + expect(candidate.waterRegime[endpoint]).toBe(0); + expect(candidate.barrier[endpoint]).toBe(1); + const validApproaches: number[] = []; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0) continue; + const component = topology.componentId[neighbor]!; + if ( + candidate.regionId[neighbor] === region + && candidate.waterRegime[neighbor] === 0 + && candidate.barrier[neighbor] === 0 + && component >= 0 + && topology.componentSizes[component]! >= 64 + ) validApproaches.push(neighbor); + } + expect(validApproaches.length).toBeGreaterThanOrEqual(1); + expect(primary[0]).not.toBe(alternate[0]); + expect(primary.every(cell => !alternate.includes(cell))).toBe(true); + const targetComponents: number[] = []; + for (const path of [primary, alternate]) { + expect(path.length).toBeGreaterThan(0); + expect(new Set(path).size).toBe(path.length); + expect(Array.from({ length: HEX_NEIGHBOR_COUNT }, (_, direction) => ( + candidate.grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction] + ))).toContain(path[0]); + for (let index = 0; index < path.length; index += 1) { + const cell = path[index]!; + expect(candidate.regionId[cell]).toBe(region); + expect(candidate.waterRegime[cell]).toBe(0); + expect(candidate.barrier[cell]).toBe(0); + if (index > 0) { + expect(Array.from({ length: HEX_NEIGHBOR_COUNT }, (_, direction) => ( + candidate.grid.neighbors[path[index - 1]! * HEX_NEIGHBOR_COUNT + direction] + ))).toContain(cell); + } + } + const targetComponent = robust.componentId[path[path.length - 1]!]!; + expect(targetComponent).toBeGreaterThanOrEqual(0); + expect(robust.componentSizes[targetComponent]).toBeGreaterThanOrEqual(64); + targetComponents.push(targetComponent); + } + expect(new Set(targetComponents).size).toBe(1); + } + } + expect(candidate.aggregate.proofs.gateApproaches).toBe(true); + }); + + it('keeps a cycle core intact when an articulation-owned leaf is attached', () => { + const cycleSize = 100; + const leaf = cycleSize; + const cellCount = cycleSize + 1; + const neighbors = new Int32Array(cellCount * HEX_NEIGHBOR_COUNT); + neighbors.fill(-1); + const connect = (first: number, second: number) => { + const firstOffset = first * HEX_NEIGHBOR_COUNT; + const secondOffset = second * HEX_NEIGHBOR_COUNT; + const firstSlot = Array.from( + { length: HEX_NEIGHBOR_COUNT }, + (_, direction) => direction, + ).find(direction => neighbors[firstOffset + direction] === -1); + const secondSlot = Array.from( + { length: HEX_NEIGHBOR_COUNT }, + (_, direction) => direction, + ).find(direction => neighbors[secondOffset + direction] === -1); + if (firstSlot === undefined || secondSlot === undefined) { + throw new Error('GREATER_REALM_SYNTHETIC_GRAPH_DEGREE_EXCEEDED'); + } + neighbors[firstOffset + firstSlot] = second; + neighbors[secondOffset + secondSlot] = first; + }; + for (let cell = 0; cell < cycleSize; cell += 1) { + connect(cell, (cell + 1) % cycleSize); + } + connect(0, leaf); + const synthetic = { + grid: { cellCount, neighbors }, + regionId: new Uint8Array(cellCount), + waterRegime: new Uint8Array(cellCount), + barrier: new Uint8Array(cellCount), + } as unknown as GreaterRealmPrivateCandidate; + + const robust = auditRobustRegions(synthetic); + const cycleComponent = robust.componentId[0]!; + expect(robust.articulation[0]).toBe(1); + expect(cycleComponent).toBeGreaterThanOrEqual(0); + expect(robust.componentSizes[cycleComponent]).toBe(cycleSize); + for (let cell = 0; cell < cycleSize; cell += 1) { + expect(robust.componentId[cell], `cycle cell ${cell}`).toBe(cycleComponent); + } + expect(robust.componentId[leaf]).toBe(-1); + const articulationCounts = inducedCoreArticulationCounts(neighbors, robust, 64); + expect(articulationCounts[cycleComponent]).toBe(0); + }); + + it('opens exactly the 18 declared physical gate edges and no side entrances', () => { + const candidate = requirePinned(); + const endpointMate = new Map(); + const declaredEdges = new Set(); + for (const gate of candidate.gates) { + endpointMate.set(gate.firstCell, gate.secondCell); + endpointMate.set(gate.secondCell, gate.firstCell); + declaredEdges.add([ + Math.min(gate.firstCell, gate.secondCell), + Math.max(gate.firstCell, gate.secondCell), + ].join(':')); + } + const openedEdges = new Set(); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor <= cell + || candidate.tierId[cell] === candidate.tierId[neighbor] + || ![0, 3, 4].includes(candidate.waterRegime[cell]!) + || ![0, 3, 4].includes(candidate.waterRegime[neighbor]!) + || (candidate.barrier[cell] === 1 && !endpointMate.has(cell)) + || (candidate.barrier[neighbor] === 1 && !endpointMate.has(neighbor)) + ) continue; + openedEdges.add(`${cell}:${neighbor}`); + } + } + expect([...openedEdges].sort()).toEqual([...declaredEdges].sort()); + expect(openedEdges.size).toBe(EXPECTED_SEALED_GATE_COUNT); + + for (const gate of candidate.gates) { + let crossings = 0; + for (const endpoint of [gate.firstCell, gate.secondCell]) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[ + endpoint * HEX_NEIGHBOR_COUNT + direction + ]!; + if ( + neighbor >= 0 + && candidate.tierId[endpoint] !== candidate.tierId[neighbor] + && [0, 3, 4].includes(candidate.waterRegime[neighbor]!) + && (candidate.barrier[neighbor] === 0 + || neighbor === gate.firstCell + || neighbor === gate.secondCell) + ) crossings += 1; + } + } + expect(crossings / 2, `gate ${gate.gateIndex}`).toBe(1); + } + }); + + it('materializes exact distance-field mountain bands with 4–8-cell local normals', () => { + const candidate = requirePinned(); + const outerBoundary = new Uint8Array(candidate.grid.cellCount); + const innerBoundary = new Uint8Array(candidate.grid.cellCount); + const expectedPairs = new Set(candidate.gateGraph.map( + ([firstRegion, secondRegion]) => `${firstRegion}:${secondRegion}`, + )); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor <= cell || candidate.tierId[cell] === candidate.tierId[neighbor]) continue; + const boundary = Math.min(candidate.tierId[cell]!, candidate.tierId[neighbor]!) === 1 + ? outerBoundary + : innerBoundary; + boundary[cell] = 1; + boundary[neighbor] = 1; + } + } + const tierDistance = (starts: Uint8Array): Uint16Array => { + const distance = new Uint16Array(candidate.grid.cellCount); + distance.fill(DISTANCE_UNREACHED); + const queue = new Uint32Array(candidate.grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if (starts[cell] !== 1) continue; + distance[cell] = 0; + queue[tail++] = cell; + } + while (head < tail) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || candidate.tierId[neighbor] !== candidate.tierId[cell] + || distance[neighbor] !== DISTANCE_UNREACHED + ) continue; + distance[neighbor] = distance[cell]! + 1; + queue[tail++] = neighbor; + } + } + return distance; + }; + const outerDistance = tierDistance(outerBoundary); + const innerDistance = tierDistance(innerBoundary); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const expectedBand = candidate.legacyLowlandsProtectedCell[cell] === 1 + ? 0 + : (outerDistance[cell]! < 2 ? 1 : 0) | (innerDistance[cell]! < 3 ? 2 : 0); + expect(candidate.geologicalBarrierBand[cell], `band cell ${cell}`).toBe(expectedBand); + if (candidate.barrier[cell] === 1) expect(expectedBand).not.toBe(0); + } + const normalDepth = (system: 1 | 2): Uint16Array => { + const depth = new Uint16Array(candidate.grid.cellCount); + depth.fill(DISTANCE_UNREACHED); + const queue = new Uint32Array(candidate.grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if ((candidate.geologicalBarrierBand[cell]! & system) === 0) continue; + let hasTierExit = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && candidate.tierId[neighbor] === candidate.tierId[cell] + && candidate.legacyLowlandsProtectedCell[neighbor] !== 1 + && (candidate.geologicalBarrierBand[neighbor]! & system) === 0 + ) { + hasTierExit = true; + break; + } + } + if (!hasTierExit) continue; + depth[cell] = 1; + queue[tail++] = cell; + } + while (head < tail) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || candidate.tierId[neighbor] !== candidate.tierId[cell] + || depth[neighbor] !== DISTANCE_UNREACHED + || (candidate.geologicalBarrierBand[neighbor]! & system) === 0 + ) continue; + depth[neighbor] = depth[cell]! + 1; + queue[tail++] = neighbor; + } + } + return depth; + }; + const outerDepth = normalDepth(1); + const innerDepth = normalDepth(2); + const actualEdges = new Set(); + const witnessedPairs = new Set(); + let minimumWidth = Number.POSITIVE_INFINITY; + let maximumWidth = 0; + for (const crossSection of candidate.barrierCrossSections) { + const edgeKey = [ + Math.min(crossSection.firstCell, crossSection.secondCell), + Math.max(crossSection.firstCell, crossSection.secondCell), + ].join(':'); + expect(actualEdges.has(edgeKey), edgeKey).toBe(false); + actualEdges.add(edgeKey); + const witnessedPair = [ + Math.min( + candidate.regionId[crossSection.firstCell]!, + candidate.regionId[crossSection.secondCell]!, + ), + Math.max( + candidate.regionId[crossSection.firstCell]!, + candidate.regionId[crossSection.secondCell]!, + ), + ].join(':'); + if (expectedPairs.has(witnessedPair)) witnessedPairs.add(witnessedPair); + const depth = crossSection.system === 1 ? outerDepth : innerDepth; + const measuredWidth = depth[crossSection.firstCell]! + + depth[crossSection.secondCell]!; + expect(measuredWidth).toBeGreaterThanOrEqual(4); + expect(measuredWidth).toBeLessThanOrEqual(8); + expect(crossSection.cells.length).toBe(measuredWidth); + expect(crossSection.firstSideCellCount).toBe(depth[crossSection.firstCell]); + expect(crossSection.cells[crossSection.firstSideCellCount - 1]) + .toBe(crossSection.firstCell); + expect(crossSection.cells[crossSection.firstSideCellCount]) + .toBe(crossSection.secondCell); + expect(crossSection.waterAssistedCellCount).toBe(crossSection.cells.filter( + cell => ![0, 3, 4].includes(candidate.waterRegime[cell]!), + ).length); + minimumWidth = Math.min(minimumWidth, measuredWidth); + maximumWidth = Math.max(maximumWidth, measuredWidth); + for (let index = 0; index < crossSection.cells.length; index += 1) { + const cell = crossSection.cells[index]!; + const endpoint = index < crossSection.firstSideCellCount + ? crossSection.firstCell + : crossSection.secondCell; + expect(candidate.legacyLowlandsProtectedCell[cell]).toBe(0); + expect(candidate.geologicalBarrierBand[cell]! & crossSection.system) + .toBe(crossSection.system); + expect(candidate.tierId[cell]).toBe(candidate.tierId[endpoint]); + if (index === 0) continue; + const previous = crossSection.cells[index - 1]!; + expect(Array.from({ length: HEX_NEIGHBOR_COUNT }, (_, direction) => ( + candidate.grid.neighbors[previous * HEX_NEIGHBOR_COUNT + direction] + ))).toContain(cell); + } + } + expect([...witnessedPairs].sort()).toEqual([...expectedPairs].sort()); + expect(minimumWidth).toBe(candidate.privateMetrics.measuredMinimumBarrierWidth); + expect(maximumWidth).toBe(candidate.privateMetrics.measuredMaximumBarrierWidth); + }); + + it('meets per-region passable-land shares and makes Tier III the smallest passable region', () => { + const candidate = requirePinned(); + const topology = auditPassableRegions(candidate); + const thresholds = [8_000, 8_000, 8_000, 8_000, 5_500, 8_000, 8_500, 8_500, 8_500, 9_000]; + + expect(topology.passableCounts.every(count => count > 0)).toBe(true); + for (let region = 0; region < REGION_COUNT; region += 1) { + expect( + topology.largestSharesBasisPoints[region]!, + `region ${region} largest passable share`, + ) + .toBeGreaterThanOrEqual(thresholds[region]!); + } + const tierThreePassableCells = topology.passableCounts[TIER_III_REGION_INDEX]!; + expect(topology.passableCounts.slice(0, TIER_III_REGION_INDEX).every( + count => tierThreePassableCells < count, + )).toBe(true); + expect(candidate.privateMetrics.minimumLargestPassableRegionShareBasisPoints) + .toBe(Math.min(...topology.largestSharesBasisPoints)); + expect(candidate.privateMetrics.tierThreePassableLandCells).toBe(tierThreePassableCells); + expect(candidate.privateMetrics.smallestOtherRegionPassableLandCells).toBe( + Math.min(...topology.passableCounts.slice(0, TIER_III_REGION_INDEX)), + ); + expect(candidate.aggregate.proofs.regionPassableLand).toBe(true); + }); + + it('bounds minor fragments, thin boundaries, and one-cell tendrils per region', () => { + const candidate = requirePinned(); + const topology = auditPassableRegions(candidate); + const componentRegion = Array(topology.componentSizes.length).fill(-1); + const minorCells = Array(REGION_COUNT).fill(0); + const boundarySides = Array(REGION_COUNT).fill(0); + const tendrilCells = Array(REGION_COUNT).fill(0); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const component = topology.componentId[cell]!; + if (component >= 0 && componentRegion[component] === -1) { + componentRegion[component] = candidate.regionId[cell]!; + } + if (component < 0) continue; + const region = candidate.regionId[cell]!; + let sameNeighbors = 0; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && topology.componentId[neighbor]! >= 0 + && candidate.regionId[neighbor] === region + ) sameNeighbors += 1; + else boundarySides[region] += 1; + } + if (sameNeighbors <= 1) tendrilCells[region] += 1; + } + for (let component = 0; component < topology.componentSizes.length; component += 1) { + if (topology.componentSizes[component]! >= 64) continue; + minorCells[componentRegion[component]!] += topology.componentSizes[component]!; + } + const minorShares = topology.passableCounts.map((count, region) => Math.round( + (minorCells[region]! * 10_000) / count, + )); + const boundaryDensities = topology.passableCounts.map((count, region) => Math.round( + (boundarySides[region]! * 10_000) / (count * HEX_NEIGHBOR_COUNT), + )); + const tendrilShares = topology.passableCounts.map((count, region) => Math.round( + (tendrilCells[region]! * 10_000) / count, + )); + + expect(candidate.privateMetrics.minorPassableFragmentSharesBasisPoints).toEqual(minorShares); + expect(candidate.privateMetrics.passableBoundaryDensityBasisPoints) + .toEqual(boundaryDensities); + expect(candidate.privateMetrics.passableTendrilSharesBasisPoints).toEqual(tendrilShares); + expect(minorShares.every((share, region) => share <= (region === 4 ? 500 : 300))) + .toBe(true); + expect(boundaryDensities.every(share => share <= 1_000)).toBe(true); + expect(tendrilShares.every(share => share <= 150)).toBe(true); + expect(candidate.aggregate.proofs.regionLandCoherence).toBe(true); + }); + + it('recomputes every published final hydrology count and proves surface consistency', () => { + const candidate = requirePinned(); + const ocean = new Uint8Array(candidate.grid.cellCount); + const sea = new Uint8Array(candidate.grid.cellCount); + const lake = new Uint8Array(candidate.grid.cellCount); + const generatedMajorRiver = new Uint8Array(candidate.grid.cellCount); + let invalidWaterRegimes = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if (candidate.waterRegime[cell] === 1) ocean[cell] = 1; + if (candidate.waterRegime[cell] === 5) sea[cell] = 1; + if (candidate.waterRegime[cell] === 2) lake[cell] = 1; + if (candidate.waterRegime[cell]! > 5) invalidWaterRegimes += 1; + if ( + candidate.legacyLowlandsProtectedCell[cell] !== 1 + && candidate.elevation[cell]! > 0 + && candidate.flowAccumulation[cell]! >= EXPECTED_MAJOR_RIVER_DISCHARGE + ) generatedMajorRiver[cell] = 1; + } + const oceanComponents = auditComponents(candidate, ocean); + const seaComponents = auditComponents(candidate, sea); + const lakeComponents = auditComponents(candidate, lake); + const majorRiverComponents = auditComponents(candidate, generatedMajorRiver); + const landlockedOceanComponents = oceanComponents.touchesBoundary.filter( + touchesBoundary => !touchesBoundary, + ).length; + const majorOceanSeaBodies = oceanComponents.sizes.length + seaComponents.sizes.length; + const lakes = lakeComponents.sizes.filter(size => size >= 2).length; + let minorStreams = 0; + let watersheds = 0; + let generatedWaterDrainingIntoDrySurface = 0; + let dryOutlets = 0; + const derivedSurface = (cell: number): number => ( + candidate.elevation[cell]! <= 0 ? 0 : candidate.filledElevation[cell]! + ); + const auditStandingWater = (components: ComponentAudit) => { + const surfaces = components.sizes.map(() => new Set()); + const hasLegalSpillOrTerminal = components.sizes.map(() => false); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const component = components.componentId[cell]!; + if (component < 0) continue; + const surface = derivedSurface(cell); + surfaces[component]!.add(surface); + const receiver = candidate.flowReceiver[cell]!; + if ( + (receiver === -1 && surface === 0) + || ( + receiver >= 0 + && components.componentId[receiver] !== component + && derivedSurface(receiver) <= surface + ) + ) hasLegalSpillOrTerminal[component] = true; + } + return Object.freeze({ + inconsistentSurfaces: surfaces.filter(values => values.size !== 1).length, + componentsWithoutLegalSpillOrTerminal: hasLegalSpillOrTerminal.filter( + value => !value, + ).length, + }); + }; + const seaSurfaceAudit = auditStandingWater(seaComponents); + const lakeSurfaceAudit = auditStandingWater(lakeComponents); + + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const receiver = candidate.flowReceiver[cell]!; + if (receiver < 0) { + if (candidate.flowAccumulation[cell]! >= 64n) watersheds += 1; + if (candidate.waterRegime[cell] === 0) dryOutlets += 1; + } + if (candidate.legacyLowlandsProtectedCell[cell] === 1) continue; + if (candidate.waterRegime[cell] === 4) { + let hasUpstreamStream = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && candidate.waterRegime[neighbor] === 4 + && candidate.flowReceiver[neighbor] === cell + ) { + hasUpstreamStream = true; + break; + } + } + if (!hasUpstreamStream) minorStreams += 1; + } + if ( + (candidate.waterRegime[cell] === 3 || candidate.waterRegime[cell] === 4) + && receiver >= 0 + && candidate.legacyLowlandsProtectedCell[receiver] !== 1 + && candidate.waterRegime[receiver] === 0 + ) generatedWaterDrainingIntoDrySurface += 1; + } + + expect(candidate.aggregate.hydrology).toEqual({ + majorOceanSeaBodies, + majorRivers: majorRiverComponents.sizes.length, + minorStreams, + lakes, + }); + expect(candidate.aggregate.geology.watersheds).toBe(watersheds); + expect(invalidWaterRegimes).toBe(0); + expect(seaComponents.sizes.length).toBeGreaterThan(0); + expect(landlockedOceanComponents).toBe(0); + expect(generatedWaterDrainingIntoDrySurface).toBe(0); + expect(dryOutlets).toBe(0); + expect(seaSurfaceAudit.inconsistentSurfaces).toBe(0); + expect(seaSurfaceAudit.componentsWithoutLegalSpillOrTerminal).toBe(0); + expect(lakeSurfaceAudit.inconsistentSurfaces).toBe(0); + expect(lakeSurfaceAudit.componentsWithoutLegalSpillOrTerminal).toBe(0); + expect(candidate.aggregate.proofs.hydrologySurfaceConsistency).toBe(true); + }); + + it('keeps enabled legacy water nonpositive and every protected nonwater cell positive', () => { + const candidate = requirePinned(); + const coordinateIndex = new Map(); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + coordinateIndex.set(coordinateKey(candidate.grid.q[cell]!, candidate.grid.r[cell]!), cell); + } + const enabledWater = new Set(); + const enabledWaterByKey = new Map( + GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1.water.enabledCells.map( + waterCell => [waterCell.cellKey, waterCell] as const, + ), + ); + for (const legacyWaterCell of GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1.water.enabledCells) { + const global = transformLegacyLowlandsToGlobal( + legacyWaterCell, + candidate.legacyLowlandsTransform, + ); + const cell = coordinateIndex.get(coordinateKey(global.q, global.r)) ?? -1; + expect(cell).toBeGreaterThanOrEqual(0); + expect(candidate.legacyLowlandsProtectedCell[cell]).toBe(1); + expect(candidate.elevation[cell]).toBeLessThanOrEqual(0); + expect(candidate.waterRegime[cell]).toBe( + legacyWaterCell.regime === 'ocean' ? 1 : legacyWaterCell.regime === 'lake' ? 2 : 3, + ); + if (legacyWaterCell.regime === 'river') { + const downstreamWaterCell = legacyWaterCell.downstreamWaterCellKey === undefined + ? undefined + : enabledWaterByKey.get(legacyWaterCell.downstreamWaterCellKey); + const expectedReceiver = downstreamWaterCell === undefined + ? -1 + : coordinateIndex.get(coordinateKey( + transformLegacyLowlandsToGlobal( + downstreamWaterCell, + candidate.legacyLowlandsTransform, + ).q, + transformLegacyLowlandsToGlobal( + downstreamWaterCell, + candidate.legacyLowlandsTransform, + ).r, + )) ?? -1; + expect(candidate.flowReceiver[cell]).toBe(expectedReceiver); + } + enabledWater.add(cell); + } + expect(enabledWater.size).toBe(GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.waterEnabledCellCount); + + let protectedNonwaterCells = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if (candidate.legacyLowlandsProtectedCell[cell] !== 1 || enabledWater.has(cell)) continue; + protectedNonwaterCells += 1; + expect(candidate.waterRegime[cell]).toBe(0); + expect(candidate.elevation[cell]).toBeGreaterThan(0); + } + expect(protectedNonwaterCells).toBeGreaterThan(0); + expect(candidate.aggregate.proofs.legacyLowlandsPreserved).toBe(true); + }); + + it('places all 500 new castles on separated, dry, reachable, gate-buffered land', () => { + const candidate = requirePinned(); + const topology = auditPassableRegions(candidate); + const gateMask = new Uint8Array(candidate.grid.cellCount); + for (const gate of candidate.gates) { + gateMask[gate.firstCell] = 1; + gateMask[gate.secondCell] = 1; + } + const gateDistance = distanceFromMask(candidate, gateMask); + const legacyCastles: number[] = []; + const newCastles: number[] = []; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if (candidate.castleSlot[cell] !== 1) continue; + if (candidate.legacyLowlandsCastleSlot[cell] === 1) legacyCastles.push(cell); + else newCastles.push(cell); + } + const allCastles = [...legacyCastles, ...newCastles]; + + expect(legacyCastles.length).toBe(GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.castleSlotCount); + expect(newCastles.length).toBe(500); + expect(allCastles.length).toBe(600); + for (const cell of newCastles) { + const component = topology.componentId[cell]!; + expect(candidate.waterRegime[cell]).toBe(0); + expect(candidate.barrier[cell]).toBe(0); + expect(candidate.tierId[cell]).toBe(1); + expect(candidate.regionId[cell]).toBeGreaterThanOrEqual(1); + expect(candidate.regionId[cell]).toBeLessThanOrEqual(5); + expect(gateDistance[cell]).toBeGreaterThanOrEqual(3); + expect(component).toBeGreaterThanOrEqual(0); + expect(topology.componentSizes[component]!).toBeGreaterThanOrEqual(200); + const passableNeighbors = Array.from( + { length: HEX_NEIGHBOR_COUNT }, + (_, direction) => candidate.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!, + ).filter(neighbor => ( + neighbor >= 0 + && candidate.regionId[neighbor] === candidate.regionId[cell] + && candidate.waterRegime[neighbor] === 0 + && candidate.barrier[neighbor] === 0 + && topology.componentId[neighbor] === component + )); + expect(passableNeighbors.length).toBeGreaterThanOrEqual(4); + for (const other of allCastles) { + if (other === cell) continue; + expect(axialDistance( + candidate.grid.q[cell]!, + candidate.grid.r[cell]!, + candidate.grid.q[other]!, + candidate.grid.r[other]!, + )).toBeGreaterThanOrEqual(5); + } + } + expect(candidate.aggregate.proofs.castleCapacity).toBe(true); + }); + + it('keeps exactly one dormant private throne anchor inside safe Tier III terrain', () => { + const candidate = requirePinned(); + const anchors = [...candidate.throneAnchor] + .map((value, cell) => value === 1 ? cell : -1) + .filter(cell => cell >= 0); + const barrierDistance = distanceFromMask(candidate, candidate.barrier); + expect(anchors).toHaveLength(1); + const anchor = anchors[0]!; + expect(candidate.regionId[anchor]).toBe(TIER_III_REGION_INDEX); + expect(candidate.waterRegime[anchor]).toBe(0); + expect(candidate.barrier[anchor]).toBe(0); + expect(barrierDistance[anchor]).toBeGreaterThanOrEqual(4); + expect(candidate.privateMetrics.throneAnchorBarrierClearance).toBe(barrierDistance[anchor]); + expect(candidate.aggregate.proofs.dormantThroneAnchor).toBe(true); + }); + + it('conserves independently summed deposited sediment plus exported material', () => { + const candidate = requirePinned(); + let depositedFromCells = 0; + for (const depth of candidate.sedimentDepth) depositedFromCells += depth; + + expect(Number.isSafeInteger(depositedFromCells)).toBe(true); + expect(depositedFromCells).toBe(candidate.privateMetrics.depositedMaterialUnits); + expect(candidate.privateMetrics.erodedMaterialUnits).toBe( + depositedFromCells + candidate.privateMetrics.exportedSedimentUnits, + ); + expect(candidate.privateMetrics.erodedMaterialUnits).toBeGreaterThanOrEqual(0); + expect(candidate.privateMetrics.depositedMaterialUnits).toBeGreaterThanOrEqual(0); + expect(candidate.privateMetrics.exportedSedimentUnits).toBeGreaterThanOrEqual(0); + }); + + it('isolates deterministic replay from mutation of the exported canvas snapshot', () => { + const candidate = requirePinned(); + const exposed = greaterRealmPrivateCanvas(); + const originalQ = exposed.q[0]!; + const originalR = exposed.r[1]!; + const originalNeighbor = exposed.neighbors[0]!; + const root = pinnedRoot(); + let replay: GreaterRealmPrivateCandidate | undefined; + try { + exposed.q[0] = 123_456_789; + exposed.r[1] = -123_456_789; + exposed.neighbors[0] = exposed.cellCount + 17; + replay = generateGreaterRealmCandidate({ + rootSeed: root, + candidateOrdinal: PINNED_ORDINAL, + }); + expect(replay.stageDigests).toEqual(candidate.stageDigests); + expect(replay.barrierCrossSections).toEqual(candidate.barrierCrossSections); + expect(replay.aggregate).toEqual(candidate.aggregate); + expect(replay.privateMetrics).toEqual(candidate.privateMetrics); + expect(replay.grid.q).toEqual(candidate.grid.q); + expect(replay.grid.r).toEqual(candidate.grid.r); + expect(replay.grid.neighbors).toEqual(candidate.grid.neighbors); + } finally { + exposed.q[0] = originalQ; + exposed.r[1] = originalR; + exposed.neighbors[0] = originalNeighbor; + root.fill(0); + if (replay) clearGreaterRealmCandidateSecret(replay); + } + }, 120_000); +}); diff --git a/tests/greaterRealmCandidateGenerator.test.ts b/tests/greaterRealmCandidateGenerator.test.ts new file mode 100644 index 00000000..7b48865c --- /dev/null +++ b/tests/greaterRealmCandidateGenerator.test.ts @@ -0,0 +1,759 @@ +import { createHash } from 'node:crypto'; + +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; + +import { + GREATER_REALM_REGION_SPECS, + clearGreaterRealmCandidateSecret, + generateGreaterRealmCandidate, + type GreaterRealmPrivateCandidate, +} from '../scripts/atlas/greater-realm-candidate-generator'; +import { GREATER_REALM_PROOF_KEYS } from '../scripts/atlas/greater-realm-contracts'; +import { + GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1, + GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1, + inverseGlobalToLegacyLowlands, + transformLegacyLowlandsToGlobal, +} from '../scripts/atlas/greater-realm-legacy-lowlands'; +import { + digestGreaterRealmTerrainStage, + greaterRealmHexDistance, + type IntegerTerrainArray, +} from '../scripts/atlas/greater-realm-terrain'; + +const PINNED_ROOT_LABEL = 'greater-realm-ordinary-parent-a'; +const SAME_FIRST_WORD_BASELINE_ROOT_INDEX = 23_248; +const SAME_FIRST_WORD_ROOT_INDEX = 41_769; +const PINNED_ORDINAL = 9; +const SAME_FIRST_WORD_ORDINAL = 0; +const EXPECTED_ACTIVE_CELL_MINIMUM = 100_000; +const EXPECTED_ACTIVE_CELL_MAXIMUM = 150_000; +const EXPECTED_CASTLES_PER_FRONTIER_REGION = 100; + +let pinned: GreaterRealmPrivateCandidate | undefined; +let replay: GreaterRealmPrivateCandidate | undefined; +let sameFirstWordBaseline: GreaterRealmPrivateCandidate | undefined; +let sameFirstWordVariant: GreaterRealmPrivateCandidate | undefined; +let mathRandomCallCount = 0; + +function programmaticRoot(index: number): Uint8Array { + return Uint8Array.from(createHash('sha256') + .update('greater-realm-test-root\0', 'utf8') + .update(String(index), 'utf8') + .digest()); +} + +function pinnedRoot(): Uint8Array { + return Uint8Array.from(createHash('sha256') + .update(`${PINNED_ROOT_LABEL}\0`, 'utf8') + .digest()); +} + +function requireCandidates(): readonly [ + GreaterRealmPrivateCandidate, + GreaterRealmPrivateCandidate, + GreaterRealmPrivateCandidate, + GreaterRealmPrivateCandidate, +] { + if (!pinned || !replay || !sameFirstWordBaseline || !sameFirstWordVariant) { + throw new Error('GREATER_REALM_CANDIDATE_FIXTURE_MISSING'); + } + return [pinned, replay, sameFirstWordBaseline, sameFirstWordVariant]; +} + +function candidateFields( + candidate: GreaterRealmPrivateCandidate, +): Readonly> { + return Object.freeze({ + bedrockElevation: candidate.bedrockElevation, + elevation: candidate.elevation, + filledElevation: candidate.filledElevation, + sedimentDepth: candidate.sedimentDepth, + flowReceiver: candidate.flowReceiver, + flowAccumulation: candidate.flowAccumulation, + domainId: candidate.domainId, + geologyId: candidate.geologyId, + tectonicUplift: candidate.tectonicUplift, + rockResistance: candidate.rockResistance, + geomorphologyElevation: candidate.geomorphologyElevation, + geomorphologyTemperature: candidate.geomorphologyTemperature, + geomorphologyMoisture: candidate.geomorphologyMoisture, + geomorphologyTotalDelta: candidate.geomorphologyTotalDelta, + geomorphologyGlacialDelta: candidate.geomorphologyGlacialDelta, + geomorphologyAridDelta: candidate.geomorphologyAridDelta, + geomorphologyVolcanicDelta: candidate.geomorphologyVolcanicDelta, + geomorphologyCoastalDelta: candidate.geomorphologyCoastalDelta, + geomorphologyGlacialMask: candidate.geomorphologyGlacialMask, + geomorphologyAridMask: candidate.geomorphologyAridMask, + geomorphologyVolcanicMask: candidate.geomorphologyVolcanicMask, + geomorphologyVolcanicAnchorMask: candidate.geomorphologyVolcanicAnchorMask, + geomorphologyCoastalMask: candidate.geomorphologyCoastalMask, + geomorphologyCoastalClass: candidate.geomorphologyCoastalClass, + regionId: candidate.regionId, + tierId: candidate.tierId, + waterRegime: candidate.waterRegime, + biomeId: candidate.biomeId, + landformId: candidate.landformId, + slope: candidate.slope, + aspect: candidate.aspect, + profileCurvature: candidate.profileCurvature, + planCurvature: candidate.planCurvature, + wetnessIndex: candidate.wetnessIndex, + exposure: candidate.exposure, + distanceToCoast: candidate.distanceToCoast, + distanceToFreshwater: candidate.distanceToFreshwater, + watershedId: candidate.watershedId, + ridgeId: candidate.ridgeId, + temperature: candidate.temperature, + moisture: candidate.moisture, + barrier: candidate.barrier, + geologicalBarrierBand: candidate.geologicalBarrierBand, + castleSlot: candidate.castleSlot, + resourcePotential: candidate.resourcePotential, + corePotential: candidate.corePotential, + throneAnchor: candidate.throneAnchor, + legacyLowlandsCell: candidate.legacyLowlandsCell, + legacyLowlandsProtectedCell: candidate.legacyLowlandsProtectedCell, + legacyLowlandsReserveCell: candidate.legacyLowlandsReserveCell, + legacyLowlandsCastleSlot: candidate.legacyLowlandsCastleSlot, + }); +} + +function areAdjacent(candidate: GreaterRealmPrivateCandidate, first: number, second: number): boolean { + for (let direction = 0; direction < 6; direction += 1) { + if (candidate.grid.neighbors[first * 6 + direction] === second) return true; + } + return false; +} + +function regionPair(first: number, second: number): string { + return `${Math.min(first, second)}:${Math.max(first, second)}`; +} + +function strategicallyPassableWaterRegime(regime: number): boolean { + return regime === 0 || regime === 3 || regime === 4; +} + +function crossTierGraphAudit( + candidate: GreaterRealmPrivateCandidate, + expectedPairs: ReadonlySet, +): Readonly<{ + rawPairs: readonly string[]; + traversablePairs: readonly string[]; + unprotectedUnexpectedContacts: number; + unexpectedGatePairs: readonly string[]; +}> { + const rawPairs = new Set(); + const traversablePairs = new Set(); + let unprotectedUnexpectedContacts = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + for (let direction = 0; direction < 6; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * 6 + direction]!; + if (neighbor <= cell || candidate.tierId[cell] === candidate.tierId[neighbor]) continue; + const pair = regionPair(candidate.regionId[cell]!, candidate.regionId[neighbor]!); + rawPairs.add(pair); + const blockedByWater = !strategicallyPassableWaterRegime(candidate.waterRegime[cell]!) + || !strategicallyPassableWaterRegime(candidate.waterRegime[neighbor]!); + const blockedByBarrier = candidate.barrier[cell] === 1 + || candidate.barrier[neighbor] === 1; + if (!blockedByWater && !blockedByBarrier) traversablePairs.add(pair); + if ( + !expectedPairs.has(pair) + && !blockedByWater + && !blockedByBarrier + ) unprotectedUnexpectedContacts += 1; + } + } + const unexpectedGatePairs = new Set(); + for (const gate of candidate.gates) { + const pair = regionPair(gate.firstRegion, gate.secondRegion); + traversablePairs.add(pair); + if (!expectedPairs.has(pair)) unexpectedGatePairs.add(pair); + } + return Object.freeze({ + rawPairs: Object.freeze([...rawPairs].sort()), + traversablePairs: Object.freeze([...traversablePairs].sort()), + unprotectedUnexpectedContacts, + unexpectedGatePairs: Object.freeze([...unexpectedGatePairs].sort()), + }); +} + +beforeAll(() => { + const firstRoot = pinnedRoot(); + const collisionRoot = programmaticRoot(SAME_FIRST_WORD_BASELINE_ROOT_INDEX); + const secondRoot = programmaticRoot(SAME_FIRST_WORD_ROOT_INDEX); + const randomSpy = vi.spyOn(Math, 'random').mockImplementation(() => { + mathRandomCallCount += 1; + throw new Error('MATH_RANDOM_MUST_NOT_BE_USED'); + }); + try { + pinned = generateGreaterRealmCandidate({ + rootSeed: firstRoot, + candidateOrdinal: PINNED_ORDINAL, + }); + replay = generateGreaterRealmCandidate({ + rootSeed: firstRoot, + candidateOrdinal: PINNED_ORDINAL, + }); + sameFirstWordBaseline = generateGreaterRealmCandidate({ + rootSeed: collisionRoot, + candidateOrdinal: SAME_FIRST_WORD_ORDINAL, + }); + sameFirstWordVariant = generateGreaterRealmCandidate({ + rootSeed: secondRoot, + candidateOrdinal: SAME_FIRST_WORD_ORDINAL, + }); + } finally { + randomSpy.mockRestore(); + firstRoot.fill(0); + collisionRoot.fill(0); + secondRoot.fill(0); + } +}, 60_000); + +afterAll(() => { + if (pinned) clearGreaterRealmCandidateSecret(pinned); + if (replay) clearGreaterRealmCandidateSecret(replay); + if (sameFirstWordBaseline) clearGreaterRealmCandidateSecret(sameFirstWordBaseline); + if (sameFirstWordVariant) clearGreaterRealmCandidateSecret(sameFirstWordVariant); +}); + +describe('Greater Realm private candidate generator', () => { + it('replays the pinned eligible candidate without mutable random state', () => { + const [candidate, repeated] = requireCandidates(); + + expect(mathRandomCallCount).toBe(0); + expect(candidate.candidateOrdinal).toBe(PINNED_ORDINAL); + expect(candidate.aggregate.eligible).toBe(true); + expect(candidate.seedMaterial).toEqual(repeated.seedMaterial); + expect(candidate.candidateSeed).toEqual(repeated.candidateSeed); + expect(candidate.domains).toEqual(repeated.domains); + expect(candidate.gates).toEqual(repeated.gates); + expect(candidate.barrierCrossSections).toEqual(repeated.barrierCrossSections); + expect(candidate.stageDigests).toEqual(repeated.stageDigests); + expect(candidate.aggregate).toEqual(repeated.aggregate); + expect(candidate.privateMetrics).toEqual(repeated.privateMetrics); + expect(candidate.grid.q).toEqual(repeated.grid.q); + expect(candidate.grid.r).toEqual(repeated.grid.r); + }); + + it('uses all 128 candidate-seed bits instead of collapsing to the first word', () => { + const [, , candidate, variant] = requireCandidates(); + + // These programmatic roots were selected for a collision in word zero. + expect(candidate.candidateSeed[0]).toBe(variant.candidateSeed[0]); + expect(candidate.candidateSeed.slice(1)).not.toEqual(variant.candidateSeed.slice(1)); + expect(candidate.stageDigests.final).not.toBe(variant.stageDigests.final); + expect(candidate.grid.q).not.toEqual(variant.grid.q); + }); + + it('binds every returned authoritative integer field into stable stage evidence', () => { + const [candidate, repeated] = requireCandidates(); + const fields = candidateFields(candidate); + + expect(candidate.stageDigests).toEqual(repeated.stageDigests); + expect(candidate.stageDigests).toEqual({ + geology: digestGreaterRealmTerrainStage('geology', candidate.grid, { + bedrockElevation: candidate.bedrockElevation, + domainId: candidate.domainId, + geologyId: candidate.geologyId, + }), + hydrology: digestGreaterRealmTerrainStage('hydrology', candidate.grid, { + elevation: candidate.elevation, + filledElevation: candidate.filledElevation, + flowReceiver: candidate.flowReceiver, + waterRegime: candidate.waterRegime, + }), + geomorphology: digestGreaterRealmTerrainStage('geomorphology', candidate.grid, { + geomorphologyElevation: candidate.geomorphologyElevation, + geomorphologyTemperature: candidate.geomorphologyTemperature, + geomorphologyMoisture: candidate.geomorphologyMoisture, + geomorphologyTotalDelta: candidate.geomorphologyTotalDelta, + geomorphologyGlacialDelta: candidate.geomorphologyGlacialDelta, + geomorphologyAridDelta: candidate.geomorphologyAridDelta, + geomorphologyVolcanicDelta: candidate.geomorphologyVolcanicDelta, + geomorphologyCoastalDelta: candidate.geomorphologyCoastalDelta, + geomorphologyGlacialMask: candidate.geomorphologyGlacialMask, + geomorphologyAridMask: candidate.geomorphologyAridMask, + geomorphologyVolcanicMask: candidate.geomorphologyVolcanicMask, + geomorphologyVolcanicAnchorMask: candidate.geomorphologyVolcanicAnchorMask, + geomorphologyCoastalMask: candidate.geomorphologyCoastalMask, + geomorphologyCoastalClass: candidate.geomorphologyCoastalClass, + }), + topography: digestGreaterRealmTerrainStage('topography', candidate.grid, { + slope: candidate.slope, + aspect: candidate.aspect, + profileCurvature: candidate.profileCurvature, + planCurvature: candidate.planCurvature, + wetnessIndex: candidate.wetnessIndex, + exposure: candidate.exposure, + distanceToCoast: candidate.distanceToCoast, + distanceToFreshwater: candidate.distanceToFreshwater, + watershedId: candidate.watershedId, + ridgeId: candidate.ridgeId, + temperature: candidate.temperature, + moisture: candidate.moisture, + biomeId: candidate.biomeId, + landformId: candidate.landformId, + }), + strategy: digestGreaterRealmTerrainStage('strategy', candidate.grid, { + regionId: candidate.regionId, + tierId: candidate.tierId, + barrier: candidate.barrier, + castleSlot: candidate.castleSlot, + throneAnchor: candidate.throneAnchor, + }), + final: digestGreaterRealmTerrainStage('final', candidate.grid, fields), + }); + expect(Object.values(candidate.stageDigests).every(digest => /^[0-9a-f]{64}$/u.test(digest))) + .toBe(true); + }); + + it('produces one connected approved mask with coherent tiers and authoritative arrays', () => { + const [candidate] = requireCandidates(); + const { grid } = candidate; + const fields = candidateFields(candidate); + + expect(grid.cellCount).toBeGreaterThanOrEqual(EXPECTED_ACTIVE_CELL_MINIMUM); + expect(grid.cellCount).toBeLessThanOrEqual(EXPECTED_ACTIVE_CELL_MAXIMUM); + expect(candidate.aggregate.activeCellCount).toBe(grid.cellCount); + expect(Object.values(fields).every(field => field.length === grid.cellCount)).toBe(true); + expect(Object.keys(candidate.aggregate.proofs).sort()).toEqual([...GREATER_REALM_PROOF_KEYS].sort()); + expect(Object.values(candidate.aggregate.proofs).every(Boolean)).toBe(true); + + const seen = new Uint8Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + let visited = 0; + seen[0] = 1; + queue[tail++] = 0; + while (head < tail) { + const cell = queue[head++]!; + visited += 1; + for (let direction = 0; direction < 6; direction += 1) { + const neighbor = grid.neighbors[cell * 6 + direction]!; + if (neighbor < 0 || seen[neighbor] === 1) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + } + } + expect(visited).toBe(grid.cellCount); + expect(candidate.aggregate.proofs.activeMaskConnected).toBe(visited === grid.cellCount); + expect(candidate.aggregate.proofs.approvedCellRange).toBe( + grid.cellCount >= EXPECTED_ACTIVE_CELL_MINIMUM + && grid.cellCount <= EXPECTED_ACTIVE_CELL_MAXIMUM, + ); + + const tierCounts = [0, 0, 0]; + const regionCounts = Array(GREATER_REALM_REGION_SPECS.length).fill(0); + let invalidTier = 0; + let invalidRegion = 0; + let tierRegionMismatch = 0; + let landCells = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const tier = candidate.tierId[cell]!; + const region = candidate.regionId[cell]!; + if (tier < 1 || tier > 3) invalidTier += 1; + else tierCounts[tier - 1] += 1; + if (region >= GREATER_REALM_REGION_SPECS.length) invalidRegion += 1; + else { + regionCounts[region] += 1; + if (GREATER_REALM_REGION_SPECS[region]!.tier !== tier) tierRegionMismatch += 1; + } + if (candidate.elevation[cell]! > 0) landCells += 1; + } + expect({ invalidTier, invalidRegion, tierRegionMismatch }).toEqual({ + invalidTier: 0, + invalidRegion: 0, + tierRegionMismatch: 0, + }); + expect(tierCounts).toEqual([ + candidate.aggregate.tierCellCounts.tierI, + candidate.aggregate.tierCellCounts.tierII, + candidate.aggregate.tierCellCounts.tierIII, + ]); + expect(regionCounts.every(count => count > 0)).toBe(true); + expect(landCells).toBe(candidate.aggregate.landCellCount); + expect(landCells + candidate.aggregate.waterCellCount).toBe(grid.cellCount); + }); + + it('maps every locked Lowlands cell and castle through one reversible protected transform', () => { + const [candidate] = requireCandidates(); + const patch = GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1; + const mappedWorldIndexes = new Set(); + const mappedProtectedIndexes = new Set(); + const mappedEnabledWaterIndexes = new Set(); + let invalidWorldCell = 0; + let invalidLegacyWater = 0; + let invalidCastleSlot = 0; + + for (const tile of patch.world.tiles) { + const global = transformLegacyLowlandsToGlobal(tile, candidate.legacyLowlandsTransform); + const local = inverseGlobalToLegacyLowlands(global, candidate.legacyLowlandsTransform); + const index = candidate.grid.indexOf(global); + if ( + index < 0 + || local.q !== tile.q + || local.r !== tile.r + || candidate.legacyLowlandsCell[index] !== 1 + || candidate.legacyLowlandsProtectedCell[index] !== 1 + || candidate.legacyLowlandsReserveCell[index] !== 1 + ) invalidWorldCell += 1; + if (index >= 0) { + mappedWorldIndexes.add(index); + mappedProtectedIndexes.add(index); + } + } + for (const waterCell of patch.water.cells) { + const global = transformLegacyLowlandsToGlobal( + waterCell, + candidate.legacyLowlandsTransform, + ); + const index = candidate.grid.indexOf(global); + if ( + index < 0 + || candidate.legacyLowlandsProtectedCell[index] !== 1 + || candidate.legacyLowlandsReserveCell[index] !== 1 + ) invalidWorldCell += 1; + if (index >= 0) mappedProtectedIndexes.add(index); + } + for (const waterCell of patch.water.enabledCells) { + const global = transformLegacyLowlandsToGlobal( + waterCell, + candidate.legacyLowlandsTransform, + ); + const index = candidate.grid.indexOf(global); + const expectedRegime = waterCell.regime === 'ocean' + ? 1 + : waterCell.regime === 'river' ? 3 : 2; + if (index < 0 || candidate.waterRegime[index] !== expectedRegime) { + invalidLegacyWater += 1; + } + if (index >= 0) mappedEnabledWaterIndexes.add(index); + } + for (const slot of patch.castleSlots.rows) { + const global = transformLegacyLowlandsToGlobal(slot, candidate.legacyLowlandsTransform); + const index = candidate.grid.indexOf(global); + if ( + index < 0 + || candidate.legacyLowlandsCastleSlot[index] !== 1 + || candidate.castleSlot[index] !== 1 + || candidate.legacyLowlandsCell[index] !== 1 + ) invalidCastleSlot += 1; + } + + const sumMask = (mask: Uint8Array): number => { + let total = 0; + for (const value of mask) total += value === 1 ? 1 : 0; + return total; + }; + const mappedExactly = invalidWorldCell === 0 + && invalidLegacyWater === 0 + && invalidCastleSlot === 0 + && mappedWorldIndexes.size === GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.worldCellCount + && mappedProtectedIndexes.size === sumMask(candidate.legacyLowlandsProtectedCell) + && sumMask(candidate.legacyLowlandsCastleSlot) + === GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.castleSlotCount; + + let protectedWaterCount = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if ( + candidate.legacyLowlandsProtectedCell[cell] === 1 + && candidate.waterRegime[cell] !== 0 + ) protectedWaterCount += 1; + } + expect({ invalidWorldCell, invalidLegacyWater, invalidCastleSlot }).toEqual({ + invalidWorldCell: 0, + invalidLegacyWater: 0, + invalidCastleSlot: 0, + }); + expect(mappedWorldIndexes.size).toBe(GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.worldCellCount); + expect(mappedProtectedIndexes.size).toBe(sumMask(candidate.legacyLowlandsProtectedCell)); + expect(mappedEnabledWaterIndexes.size).toBe( + GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1.waterEnabledCellCount, + ); + expect(protectedWaterCount).toBe(mappedEnabledWaterIndexes.size); + expect(candidate.aggregate.proofs.legacyLowlandsPreserved).toBe(mappedExactly); + }); + + it('derives the declared traversable graph while sealing every unexpected raw contact', () => { + const [candidate] = requireCandidates(); + const expectedPairs = candidate.gateGraph + .map(([first, second]) => regionPair(first, second)) + .sort(); + const audit = crossTierGraphAudit(candidate, new Set(expectedPairs)); + const graphMatches = audit.traversablePairs.length === expectedPairs.length + && audit.traversablePairs.every((pair, index) => pair === expectedPairs[index]) + && audit.unprotectedUnexpectedContacts === 0 + && audit.unexpectedGatePairs.length === 0; + + expect(audit.rawPairs.length).toBeGreaterThanOrEqual(expectedPairs.length); + expect(audit.traversablePairs).toEqual(expectedPairs); + expect(audit.unprotectedUnexpectedContacts).toBe(0); + expect(audit.unexpectedGatePairs).toEqual([]); + expect(candidate.aggregate.proofs.regionGraph).toBe(graphMatches); + }); + + it('keeps the abstract 6-to-3-to-1 graph while semantic T1 IDs vary by candidate', () => { + const [candidate] = requireCandidates(); + const outerEdges = candidate.gateGraph.filter(([first, second]) => first < 6 && second < 9); + const innerEdges = candidate.gateGraph.filter(([first, second]) => first >= 6 && second === 9); + expect(candidate.gateGraph).toHaveLength(9); + expect(outerEdges.map(([first]) => first).sort((a, b) => a - b)).toEqual([0, 1, 2, 3, 4, 5]); + expect([6, 7, 8].map(region => ( + outerEdges.filter(([, second]) => second === region).length + ))).toEqual([2, 2, 2]); + expect(innerEdges).toEqual([[6, 9], [7, 9], [8, 9]]); + expect(candidate.tierOneSemanticPermutation[0]).toBe(0); + expect([...candidate.tierOneSemanticPermutation].sort((a, b) => a - b)) + .toEqual([0, 1, 2, 3, 4, 5]); + }); + + it('stores two correctly oriented gates per declared graph edge', () => { + const [candidate] = requireCandidates(); + const expectedPairs = candidate.gateGraph + .map(([first, second]) => regionPair(first, second)) + .sort(); + const gatePairCounts = new Map(); + const endpointCells = new Set(); + let invalidGate = 0; + + for (let index = 0; index < candidate.gates.length; index += 1) { + const gate = candidate.gates[index]!; + const pair = regionPair(gate.firstRegion, gate.secondRegion); + gatePairCounts.set(pair, (gatePairCounts.get(pair) ?? 0) + 1); + endpointCells.add(gate.firstCell); + endpointCells.add(gate.secondCell); + if ( + gate.gateIndex !== index + || candidate.regionId[gate.firstCell] !== gate.firstRegion + || candidate.regionId[gate.secondCell] !== gate.secondRegion + || !areAdjacent(candidate, gate.firstCell, gate.secondCell) + || candidate.tierId[gate.firstCell] === candidate.tierId[gate.secondCell] + || candidate.waterRegime[gate.firstCell] !== 0 + || candidate.waterRegime[gate.secondCell] !== 0 + || candidate.barrier[gate.firstCell] !== 1 + || candidate.barrier[gate.secondCell] !== 1 + ) invalidGate += 1; + } + + expect([...gatePairCounts.keys()].sort()).toEqual(expectedPairs); + expect([...gatePairCounts.values()].every(count => count === 2)).toBe(true); + expect(candidate.gates).toHaveLength(expectedPairs.length * 2); + expect(endpointCells.size).toBe(candidate.gates.length * 2); + expect(invalidGate).toBe(0); + expect(candidate.aggregate.proofs.gateGraph).toBe( + invalidGate === 0 + && gatePairCounts.size === expectedPairs.length + && [...gatePairCounts.values()].every(count => count === 2), + ); + }); + + it('attests sealed tier boundaries and the measured natural deep-ocean edge', () => { + const [candidate] = requireCandidates(); + const radiusCounts = new Map(); + const coordinateKeys = new Set(); + let boundaryCells = 0; + let maximumBoundaryRadius = 0; + let barrierBypasses = 0; + + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const q = candidate.grid.q[cell]!; + const r = candidate.grid.r[cell]!; + coordinateKeys.add(`${q},${r}`); + let boundary = false; + for (let direction = 0; direction < 6; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * 6 + direction]!; + if (neighbor < 0) { + boundary = true; + continue; + } + if ( + neighbor > cell + && candidate.tierId[cell] !== candidate.tierId[neighbor] + && strategicallyPassableWaterRegime(candidate.waterRegime[cell]!) + && strategicallyPassableWaterRegime(candidate.waterRegime[neighbor]!) + && candidate.barrier[cell] !== 1 + && candidate.barrier[neighbor] !== 1 + ) barrierBypasses += 1; + } + if (!boundary) continue; + boundaryCells += 1; + const radius = greaterRealmHexDistance({ q, r }); + maximumBoundaryRadius = Math.max(maximumBoundaryRadius, radius); + radiusCounts.set(radius, (radiusCounts.get(radius) ?? 0) + 1); + } + + let rotatedIntersection = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const rotatedQ = -candidate.grid.r[cell]!; + const rotatedR = candidate.grid.q[cell]! + candidate.grid.r[cell]!; + if (coordinateKeys.has(`${rotatedQ},${rotatedR}`)) rotatedIntersection += 1; + } + const maximumRadiusCount = Math.max(0, ...radiusCounts.values()); + const maximumRadiusShareBasisPoints = boundaryCells === 0 + ? 10_000 + : Math.round((maximumRadiusCount * 10_000) / boundaryCells); + const rotationalSimilarityBasisPoints = Math.round( + (rotatedIntersection * 10_000) + / (candidate.grid.cellCount * 2 - rotatedIntersection), + ); + const naturalOuterBoundary = boundaryCells > 0 + && maximumRadiusShareBasisPoints < 1_800 + && rotationalSimilarityBasisPoints < 9_300; + const deepOceanBoundary = maximumBoundaryRadius <= 270 - 8; + + expect(barrierBypasses).toBe(0); + expect(candidate.aggregate.proofs.barriersHaveNoBypass).toBe(barrierBypasses === 0); + expect(candidate.aggregate.proofs.naturalOuterBoundary).toBe(naturalOuterBoundary); + expect(candidate.aggregate.proofs.deepOceanBoundary).toBe(deepOceanBoundary); + expect(candidate.privateMetrics).toMatchObject({ + activeBoundaryCells: boundaryCells, + maximumBoundaryRadiusShareBasisPoints: maximumRadiusShareBasisPoints, + rotationalSimilarityBasisPoints, + }); + }); + + it('preserves 100 Lowlands slots and allocates separated slots to every new frontier', () => { + const [candidate] = requireCandidates(); + const castlesByRegion = Array.from({ length: 6 }, () => [] as number[]); + let invalidCastle = 0; + let underConnectedCastle = 0; + + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if (candidate.castleSlot[cell] !== 1) continue; + const region = candidate.regionId[cell]!; + if ( + region >= castlesByRegion.length + || candidate.tierId[cell] !== 1 + || candidate.waterRegime[cell] !== 0 + || candidate.barrier[cell] !== 0 + ) { + invalidCastle += 1; + continue; + } + castlesByRegion[region]!.push(cell); + if (candidate.legacyLowlandsCastleSlot[cell] !== 1) { + const passableNeighbors = Array.from( + { length: 6 }, + (_, direction) => candidate.grid.neighbors[cell * 6 + direction]!, + ).filter(neighbor => ( + neighbor >= 0 + && candidate.regionId[neighbor] === region + && candidate.waterRegime[neighbor] === 0 + && candidate.barrier[neighbor] === 0 + )).length; + if (passableNeighbors < 4) underConnectedCastle += 1; + } + } + + let tooClose = 0; + for (const castles of castlesByRegion.slice(1)) { + for (let first = 0; first < castles.length; first += 1) { + for (let second = first + 1; second < castles.length; second += 1) { + const firstCell = castles[first]!; + const secondCell = castles[second]!; + if (greaterRealmHexDistance( + { q: candidate.grid.q[firstCell]!, r: candidate.grid.r[firstCell]! }, + { q: candidate.grid.q[secondCell]!, r: candidate.grid.r[secondCell]! }, + ) < 5) tooClose += 1; + } + } + } + + expect(invalidCastle).toBe(0); + expect(underConnectedCastle).toBe(0); + expect(tooClose).toBe(0); + expect(castlesByRegion.map(castles => castles.length)).toEqual( + Array(6).fill(EXPECTED_CASTLES_PER_FRONTIER_REGION), + ); + expect(candidate.aggregate.castleSlotCount).toBe( + EXPECTED_CASTLES_PER_FRONTIER_REGION * castlesByRegion.length, + ); + expect(candidate.aggregate.proofs.castleCapacity).toBe( + invalidCastle === 0 + && underConnectedCastle === 0 + && tooClose === 0 + && castlesByRegion.every( + castles => castles.length === EXPECTED_CASTLES_PER_FRONTIER_REGION, + ), + ); + }); + + it('returns adjacent downhill acyclic flow whose outlets conserve all local runoff', () => { + const [candidate] = requireCandidates(); + const state = new Uint8Array(candidate.grid.cellCount); + let invalidReceiver = 0; + let uphillReceiver = 0; + let cycles = 0; + let outletAccumulation = 0n; + const incomingAccumulation = new BigUint64Array(candidate.grid.cellCount); + + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const receiver = candidate.flowReceiver[cell]!; + if (receiver < 0) { + outletAccumulation += candidate.flowAccumulation[cell]!; + continue; + } + if (receiver >= candidate.grid.cellCount || !areAdjacent(candidate, cell, receiver)) { + invalidReceiver += 1; + } else if (candidate.filledElevation[receiver]! > candidate.filledElevation[cell]!) { + uphillReceiver += 1; + } + if (receiver >= 0 && receiver < candidate.grid.cellCount) { + incomingAccumulation[receiver] = ( + incomingAccumulation[receiver]! + candidate.flowAccumulation[cell]! + ); + } + } + + for (let start = 0; start < candidate.grid.cellCount; start += 1) { + if (state[start] !== 0) continue; + let cell = start; + while (cell >= 0 && state[cell] === 0) { + state[cell] = 1; + cell = candidate.flowReceiver[cell]!; + } + if (cell >= 0 && state[cell] === 1) cycles += 1; + cell = start; + while (cell >= 0 && state[cell] === 1) { + state[cell] = 2; + cell = candidate.flowReceiver[cell]!; + } + } + + let derivedLocalContribution = 0n; + let invalidLocalContribution = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + const contribution = candidate.flowAccumulation[cell]! - incomingAccumulation[cell]!; + if (contribution !== 0n && contribution !== 1n) invalidLocalContribution += 1; + derivedLocalContribution += contribution; + } + + expect({ invalidReceiver, uphillReceiver, cycles, invalidLocalContribution }).toEqual({ + invalidReceiver: 0, + uphillReceiver: 0, + cycles: 0, + invalidLocalContribution: 0, + }); + expect(outletAccumulation).toBe(derivedLocalContribution); + expect(candidate.aggregate.proofs.hydrologyAcyclic).toBe(cycles === 0); + }); + + it('zeroizes both candidate seed material and all derived seed words', () => { + const [candidate] = requireCandidates(); + const disposable = Object.freeze({ + ...candidate, + seedMaterial: Buffer.from(candidate.seedMaterial), + candidateSeed: Uint32Array.from(candidate.candidateSeed), + }) as unknown as GreaterRealmPrivateCandidate; + expect(disposable.seedMaterial.some(byte => byte !== 0)).toBe(true); + expect([...disposable.candidateSeed].some(word => word !== 0)).toBe(true); + + clearGreaterRealmCandidateSecret(disposable); + + expect(disposable.seedMaterial.every(byte => byte === 0)).toBe(true); + expect([...disposable.candidateSeed].every(word => word === 0)).toBe(true); + }); +}); diff --git a/tests/greaterRealmCandidatePackage.test.ts b/tests/greaterRealmCandidatePackage.test.ts new file mode 100644 index 00000000..0e59171b --- /dev/null +++ b/tests/greaterRealmCandidatePackage.test.ts @@ -0,0 +1,1110 @@ +// @vitest-environment node + +import { createHash } from 'node:crypto'; +import { + chmodSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + statSync, + writeFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { isAbsolute, join } from 'node:path'; + +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; + +import { + generateGreaterRealmCandidate, + type GreaterRealmPrivateCandidate, +} from '../scripts/atlas/greater-realm-candidate-generator'; +import { + GREATER_REALM_PRIVATE_PREVIEW_MARKER, + clearGreaterRealmPrivateCandidateBuffers, + renderGreaterRealmPrivatePreview, + serializeGreaterRealmPrivateAtlas, + verifyGreaterRealmPrivateCandidatePackage, + writeGreaterRealmPrivateCandidate, + type GreaterRealmCandidatePerformance, + type GreaterRealmVerifiedPrivateShortlistMetrics, +} from '../scripts/atlas/greater-realm-candidate-package'; +import { + openGreaterRealmPrivateWorkspace, + type GreaterRealmPrivateWorkspace, +} from '../scripts/atlas/greater-realm-private-workspace'; +import { + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + encodeGreaterRealmPrivateSeed, +} from '../scripts/atlas/greater-realm-private-seed'; + +const SOURCE_COMMIT = 'a'.repeat(40); +const BATCH_HANDLE = 'GR-B-AAAAAAAAAAAAAAAA'; +const CANDIDATE_HANDLE = 'GR-A-AAAAAAAAAAAAAAAA'; +const CANDIDATE_ROOT_LABEL = 'greater-realm-ordinary-parent-a'; +const CANDIDATE_ORDINAL = 9; +const PERFORMANCE = Object.freeze({ + generationMilliseconds: 1_200, + processPeakMemoryMiB: 512, +}); +const TOOLCHAIN_LOCK_PATH = join( + import.meta.dirname, + '..', + 'scripts', + 'atlas', + 'greater-realm-toolchain-lock.json', +); +const TOOLCHAIN_LOCK_SHA256 = createHash('sha256') + .update(readFileSync(TOOLCHAIN_LOCK_PATH)) + .digest('hex'); +const priorToolchainReceipt = process.env.WKGR_TOOLCHAIN_PREFLIGHT_RECEIPT; +const priorToolchainProfile = process.env.WKGR_TOOLCHAIN_PREFLIGHT_PROFILE; + +// Full-repository runs exercise renderer and database suites in parallel. A +// package verification intentionally regenerates a 100k+ cell candidate, so +// retain strict assertions while giving those fail-closed checks enough wall +// time under shared CI load. +vi.setConfig({ testTimeout: 20_000 }); + +const temporaryRoots: string[] = []; +let repositoryRoot = ''; +let workspace: GreaterRealmPrivateWorkspace | undefined; +let candidate: GreaterRealmPrivateCandidate | undefined; +let atlasDigest = ''; +let manifestDigest = ''; +let batchSeedDigest = ''; + +type GreaterRealmV4ManifestAuthority = { + barrierCrossSections: Array<{ cells: number[] }>; + gates: Array<{ + firstApproachPath: number[]; + firstAlternateApproachPath: number[]; + secondApproachPath: number[]; + secondAlternateApproachPath: number[]; + }>; +}; + +function privateAtlasFieldNames(atlas: Buffer): readonly string[] { + let offset = 0; + const magicLength = atlas.readUInt16LE(offset); + offset += 2 + magicLength + 2; + const cellCount = atlas.readUInt32LE(offset); + offset += 4; + const fieldCount = atlas.readUInt16LE(offset); + offset += 2; + const names: string[] = []; + for (let fieldIndex = 0; fieldIndex < fieldCount; fieldIndex += 1) { + const nameLength = atlas.readUInt8(offset); + offset += 1; + names.push(atlas.subarray(offset, offset + nameLength).toString('utf8')); + offset += nameLength + 1; + const length = atlas.readUInt32LE(offset); + offset += 4; + const byteLength = Number(atlas.readBigUInt64LE(offset)); + offset += 8; + if (length !== cellCount || !Number.isSafeInteger(byteLength)) { + throw new Error('GREATER_REALM_PRIVATE_ATLAS_TEST_INVENTORY_INVALID'); + } + offset += byteLength; + } + if (offset !== atlas.length) { + throw new Error('GREATER_REALM_PRIVATE_ATLAS_TEST_INVENTORY_INVALID'); + } + return Object.freeze(names); +} + +function candidateRoot(): Uint8Array { + return Uint8Array.from(createHash('sha256') + .update(`${CANDIDATE_ROOT_LABEL}\0`, 'utf8') + .digest()); +} + +function requireFixture(): Readonly<{ + workspace: GreaterRealmPrivateWorkspace; + candidate: GreaterRealmPrivateCandidate; +}> { + if (!workspace || !candidate || !atlasDigest || !manifestDigest) { + throw new Error('GREATER_REALM_PRIVATE_PACKAGE_FIXTURE_MISSING'); + } + return { workspace, candidate }; +} + +function candidateRelativePath(path: string): string { + return `batches/${BATCH_HANDLE}/candidates/${CANDIDATE_HANDLE}/${path}`; +} + +function verifyFixture(overrides: Readonly<{ + expectedCandidateOrdinal?: number; + expectedAggregate?: GreaterRealmPrivateCandidate['aggregate']; + expectedPerformance?: GreaterRealmCandidatePerformance; + expectedAtlasDigest?: string; + expectedManifestDigest?: string; + onVerifiedPrivateShortlistMetrics?: ( + metrics: GreaterRealmVerifiedPrivateShortlistMetrics, + ) => void; +}> = {}) { + const fixture = requireFixture(); + return verifyGreaterRealmPrivateCandidatePackage({ + workspace: fixture.workspace, + batchHandle: BATCH_HANDLE, + candidateHandle: CANDIDATE_HANDLE, + expectedCandidateOrdinal: overrides.expectedCandidateOrdinal ?? CANDIDATE_ORDINAL, + sourceCommit: SOURCE_COMMIT, + expectedBatchSeedDigest: batchSeedDigest, + expectedActiveCellCount: fixture.candidate.grid.cellCount, + expectedAggregate: overrides.expectedAggregate ?? fixture.candidate.aggregate, + expectedPerformance: overrides.expectedPerformance ?? PERFORMANCE, + expectedAtlasDigest: overrides.expectedAtlasDigest ?? atlasDigest, + expectedManifestDigest: overrides.expectedManifestDigest ?? manifestDigest, + onVerifiedPrivateShortlistMetrics: overrides.onVerifiedPrivateShortlistMetrics, + }); +} + +async function replacePrivateFile( + relativePath: string, + replacement: Buffer, + operation: () => Promise, +): Promise { + const fixture = requireFixture(); + const path = join(fixture.workspace.root, relativePath); + const original = fixture.workspace.readFile(relativePath); + try { + writeFileSync(path, replacement); + chmodSync(path, 0o600); + await operation(); + } finally { + writeFileSync(path, original); + chmodSync(path, 0o600); + original.fill(0); + replacement.fill(0); + } +} + +beforeAll(async () => { + process.env.WKGR_TOOLCHAIN_PREFLIGHT_RECEIPT = `sha256:${TOOLCHAIN_LOCK_SHA256}`; + process.env.WKGR_TOOLCHAIN_PREFLIGHT_PROFILE = `${process.platform}-${process.arch}`; + const root = mkdtempSync(join(tmpdir(), 'warpkeep-greater-realm-package-')); + temporaryRoots.push(root); + repositoryRoot = join(root, 'repository'); + mkdirSync(repositoryRoot, { mode: 0o700 }); + workspace = openGreaterRealmPrivateWorkspace({ + repositoryRoot, + workspaceRoot: join(root, 'owner-private'), + }); + const rootSeed = candidateRoot(); + try { + batchSeedDigest = createHash('sha256').update(rootSeed).digest('hex'); + const batchSeedEnvelope = encodeGreaterRealmPrivateSeed(rootSeed, 'batch'); + try { + workspace.writeFileAtomic( + `batches/${BATCH_HANDLE}/batch-seed.bin`, + batchSeedEnvelope, + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + } finally { + batchSeedEnvelope.fill(0); + } + candidate = generateGreaterRealmCandidate({ + rootSeed, + candidateOrdinal: CANDIDATE_ORDINAL, + }); + } finally { + rootSeed.fill(0); + } + if (!candidate.aggregate.eligible) { + throw new Error('GREATER_REALM_PRIVATE_PACKAGE_FIXTURE_INELIGIBLE'); + } + const written = await writeGreaterRealmPrivateCandidate({ + workspace, + batchHandle: BATCH_HANDLE, + candidateHandle: CANDIDATE_HANDLE, + sourceCommit: SOURCE_COMMIT, + candidate, + performance: PERFORMANCE, + }); + atlasDigest = written.atlasDigest; + manifestDigest = written.manifestDigest; +}, 30_000); + +afterAll(() => { + if (priorToolchainReceipt === undefined) delete process.env.WKGR_TOOLCHAIN_PREFLIGHT_RECEIPT; + else process.env.WKGR_TOOLCHAIN_PREFLIGHT_RECEIPT = priorToolchainReceipt; + if (priorToolchainProfile === undefined) delete process.env.WKGR_TOOLCHAIN_PREFLIGHT_PROFILE; + else process.env.WKGR_TOOLCHAIN_PREFLIGHT_PROFILE = priorToolchainProfile; + if (candidate) clearGreaterRealmPrivateCandidateBuffers(candidate); + for (const root of temporaryRoots.splice(0)) { + rmSync(root, { force: true, recursive: true }); + } +}); + +describe('Greater Realm owner-only candidate package', () => { + it('serializes canonically and verifies every expected private package component', async () => { + const fixture = requireFixture(); + const first = serializeGreaterRealmPrivateAtlas(fixture.candidate); + const second = serializeGreaterRealmPrivateAtlas(fixture.candidate); + let comparisonMetrics: GreaterRealmVerifiedPrivateShortlistMetrics | undefined; + try { + expect(first.equals(second)).toBe(true); + expect(createHash('sha256').update(first).digest('hex')).toBe(atlasDigest); + const atlasFields = privateAtlasFieldNames(first); + expect(atlasFields.filter(name => name === 'geological-barrier-band')) + .toEqual(['geological-barrier-band']); + expect(atlasFields.indexOf('geological-barrier-band')) + .toBe(atlasFields.indexOf('barrier') + 1); + for (const fieldName of [ + 'geomorphology-total-delta', + 'geomorphology-elevation', + 'geomorphology-glacial-delta', + 'geomorphology-arid-delta', + 'geomorphology-volcanic-delta', + 'geomorphology-coastal-delta', + 'geomorphology-glacial-mask', + 'geomorphology-arid-mask', + 'geomorphology-volcanic-mask', + 'geomorphology-volcanic-anchor-mask', + 'geomorphology-coastal-mask', + 'geomorphology-coastal-class', + 'geomorphology-temperature', + 'geomorphology-moisture', + 'throne-anchor', + ]) { + expect(first.includes(Buffer.from(fieldName, 'utf8'))).toBe(true); + } + await expect(verifyFixture({ + onVerifiedPrivateShortlistMetrics: metrics => { + comparisonMetrics = metrics; + }, + })).resolves.toEqual({ atlasDigest, manifestDigest }); + expect(comparisonMetrics).toMatchObject({ + candidateHandle: CANDIDATE_HANDLE, + gateRouteRedundancyProof: true, + }); + expect(comparisonMetrics!.measuredMinimumBarrierWidth).toBeGreaterThanOrEqual(4); + expect(comparisonMetrics!.measuredMaximumBarrierWidth).toBeLessThanOrEqual(8); + expect(JSON.stringify(comparisonMetrics)).not.toMatch( + /(?:coordinate|seed|transform|digest|path|chunkKey)/iu, + ); + expect(fixture.workspace.attestTree( + `batches/${BATCH_HANDLE}/candidates/${CANDIDATE_HANDLE}`, + ).fileCount).toBe(9); + expect(readdirSync(repositoryRoot)).toEqual([]); + + const preview = fixture.workspace.readFile( + candidateRelativePath('previews/private-preview-silhouette.png'), + ); + try { + expect(preview.includes(Buffer.from(GREATER_REALM_PRIVATE_PREVIEW_MARKER, 'ascii'))) + .toBe(true); + } finally { + preview.fill(0); + } + + for (const relativePath of [ + candidateRelativePath('seed.bin'), + candidateRelativePath('atlas.wkgr-atlas'), + candidateRelativePath('manifest.private.json'), + candidateRelativePath('previews/private-preview-silhouette.png'), + ]) { + expect(statSync(join(fixture.workspace.root, relativePath)).mode & 0o077).toBe(0); + } + } finally { + first.fill(0); + second.fill(0); + } + }, 30_000); + + it('binds deterministic 15-by-15 axial bins, topography patches, and toolchain pins', () => { + const fixture = requireFixture(); + const bytes = fixture.workspace.readFile(candidateRelativePath('manifest.private.json')); + try { + const parsed = JSON.parse(bytes.toString('utf8')) as { + barrierCrossSections: GreaterRealmV4ManifestAuthority['barrierCrossSections']; + gates: GreaterRealmV4ManifestAuthority['gates']; + geomorphologyVersion: string; + toolchainVersions: { + architecture: string; + configuredNodeEngine: string; + configuredPackageManager: string; + git: { + binaryPath: string; + binarySha256: string; + execPath: string; + version: string; + }; + installLayout: { + kind: string; + aliases: Record; + }; + libvips: string; + nodeExecutable: { path: string; sha256: string }; + platform: string; + preflight: { + kind: string; + manifestPath: string; + manifestSha256: string; + profile: string; + }; + runtimeNode: string; + sharp: { + version: string; + packageJson: { path: string; sha256: string }; + entrypoint: { path: string; sha256: string }; + native: Array<{ path: string; sha256: string }>; + libvipsNative: Array<{ path: string; sha256: string }>; + }; + tsx: { + version: string; + packageJson: { path: string; sha256: string }; + cli: { path: string; sha256: string }; + esbuildVersion: string; + esbuildPackageJson: { path: string; sha256: string }; + esbuildEntrypoint: { path: string; sha256: string }; + esbuildNativePackageJson: { path: string; sha256: string }; + esbuildNativeCli: { path: string; sha256: string }; + }; + typescript: { + version: string; + packageJson: { path: string; sha256: string }; + cli: { path: string; sha256: string }; + nativePackageJson: { path: string; sha256: string }; + nativeCli: { path: string; sha256: string }; + }; + }; + throneAnchor: { + dormant: boolean; + cell: number; + q: number; + r: number; + regionId: number; + tierId: number; + }; + chunkManifests: Array<{ + chunkKey: string; + partitionVersion: string; + geomorphologyVersion: string; + cellCount: number; + topographyPatchId: string; + topographyPatchDigest: string; + }>; + topographyPatchManifests: Array<{ + chunkKey: string; + sampleWidth: number; + sampleHeight: number; + sampleCount: number; + fieldCount: number; + payloadByteCount: number; + payloadDigest: string; + manifestDigest: string; + topographyPatchId: string; + geomorphologyVersion: string; + }>; + }; + expect(parsed.toolchainVersions.configuredNodeEngine).toBe('>=22.13 <23'); + expect(parsed.toolchainVersions.configuredPackageManager).toBe('npm@10.9.8'); + expect(parsed.toolchainVersions.libvips).toBe('8.18.3'); + expect(parsed.toolchainVersions.runtimeNode).toBe(process.versions.node); + expect(isAbsolute(parsed.toolchainVersions.nodeExecutable.path)).toBe(true); + expect(parsed.toolchainVersions.nodeExecutable.sha256).toMatch(/^[0-9a-f]{64}$/u); + expect(parsed.toolchainVersions.platform).toBe(process.platform); + expect(parsed.toolchainVersions.architecture).toBe(process.arch); + expect(parsed.toolchainVersions.preflight).toEqual({ + kind: 'locked-package-tree-v1', + manifestPath: 'scripts/atlas/greater-realm-toolchain-lock.json', + manifestSha256: TOOLCHAIN_LOCK_SHA256, + profile: `${process.platform}-${process.arch}`, + }); + expect(parsed.toolchainVersions.sharp.version).toBe('0.35.3'); + expect(parsed.toolchainVersions.tsx.version).toBe('4.23.0'); + expect(parsed.toolchainVersions.tsx.esbuildVersion).toBe('0.28.1'); + expect(parsed.toolchainVersions.typescript.version).toBe('7.0.2'); + expect(parsed.toolchainVersions.git.version).toMatch(/^git version /u); + expect(isAbsolute(parsed.toolchainVersions.git.binaryPath)).toBe(true); + expect(isAbsolute(parsed.toolchainVersions.git.execPath)).toBe(true); + expect(parsed.toolchainVersions.git.binarySha256).toMatch(/^[0-9a-f]{64}$/u); + expect(parsed.toolchainVersions.installLayout.kind).toMatch( + /^(?:direct-node-modules|bound-symlink-store|bound-mixed-node-modules)$/u, + ); + for (const alias of Object.values(parsed.toolchainVersions.installLayout.aliases)) { + expect(alias.kind).toMatch(/^(?:directory|symlink)$/u); + expect(alias.target).toMatch(/^node_modules\//u); + } + const attestedFiles = [ + parsed.toolchainVersions.sharp.packageJson, + parsed.toolchainVersions.sharp.entrypoint, + ...parsed.toolchainVersions.sharp.native, + ...parsed.toolchainVersions.sharp.libvipsNative, + parsed.toolchainVersions.tsx.packageJson, + parsed.toolchainVersions.tsx.cli, + parsed.toolchainVersions.tsx.esbuildPackageJson, + parsed.toolchainVersions.tsx.esbuildEntrypoint, + parsed.toolchainVersions.tsx.esbuildNativePackageJson, + parsed.toolchainVersions.tsx.esbuildNativeCli, + parsed.toolchainVersions.typescript.packageJson, + parsed.toolchainVersions.typescript.cli, + parsed.toolchainVersions.typescript.nativePackageJson, + parsed.toolchainVersions.typescript.nativeCli, + ]; + expect(parsed.toolchainVersions.sharp.native.length).toBeGreaterThan(0); + expect(parsed.toolchainVersions.sharp.libvipsNative.length).toBeGreaterThan(0); + for (const file of attestedFiles) { + expect(file.path).toMatch(/^node_modules\//u); + expect(file.sha256).toMatch(/^[0-9a-f]{64}$/u); + } + expect(parsed.geomorphologyVersion).toBe('greater-realm-geomorphology-v1'); + const throneCell = fixture.candidate.throneAnchor.findIndex(value => value === 1); + expect(throneCell).toBeGreaterThanOrEqual(0); + expect(parsed.throneAnchor).toEqual({ + dormant: true, + cell: throneCell, + q: fixture.candidate.grid.q[throneCell], + r: fixture.candidate.grid.r[throneCell], + regionId: 9, + tierId: 3, + }); + expect(parsed.barrierCrossSections).toEqual(fixture.candidate.barrierCrossSections); + expect(parsed.gates).toHaveLength(fixture.candidate.gates.length); + for (let index = 0; index < parsed.gates.length; index += 1) { + expect({ + firstApproachPath: parsed.gates[index]!.firstApproachPath, + firstAlternateApproachPath: parsed.gates[index]!.firstAlternateApproachPath, + secondApproachPath: parsed.gates[index]!.secondApproachPath, + secondAlternateApproachPath: parsed.gates[index]!.secondAlternateApproachPath, + }).toEqual({ + firstApproachPath: fixture.candidate.gates[index]!.firstApproachPath, + firstAlternateApproachPath: fixture.candidate.gates[index]!.firstAlternateApproachPath, + secondApproachPath: fixture.candidate.gates[index]!.secondApproachPath, + secondAlternateApproachPath: fixture.candidate.gates[index]!.secondAlternateApproachPath, + }); + } + expect(parsed.chunkManifests).toHaveLength(fixture.candidate.privateMetrics.chunkCount); + expect(parsed.topographyPatchManifests).toHaveLength(parsed.chunkManifests.length); + expect(new Set(parsed.chunkManifests.map(chunk => chunk.chunkKey)).size) + .toBe(parsed.chunkManifests.length); + expect(parsed.chunkManifests.reduce((sum, chunk) => sum + chunk.cellCount, 0)) + .toBe(fixture.candidate.grid.cellCount); + for (let index = 0; index < parsed.chunkManifests.length; index += 1) { + const chunk = parsed.chunkManifests[index]!; + const patch = parsed.topographyPatchManifests[index]!; + expect(patch.chunkKey).toBe(chunk.chunkKey); + expect(chunk.partitionVersion).toBe('axial-bin-15-v1'); + expect(chunk.geomorphologyVersion).toBe('greater-realm-geomorphology-v1'); + expect(patch.geomorphologyVersion).toBe('greater-realm-geomorphology-v1'); + expect(patch.topographyPatchId).toBe(chunk.topographyPatchId); + expect(patch.manifestDigest).toBe(chunk.topographyPatchDigest); + expect(patch.sampleCount).toBe(chunk.cellCount); + expect(patch.sampleWidth).toBeGreaterThan(0); + expect(patch.sampleWidth).toBeLessThanOrEqual(15); + expect(patch.sampleHeight).toBeGreaterThan(0); + expect(patch.sampleHeight).toBeLessThanOrEqual(15); + expect(patch.fieldCount).toBe(29); + expect(patch.payloadByteCount).toBe(patch.sampleCount * 84); + expect(patch.payloadDigest).toMatch(/^[0-9a-f]{64}$/u); + } + } finally { + bytes.fill(0); + } + }, 30_000); + + it('rejects path traversal before creating any package output', async () => { + const fixture = requireFixture(); + const before = fixture.workspace.attestTree().fileCount; + + await expect(writeGreaterRealmPrivateCandidate({ + workspace: fixture.workspace, + batchHandle: '../escaped', + candidateHandle: CANDIDATE_HANDLE, + sourceCommit: SOURCE_COMMIT, + candidate: fixture.candidate, + performance: Object.freeze({ + generationMilliseconds: 1_200, + processPeakMemoryMiB: 512, + }), + })).rejects.toThrow('GREATER_REALM_PRIVATE_PACKAGE_INPUT_INVALID'); + + expect(fixture.workspace.attestTree().fileCount).toBe(before); + expect(readdirSync(repositoryRoot)).toEqual([]); + }); + + it('rejects package values that the verifier cannot accept', async () => { + const fixture = requireFixture(); + await expect(writeGreaterRealmPrivateCandidate({ + workspace: fixture.workspace, + batchHandle: BATCH_HANDLE, + candidateHandle: CANDIDATE_HANDLE, + sourceCommit: SOURCE_COMMIT, + candidate: Object.freeze({ + ...fixture.candidate, + candidateOrdinal: 256, + }), + performance: PERFORMANCE, + })).rejects.toThrow('GREATER_REALM_PRIVATE_PACKAGE_INPUT_INVALID'); + await expect(writeGreaterRealmPrivateCandidate({ + workspace: fixture.workspace, + batchHandle: BATCH_HANDLE, + candidateHandle: CANDIDATE_HANDLE, + sourceCommit: SOURCE_COMMIT, + candidate: fixture.candidate, + performance: Object.freeze({ + generationMilliseconds: 7 * 24 * 60 * 60 * 1_000 + 100, + processPeakMemoryMiB: 512, + }), + })).rejects.toThrow('GREATER_REALM_PRIVATE_PACKAGE_INPUT_INVALID'); + await expect(writeGreaterRealmPrivateCandidate({ + workspace: fixture.workspace, + batchHandle: BATCH_HANDLE, + candidateHandle: CANDIDATE_HANDLE, + sourceCommit: SOURCE_COMMIT, + candidate: fixture.candidate, + performance: Object.freeze({ + generationMilliseconds: 1_200, + processPeakMemoryMiB: 1_048_584, + }), + })).rejects.toThrow('GREATER_REALM_PRIVATE_PACKAGE_INPUT_INVALID'); + }); + + it('rejects an unknown private preview mode before allocating image data', async () => { + const fixture = requireFixture(); + await expect(renderGreaterRealmPrivatePreview( + fixture.candidate, + 'untrusted-svg-fragment' as never, + )).rejects.toThrow('GREATER_REALM_PRIVATE_PREVIEW_MODE_INVALID'); + }); + + it('rejects an atlas whose structure and digest were changed together', async () => { + const relativePath = candidateRelativePath('atlas.wkgr-atlas'); + const original = requireFixture().workspace.readFile(relativePath); + const corrupted = Buffer.from(original); + original.fill(0); + corrupted[2] = corrupted[2]! ^ 0xff; + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedAtlasDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + }); + }, 15_000); + + it('rejects geological-barrier-band inventory-name tampering with an updated digest', async () => { + const relativePath = candidateRelativePath('atlas.wkgr-atlas'); + const original = requireFixture().workspace.readFile(relativePath); + const corrupted = Buffer.from(original); + original.fill(0); + const fieldName = Buffer.from('geological-barrier-band', 'utf8'); + const fieldNameOffset = corrupted.indexOf(fieldName); + expect(fieldNameOffset).toBeGreaterThan(0); + corrupted[fieldNameOffset] = 'x'.charCodeAt(0); + fieldName.fill(0); + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedAtlasDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_ATLAS_INVALID'); + }); + }, 15_000); + + it('rejects a manifest whose contents and expected digest were changed together', async () => { + const relativePath = candidateRelativePath('manifest.private.json'); + const original = requireFixture().workspace.readFile(relativePath); + const parsed = JSON.parse(original.toString('utf8')) as Record; + original.fill(0); + parsed.sourceCommit = 'b'.repeat(40); + const corrupted = Buffer.from(`${JSON.stringify(parsed, null, 2)}\n`, 'utf8'); + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + }); + }, 15_000); + + it('rejects unknown manifest fields even when the expected digest is updated', async () => { + const relativePath = candidateRelativePath('manifest.private.json'); + const original = requireFixture().workspace.readFile(relativePath); + const parsed = JSON.parse(original.toString('utf8')) as Record; + original.fill(0); + parsed.unreviewedPrivateField = true; + const corrupted = Buffer.from(`${JSON.stringify(parsed, null, 2)}\n`, 'utf8'); + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + }); + }, 15_000); + + it.each<[ + string, + (manifest: GreaterRealmV4ManifestAuthority) => void, + ]>([ + ['barrier cross-section cells', manifest => { + manifest.barrierCrossSections[0]!.cells[0] = + manifest.barrierCrossSections[0]!.cells[0]! + 1; + }], + ['first gate approach path', manifest => { + manifest.gates[0]!.firstApproachPath[0] = + manifest.gates[0]!.firstApproachPath[0]! + 1; + }], + ['first alternate gate approach path', manifest => { + manifest.gates[0]!.firstAlternateApproachPath[0] = + manifest.gates[0]!.firstAlternateApproachPath[0]! + 1; + }], + ['second gate approach path', manifest => { + manifest.gates[0]!.secondApproachPath[0] = + manifest.gates[0]!.secondApproachPath[0]! + 1; + }], + ['second alternate gate approach path', manifest => { + manifest.gates[0]!.secondAlternateApproachPath[0] = + manifest.gates[0]!.secondAlternateApproachPath[0]! + 1; + }], + ])('rejects tampered v4 %s authority with an updated digest', async (_label, mutate) => { + const relativePath = candidateRelativePath('manifest.private.json'); + const original = requireFixture().workspace.readFile(relativePath); + const parsed = JSON.parse(original.toString('utf8')) as GreaterRealmV4ManifestAuthority; + original.fill(0); + mutate(parsed); + const corrupted = Buffer.from(`${JSON.stringify(parsed, null, 2)}\n`, 'utf8'); + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + }); + }, 15_000); + + it('rejects duplicate private-manifest keys even when the final parsed value is valid', async () => { + const relativePath = candidateRelativePath('manifest.private.json'); + const original = requireFixture().workspace.readFile(relativePath); + const text = original.toString('utf8'); + original.fill(0); + const kindLine = text.split('\n').find(line => line.startsWith(' "kind": ')); + expect(kindLine).toBeDefined(); + const corrupted = Buffer.from( + text.replace(`${kindLine}\n`, `${kindLine}\n${kindLine}\n`), + 'utf8', + ); + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + }); + }); + + it('rejects a reordered private manifest even when all parsed values remain valid', async () => { + const relativePath = candidateRelativePath('manifest.private.json'); + const original = requireFixture().workspace.readFile(relativePath); + const parsed = JSON.parse(original.toString('utf8')) as Record; + original.fill(0); + const reordered = Object.fromEntries(Object.entries(parsed).reverse()); + const corrupted = Buffer.from(`${JSON.stringify(reordered, null, 2)}\n`, 'utf8'); + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + }); + }); + + it('rejects noncanonical private-manifest whitespace with an updated digest', async () => { + const relativePath = candidateRelativePath('manifest.private.json'); + const original = requireFixture().workspace.readFile(relativePath); + const parsed = JSON.parse(original.toString('utf8')) as Record; + original.fill(0); + const corrupted = Buffer.from(`${JSON.stringify(parsed)}\n`, 'utf8'); + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + }); + }); + + it.each([ + ['chunk payload', (manifest: { + chunkManifests: Array<{ payloadDigest: string }>; + }) => { manifest.chunkManifests[0]!.payloadDigest = '0'.repeat(64); }], + ['topography patch', (manifest: { + topographyPatchManifests: Array<{ payloadByteCount: number }>; + }) => { manifest.topographyPatchManifests[0]!.payloadByteCount += 1; }], + ['toolchain pin', (manifest: { + toolchainVersions: { typescript: string }; + }) => { manifest.toolchainVersions.typescript = '7.0.3'; }], + ])('rejects a changed %s even when the expected manifest digest is updated', async ( + _label, + mutate, + ) => { + const relativePath = candidateRelativePath('manifest.private.json'); + const original = requireFixture().workspace.readFile(relativePath); + const parsed = JSON.parse(original.toString('utf8')) as never; + original.fill(0); + mutate(parsed); + const corrupted = Buffer.from(`${JSON.stringify(parsed, null, 2)}\n`, 'utf8'); + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + }); + }); + + it('rejects preview corruption even when the PNG signature remains intact', async () => { + const relativePath = candidateRelativePath('previews/private-preview-regions.png'); + const original = requireFixture().workspace.readFile(relativePath); + const corrupted = Buffer.from(original); + original.fill(0); + expect(corrupted.length).toBeGreaterThan(64); + corrupted[Math.floor(corrupted.length / 2)] = ( + corrupted[Math.floor(corrupted.length / 2)]! ^ 0xff + ); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture()).rejects.toThrow( + 'GREATER_REALM_PRIVATE_PROVENANCE_INVALID', + ); + }); + }); + + it('rejects an oversized preview before parsing owner-workspace image bytes', async () => { + const relativePath = candidateRelativePath('previews/private-preview-hydrology.png'); + const original = requireFixture().workspace.readFile(relativePath); + const oversized = Buffer.concat([original, Buffer.from([0])]); + original.fill(0); + + await replacePrivateFile(relativePath, oversized, async () => { + await expect(verifyFixture()).rejects.toThrow( + 'GREATER_REALM_PRIVATE_FILE_INVALID', + ); + }); + }); + + it('requires the CRC-protected private marker inside every preview PNG', async () => { + const previewPath = candidateRelativePath('previews/private-preview-regions.png'); + const originalPreview = requireFixture().workspace.readFile(previewPath); + const corruptedPreview = Buffer.from(originalPreview); + originalPreview.fill(0); + const markerOffset = corruptedPreview.indexOf( + Buffer.from(GREATER_REALM_PRIVATE_PREVIEW_MARKER, 'ascii'), + ); + expect(markerOffset).toBeGreaterThan(0); + corruptedPreview[markerOffset] = 'X'.charCodeAt(0); + + const manifestPath = candidateRelativePath('manifest.private.json'); + const originalManifest = requireFixture().workspace.readFile(manifestPath); + const parsed = JSON.parse(originalManifest.toString('utf8')) as { + previewDigests: Record; + }; + originalManifest.fill(0); + parsed.previewDigests.regions = createHash('sha256').update(corruptedPreview).digest('hex'); + const corruptedManifest = Buffer.from(`${JSON.stringify(parsed, null, 2)}\n`, 'utf8'); + const corruptedManifestDigest = createHash('sha256').update(corruptedManifest).digest('hex'); + + await replacePrivateFile(manifestPath, corruptedManifest, async () => { + await replacePrivateFile(previewPath, corruptedPreview, async () => { + await expect(verifyFixture({ expectedManifestDigest: corruptedManifestDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); + }); + }); + }); + + it('rejects seed substitution even when every public expectation is unchanged', async () => { + const relativePath = candidateRelativePath('seed.bin'); + const original = requireFixture().workspace.readFile(relativePath); + const corrupted = Buffer.from(original); + original.fill(0); + corrupted[corrupted.length - 1] = corrupted[corrupted.length - 1]! ^ 0xff; + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture()).rejects.toThrow( + 'GREATER_REALM_PRIVATE_PROVENANCE_INVALID', + ); + }); + }); + + it('rejects candidate packages containing an unrecognized extra file', async () => { + const fixture = requireFixture(); + const relativePath = candidateRelativePath('unreviewed.private.bin'); + const path = join(fixture.workspace.root, relativePath); + fixture.workspace.writeFileAtomic(relativePath, Buffer.from('controlled-extra-file', 'utf8')); + try { + await expect(verifyFixture()).rejects.toThrow( + 'GREATER_REALM_PRIVATE_PACKAGE_INVENTORY_INVALID', + ); + } finally { + rmSync(path, { force: true }); + } + }); + + it('rejects a candidate that cannot be derived from the protected batch seed', async () => { + const relativePath = `batches/${BATCH_HANDLE}/batch-seed.bin`; + const original = requireFixture().workspace.readFile( + relativePath, + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + const corrupted = Buffer.from(original); + original.fill(0); + corrupted[corrupted.length - 1] = corrupted[corrupted.length - 1]! ^ 0xff; + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture()).rejects.toThrow( + 'GREATER_REALM_PRIVATE_PROVENANCE_INVALID', + ); + }); + }); + + it('binds the candidate ordinal into private provenance verification', async () => { + await expect(verifyFixture({ expectedCandidateOrdinal: CANDIDATE_ORDINAL + 1 })) + .rejects.toThrow('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); + }); + + it('binds the sanitized aggregate and rounded performance to the private authority', async () => { + const fixture = requireFixture(); + const alteredAggregate = Object.freeze({ + ...fixture.candidate.aggregate, + quality: Object.freeze({ + ...fixture.candidate.aggregate.quality, + naturalnessBasisPoints: fixture.candidate.aggregate.quality.naturalnessBasisPoints - 1, + }), + }); + await expect(verifyFixture({ expectedAggregate: alteredAggregate })) + .rejects.toThrow('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); + await expect(verifyFixture({ + expectedPerformance: Object.freeze({ ...PERFORMANCE, processPeakMemoryMiB: 520 }), + })).rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + }, 30_000); + + it('rejects a topography field whose runtime array type does not match its encoding', () => { + const fixture = requireFixture(); + const invalid = Object.freeze({ + ...fixture.candidate, + aspect: new Uint16Array(fixture.candidate.aspect), + }) as unknown as GreaterRealmPrivateCandidate; + expect(() => serializeGreaterRealmPrivateAtlas(invalid)).toThrow( + 'GREATER_REALM_PRIVATE_FIELD_INVALID', + ); + }); + + it('zeroes a serialized atlas when private persistence fails', async () => { + const fixture = requireFixture(); + let exposedAtlas: Uint8Array | undefined; + const rejectingWorkspace: GreaterRealmPrivateWorkspace = Object.freeze({ + ...fixture.workspace, + ensureDirectory: () => fixture.workspace.root, + writeFileAtomic: (path: string, bytes: Uint8Array) => { + if (!path.endsWith('/atlas.wkgr-atlas')) return; + exposedAtlas = bytes; + throw new Error('CONTROLLED_PRIVATE_ATLAS_WRITE_FAILURE'); + }, + }); + + await expect(writeGreaterRealmPrivateCandidate({ + workspace: rejectingWorkspace, + batchHandle: BATCH_HANDLE, + candidateHandle: CANDIDATE_HANDLE, + sourceCommit: SOURCE_COMMIT, + candidate: fixture.candidate, + performance: Object.freeze({ + generationMilliseconds: 1_200, + processPeakMemoryMiB: 512, + }), + })).rejects.toThrow('CONTROLLED_PRIVATE_ATLAS_WRITE_FAILURE'); + + expect(exposedAtlas).toBeDefined(); + expect(exposedAtlas?.every(value => value === 0)).toBe(true); + }); + + it('zeroes an atlas allocated before candidate-seed persistence fails', async () => { + const fixture = requireFixture(); + const expectedAtlas = serializeGreaterRealmPrivateAtlas(fixture.candidate); + const expectedAtlasBytes = expectedAtlas.byteLength; + expectedAtlas.fill(0); + const atlasAllocations: Buffer[] = []; + const allocateUnsafe = Buffer.allocUnsafe; + const allocationSpy = vi.spyOn(Buffer, 'allocUnsafe').mockImplementation(byteLength => { + const allocation = allocateUnsafe(byteLength); + if (byteLength === expectedAtlasBytes) atlasAllocations.push(allocation); + return allocation; + }); + const rejectingWorkspace: GreaterRealmPrivateWorkspace = Object.freeze({ + ...fixture.workspace, + ensureDirectory: () => fixture.workspace.root, + writeFileAtomic: (path: string) => { + if (path.endsWith('/seed.bin')) { + throw new Error('CONTROLLED_PRIVATE_SEED_WRITE_FAILURE'); + } + }, + }); + + try { + await expect(writeGreaterRealmPrivateCandidate({ + workspace: rejectingWorkspace, + batchHandle: BATCH_HANDLE, + candidateHandle: CANDIDATE_HANDLE, + sourceCommit: SOURCE_COMMIT, + candidate: fixture.candidate, + performance: Object.freeze({ + generationMilliseconds: 1_200, + processPeakMemoryMiB: 512, + }), + })).rejects.toThrow('CONTROLLED_PRIVATE_SEED_WRITE_FAILURE'); + } finally { + allocationSpy.mockRestore(); + } + + expect(atlasAllocations).toHaveLength(1); + expect(atlasAllocations[0]?.every(value => value === 0)).toBe(true); + }); + + it('zeroes a private manifest when its atomic write fails', async () => { + const fixture = requireFixture(); + let exposedManifest: Uint8Array | undefined; + const rejectingWorkspace: GreaterRealmPrivateWorkspace = Object.freeze({ + ...fixture.workspace, + ensureDirectory: () => fixture.workspace.root, + writeFileAtomic: (path: string, bytes: Uint8Array) => { + if (!path.endsWith('/manifest.private.json')) return; + exposedManifest = bytes; + throw new Error('CONTROLLED_PRIVATE_MANIFEST_WRITE_FAILURE'); + }, + }); + + await expect(writeGreaterRealmPrivateCandidate({ + workspace: rejectingWorkspace, + batchHandle: BATCH_HANDLE, + candidateHandle: CANDIDATE_HANDLE, + sourceCommit: SOURCE_COMMIT, + candidate: fixture.candidate, + performance: Object.freeze({ + generationMilliseconds: 1_200, + processPeakMemoryMiB: 512, + }), + })).rejects.toThrow('CONTROLLED_PRIVATE_MANIFEST_WRITE_FAILURE'); + + expect(exposedManifest).toBeDefined(); + expect(exposedManifest?.every(value => value === 0)).toBe(true); + }); + + it('zeroes an acquired seed if a later private package read fails', async () => { + const fixture = requireFixture(); + const exposedBatchSeed = fixture.workspace.readFile( + `batches/${BATCH_HANDLE}/batch-seed.bin`, + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + const exposedSeed = fixture.workspace.readFile( + candidateRelativePath('seed.bin'), + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + ); + const rejectingWorkspace: GreaterRealmPrivateWorkspace = Object.freeze({ + ...fixture.workspace, + readFile: (path: string) => { + if (path.endsWith('/batch-seed.bin')) return exposedBatchSeed; + if (path.endsWith('/seed.bin')) return exposedSeed; + throw new Error('CONTROLLED_PRIVATE_ATLAS_READ_FAILURE'); + }, + }); + + await expect(verifyGreaterRealmPrivateCandidatePackage({ + workspace: rejectingWorkspace, + batchHandle: BATCH_HANDLE, + candidateHandle: CANDIDATE_HANDLE, + expectedCandidateOrdinal: CANDIDATE_ORDINAL, + sourceCommit: SOURCE_COMMIT, + expectedBatchSeedDigest: batchSeedDigest, + expectedActiveCellCount: fixture.candidate.grid.cellCount, + expectedAggregate: fixture.candidate.aggregate, + expectedPerformance: PERFORMANCE, + expectedAtlasDigest: atlasDigest, + expectedManifestDigest: manifestDigest, + })).rejects.toThrow('CONTROLLED_PRIVATE_ATLAS_READ_FAILURE'); + + expect(exposedBatchSeed.every(value => value === 0)).toBe(true); + expect(exposedSeed.every(value => value === 0)).toBe(true); + }); + + it('zeroes every advanced-topography and dormant-throne authority buffer', () => { + const advanced = { + tectonicUplift: Int32Array.of(1), + rockResistance: Int32Array.of(2), + slope: Uint16Array.of(3), + aspect: Uint8Array.of(4), + profileCurvature: Int32Array.of(-5), + planCurvature: Int32Array.of(6), + wetnessIndex: Uint16Array.of(7), + exposure: Int32Array.of(-8), + distanceToCoast: Uint16Array.of(9), + distanceToFreshwater: Uint16Array.of(10), + watershedId: Int32Array.of(11), + ridgeId: Int32Array.of(12), + temperature: Int32Array.of(-13), + moisture: Int32Array.of(14), + geomorphologyTotalDelta: Int32Array.of(15), + geomorphologyElevation: Int32Array.of(14), + geomorphologyGlacialDelta: Int32Array.of(16), + geomorphologyAridDelta: Int32Array.of(17), + geomorphologyVolcanicDelta: Int32Array.of(18), + geomorphologyCoastalDelta: Int32Array.of(19), + geomorphologyGlacialMask: Uint8Array.of(1), + geomorphologyAridMask: Uint8Array.of(1), + geomorphologyVolcanicMask: Uint8Array.of(1), + geomorphologyVolcanicAnchorMask: Uint8Array.of(1), + geomorphologyCoastalMask: Uint8Array.of(1), + geomorphologyCoastalClass: Uint8Array.of(2), + geomorphologyTemperature: Int32Array.of(-20), + geomorphologyMoisture: Int32Array.of(21), + }; + const u8 = () => Uint8Array.of(1); + const i32 = () => Int32Array.of(1); + const gatePaths = [[1, 2], [3, 4], [5, 6], [7, 8]]; + const barrierCrossSectionCells = [1, 2, 3, 4]; + const seedMaterialKey = ['seed', 'Material'].join('') as 'seedMaterial'; + const dummy = { + [seedMaterialKey]: Buffer.alloc(32, 1), + candidateSeed: Uint32Array.of(1, 2, 3, 4), + gates: [{ + firstApproachPath: gatePaths[0], + firstAlternateApproachPath: gatePaths[1], + secondApproachPath: gatePaths[2], + secondAlternateApproachPath: gatePaths[3], + }], + barrierCrossSections: [{ cells: barrierCrossSectionCells }], + grid: { q: i32(), r: i32(), neighbors: i32() }, + bedrockElevation: i32(), + elevation: i32(), + filledElevation: i32(), + sedimentDepth: Uint16Array.of(1), + flowReceiver: i32(), + flowAccumulation: BigUint64Array.of(1n), + domainId: u8(), + geologyId: u8(), + regionId: u8(), + tierId: u8(), + waterRegime: u8(), + biomeId: u8(), + landformId: u8(), + barrier: u8(), + geologicalBarrierBand: u8(), + castleSlot: u8(), + resourcePotential: u8(), + corePotential: u8(), + throneAnchor: u8(), + legacyLowlandsCell: u8(), + legacyLowlandsProtectedCell: u8(), + legacyLowlandsReserveCell: u8(), + legacyLowlandsCastleSlot: u8(), + ...advanced, + } as unknown as GreaterRealmPrivateCandidate; + + clearGreaterRealmPrivateCandidateBuffers(dummy); + + expect(dummy.seedMaterial.every(value => value === 0)).toBe(true); + expect(dummy.candidateSeed.every(value => value === 0)).toBe(true); + expect(gatePaths.every(path => path.every(value => value === 0))).toBe(true); + expect(barrierCrossSectionCells.every(value => value === 0)).toBe(true); + expect(dummy.geologicalBarrierBand.every(value => value === 0)).toBe(true); + for (const array of Object.values(advanced)) { + expect(array.every(value => value === 0)).toBe(true); + } + expect(dummy.throneAnchor.every(value => value === 0)).toBe(true); + expect(dummy.grid.neighbors.every(value => value === 0)).toBe(true); + }); +}); diff --git a/tests/greaterRealmCandidateRejection.test.ts b/tests/greaterRealmCandidateRejection.test.ts new file mode 100644 index 00000000..586f68cd --- /dev/null +++ b/tests/greaterRealmCandidateRejection.test.ts @@ -0,0 +1,71 @@ +// @vitest-environment node + +import { describe, expect, it } from 'vitest'; + +import { + GREATER_REALM_CANDIDATE_REJECTION_CODES, + GreaterRealmCandidateRejectionError, + greaterRealmCandidateRejectionCode, + rejectGreaterRealmCandidate, + type GreaterRealmCandidateRejectionCode, +} from '../scripts/atlas/greater-realm-candidate-rejection'; + +describe('Greater Realm expected candidate rejection boundary', () => { + it('keeps one exact frozen allowlist of geography-search exhaustion codes', () => { + expect(GREATER_REALM_CANDIDATE_REJECTION_CODES).toEqual([ + 'GREATER_REALM_TECTONIC_DOMAIN_PLACEMENT_FAILED', + 'GREATER_REALM_ISLAND_ARC_PLACEMENT_FAILED', + 'GREATER_REALM_ACTIVE_MASK_EMPTY', + 'GREATER_REALM_LEGACY_LOWLANDS_PLACEMENT_MISSING', + 'GREATER_REALM_LEGACY_LOWLANDS_PLACEMENT_FAILED', + 'GREATER_REALM_OCEAN_OUTLETS_MISSING', + 'GREATER_REALM_RECONCILED_OCEAN_OUTLETS_MISSING', + 'GREATER_REALM_LEGACY_LOWLANDS_RESERVE_TOO_LARGE', + 'GREATER_REALM_STRATEGIC_BASIN_CAPACITY_INVARIANT', + 'GREATER_REALM_TIER_THREE_CAPACITY_INVARIANT', + 'GREATER_REALM_TIER_TWO_CAPACITY_INVARIANT', + 'GREATER_REALM_STRATEGIC_HIGHLAND_REFERENCE_MISSING', + ]); + expect(Object.isFrozen(GREATER_REALM_CANDIDATE_REJECTION_CODES)).toBe(true); + expect(new Set(GREATER_REALM_CANDIDATE_REJECTION_CODES).size) + .toBe(GREATER_REALM_CANDIDATE_REJECTION_CODES.length); + }); + + it('classifies only the typed error and never a message-compatible Error', () => { + const code = GREATER_REALM_CANDIDATE_REJECTION_CODES[0]; + const typed = new GreaterRealmCandidateRejectionError(code); + + expect(typed).toBeInstanceOf(GreaterRealmCandidateRejectionError); + expect(typed.code).toBe(code); + expect(typed.message).toBe(code); + expect(greaterRealmCandidateRejectionCode(typed)).toBe(code); + expect(greaterRealmCandidateRejectionCode(new Error(code))).toBeUndefined(); + expect(greaterRealmCandidateRejectionCode({ name: typed.name, code })).toBeUndefined(); + }); + + it('rejects a runtime-forged code outside the audited union', () => { + for (const fatalCode of [ + 'GREATER_REALM_FLOW_CYCLE', + 'GREATER_REALM_SEDIMENT_BUDGET_MISMATCH', + 'GREATER_REALM_PARENT_BASIN_ANCHOR_MISSING', + 'GREATER_REALM_GATE_PARENT_ASSIGNMENT_FAILED', + ]) { + expect(GREATER_REALM_CANDIDATE_REJECTION_CODES).not.toContain(fatalCode); + expect(() => new GreaterRealmCandidateRejectionError( + fatalCode as GreaterRealmCandidateRejectionCode, + )).toThrow('GREATER_REALM_CANDIDATE_REJECTION_CODE_INVALID'); + } + }); + + it('throws the typed rejection through the dedicated factory', () => { + const code = GREATER_REALM_CANDIDATE_REJECTION_CODES[1]; + try { + rejectGreaterRealmCandidate(code); + } catch (error) { + expect(error).toBeInstanceOf(GreaterRealmCandidateRejectionError); + expect(greaterRealmCandidateRejectionCode(error)).toBe(code); + return; + } + throw new Error('GREATER_REALM_REJECTION_FACTORY_DID_NOT_THROW'); + }); +}); diff --git a/tests/greaterRealmCliSecurity.test.ts b/tests/greaterRealmCliSecurity.test.ts new file mode 100644 index 00000000..108b7cf9 --- /dev/null +++ b/tests/greaterRealmCliSecurity.test.ts @@ -0,0 +1,427 @@ +// @vitest-environment node + +import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { + chmodSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + realpathSync, + renameSync, + rmSync, + statSync, + writeFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { afterEach, describe, expect, it } from 'vitest'; + +import { + buildGreaterRealmPrivateCandidateShortlist, + greaterRealmPublicEvidenceTestSeams, + resolveGreaterRealmPublicEvidenceDestination, + verifyGreaterRealmPrivateRejectedAttempt, +} from '../scripts/atlas/greater-realm-cli'; +import { generateGreaterRealmCandidate } from '../scripts/atlas/greater-realm-candidate-generator'; +import { clearGreaterRealmPrivateCandidateBuffers } from '../scripts/atlas/greater-realm-candidate-package'; +import type { + GreaterRealmVerifiedPrivateShortlistMetrics, +} from '../scripts/atlas/greater-realm-candidate-package'; +import type { GreaterRealmSanitizedReview } from '../scripts/atlas/greater-realm-contracts'; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const tsxCli = resolve(repositoryRoot, 'node_modules/tsx/dist/cli.mjs'); +const atlasCli = resolve(repositoryRoot, 'scripts/atlas/greater-realm-cli.ts'); +const forbiddenWorkspace = join(repositoryRoot, '.warpkeep-private-cli-security-test'); +const publicEvidenceRoots: string[] = []; + +function publicEvidenceFixture() { + const root = realpathSync(mkdtempSync(join(tmpdir(), 'warpkeep-public-evidence-'))); + publicEvidenceRoots.push(root); + const fixtureRepositoryRoot = join(root, 'repository'); + const evidenceRoot = join(fixtureRepositoryRoot, 'docs', 'evidence', 'greater-realm'); + mkdirSync(evidenceRoot, { recursive: true, mode: 0o755 }); + return Object.freeze({ evidenceRoot, repositoryRoot: fixtureRepositoryRoot, root }); +} + +function runAtlasCli( + arguments_: readonly string[], + overrides: Readonly> = {}, +) { + const environment: NodeJS.ProcessEnv = { + HOME: process.env.HOME, + PATH: process.env.PATH, + TMPDIR: process.env.TMPDIR, + WARPKEEP_QA_SOCKET_TMP: process.env.WARPKEEP_QA_SOCKET_TMP, + ...overrides, + }; + for (const [key, value] of Object.entries(environment)) { + if (value === undefined) delete environment[key]; + } + return spawnSync(process.execPath, [tsxCli, atlasCli, ...arguments_], { + cwd: repositoryRoot, + encoding: 'utf8', + env: environment, + timeout: 10_000, + }); +} + +afterEach(() => { + rmSync(forbiddenWorkspace, { force: true, recursive: true }); + for (const root of publicEvidenceRoots.splice(0)) { + rmSync(root, { force: true, recursive: true }); + } +}); + +describe('Greater Realm atlas CLI security boundary', () => { + it('creates an unranked Pareto-diversity shortlist without choosing a winner', () => { + const candidate = ( + suffix: string, + values: readonly [number, number, number, number, number], + ) => ({ + candidateHandle: `GR-A-${suffix.padStart(16, 'A')}`, + eligible: true, + quality: { + naturalnessBasisPoints: values[0], + ridgeContinuityBasisPoints: values[1], + hydrologyCoherenceBasisPoints: values[2], + }, + biomes: { + visualClassCount: values[3], + minimumPerRegionVisualClassCount: values[4], + maximumTierISingleBiomeShareBasisPoints: 6_000, + incompatibleVisualAdjacencyCount: 0, + incompatibleBiomeLandformPairCount: 0, + }, + }); + const candidates = [ + candidate('B', [9_900, 5_000, 5_000, 5, 2]), + candidate('C', [8_000, 5_000, 5_000, 5, 2]), + candidate('D', [8_000, 5_000, 5_000, 5, 2]), + candidate('E', [8_000, 5_000, 5_000, 5, 2]), + candidate('F', [8_000, 5_000, 5_000, 12, 9]), + candidate('G', [8_100, 5_100, 5_100, 6, 3]), + candidate('H', [7_000, 4_000, 4_000, 4, 1]), + candidate('I', [7_100, 4_100, 4_100, 4, 1]), + ]; + const privateMetrics = candidates.map((entry, index) => Object.freeze({ + candidateHandle: entry.candidateHandle, + maximumBoundaryRadiusShareBasisPoints: 1_000, + rotationalSimilarityBasisPoints: 4_000, + maximumAlignedBoundaryRun: 24, + saltwaterBoundaryBasisPoints: 9_900, + minimumLargestPassableRegionShareBasisPoints: index === 1 ? 9_900 : 8_000, + maximumMinorPassableFragmentShareBasisPoints: 500, + maximumPassableBoundaryDensityBasisPoints: 2_000, + maximumPassableTendrilShareBasisPoints: 300, + throneAnchorBarrierClearance: 8, + gateRouteRedundancyProof: true, + measuredMinimumBarrierWidth: 4, + measuredMaximumBarrierWidth: 8, + chunkCount: 700, + chunkPopulationSpread: index === 2 ? 1 : 100, + chunkUpperTailSpread: 20, + highlandBarrierShareBasisPoints: 8_000, + barrierMeanElevationAdvantage: 2_000, + barrierMeanUpliftAdvantage: 700, + ridgeUpliftAlignmentBasisPoints: index === 3 ? 9_900 : 7_000, + riverValleyAlignmentBasisPoints: 8_000, + landformClimateCompatibilityFloorBasisPoints: 9_000, + coastalProximityCompatibilityBasisPoints: 9_000, + coastalClassCount: 4, + } satisfies GreaterRealmVerifiedPrivateShortlistMetrics)); + const review = { + reviewBatchHandle: 'GR-B-AAAAAAAAAAAAAAAA', + reportDigest: 'a'.repeat(64), + selectionStatus: 'pending', + selectedCandidateHandle: null, + candidates, + } as unknown as GreaterRealmSanitizedReview; + + const shortlist = buildGreaterRealmPrivateCandidateShortlist(review, privateMetrics); + const reversed = buildGreaterRealmPrivateCandidateShortlist({ + ...review, + candidates: [...review.candidates].reverse(), + }, [...privateMetrics].reverse()); + + expect(shortlist).toEqual(reversed); + expect(shortlist.shortlistCount).toBeGreaterThanOrEqual(3); + expect(shortlist.shortlistCount).toBeLessThanOrEqual(5); + expect(shortlist.candidateHandles).toHaveLength(shortlist.shortlistCount); + expect(shortlist.candidateHandles).toEqual( + candidates.slice(0, 5).map(entry => entry.candidateHandle).sort(), + ); + expect(shortlist.selectionStatus).toBe('pending'); + expect(shortlist.selectedCandidateHandle).toBeNull(); + expect(shortlist.ranked).toBe(false); + expect(shortlist.automaticSelection).toBe(false); + expect(shortlist.method).toBe('pareto-private-vector-diversity-v2'); + expect(shortlist.comparisonBasis).toBe('verified-private-package-aggregate-metrics-v1'); + expect(shortlist.objectiveDirections).toEqual(expect.arrayContaining([ + 'minimize:OUTER_BOUNDARY_ROTATIONAL_ARTIFACT', + 'maximize:PASSABLE_REGION_COHERENCE', + 'minimize:CHUNK_POPULATION_SPREAD', + 'maximize:RIDGE_UPLIFT_ALIGNMENT', + 'maximize:LANDFORM_CLIMATE_COMPATIBILITY_FLOOR', + 'minimize:TIER_I_SINGLE_BIOME_DOMINANCE', + ])); + expect(shortlist.requiredConstraints) + .toContain('equal-zero:INCOMPATIBLE_BIOME_ADJACENCY'); + expect(shortlist.requiredConstraints) + .toContain('equal-zero:INCOMPATIBLE_BIOME_LANDFORM_PAIRING'); + expect(shortlist.requiredConstraints).toContain('equal-true:GATE_ROUTE_REDUNDANCY'); + expect(shortlist.requiredConstraints).toContain('range-inclusive:MOUNTAIN_BARRIER_WIDTH_4_8'); + expect(JSON.stringify(shortlist)).not.toMatch(/(?:winner|recommend|score|rank":\s*[0-9])/iu); + expect(JSON.stringify(shortlist)).not.toMatch(/(?:coordinate|seed|transform|chunkKey)/iu); + + expect(() => buildGreaterRealmPrivateCandidateShortlist({ + ...review, + candidates: review.candidates.map((entry, index) => index === 0 + ? { + ...entry, + biomes: { ...entry.biomes, incompatibleBiomeLandformPairCount: 1 }, + } + : entry), + }, privateMetrics)).toThrow('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + expect(() => buildGreaterRealmPrivateCandidateShortlist( + review, + privateMetrics.slice(1), + )).toThrow('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + expect(() => buildGreaterRealmPrivateCandidateShortlist( + review, + privateMetrics.map((entry, index) => index === 0 + ? { ...entry, gateRouteRedundancyProof: false } + : entry), + )).toThrow('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); + }); + + it.each([ + Object.freeze({ + label: 'named seed argument', + arguments: Object.freeze([ + 'generate-candidates', + '--seed', + 'ab'.repeat(32), + ]), + environment: Object.freeze({}), + }), + Object.freeze({ + label: 'unnamed secret-like argument', + arguments: Object.freeze([ + 'generate-candidates', + 'cd'.repeat(32), + ]), + environment: Object.freeze({}), + }), + Object.freeze({ + label: 'reserved generation environment key', + arguments: Object.freeze(['generate-candidates']), + environment: Object.freeze({ WARPKEEP_GREATER_REALM_SEED: 'controlled-test-value' }), + }), + Object.freeze({ + label: 'secret-like generic environment value', + arguments: Object.freeze(['generate-candidates']), + environment: Object.freeze({ GENERIC_CREDENTIAL: 'ef'.repeat(32) }), + }), + ])('rejects $label without reflecting private material', ({ arguments: args, environment }) => { + const result = runAtlasCli(args, environment); + const privateValues = [ + ...args.filter(value => value.length >= 32), + ...Object.values(environment).filter(value => value.length >= 32), + ]; + + expect(result.error).toBeUndefined(); + expect(result.status).toBe(1); + expect(result.stdout).toBe(''); + expect(result.stderr).toBe('GREATER_REALM_PRIVATE_INVOCATION_REJECTED\n'); + for (const value of privateValues) { + expect(`${result.stdout}${result.stderr}`).not.toContain(value); + } + }); + + it('refuses a private workspace inside the repository before writing anything', () => { + expect(existsSync(forbiddenWorkspace)).toBe(false); + + const result = runAtlasCli([ + 'generate-candidates', + '--workspace', + forbiddenWorkspace, + '--count', + '8', + '--maximum-attempts', + '8', + ]); + + expect(result.error).toBeUndefined(); + expect(result.status).toBe(1); + expect(result.stdout).toBe(''); + expect(result.stderr).toBe('GREATER_REALM_PRIVATE_ROOT_REPOSITORY_OVERLAP\n'); + expect(result.stderr).not.toContain(forbiddenWorkspace); + expect(existsSync(forbiddenWorkspace)).toBe(false); + }); + + it('rejects an attempt budget smaller than the requested candidate count', () => { + const result = runAtlasCli([ + 'generate-candidates', + '--count', + '16', + '--maximum-attempts', + '8', + ]); + + expect(result.error).toBeUndefined(); + expect(result.status).toBe(1); + expect(result.stdout).toBe(''); + expect(result.stderr).toBe('GREATER_REALM_CLI_ARGUMENTS_INVALID\n'); + }); + + it('regenerates rejected attempts and rejects a tampered failure ledger', () => { + const rootSeed = Uint8Array.from(createHash('sha256') + .update('greater-realm-test-root\0', 'utf8') + .update('52', 'utf8') + .digest()); + const candidate = generateGreaterRealmCandidate({ rootSeed, candidateOrdinal: 0 }); + try { + expect(candidate.aggregate.eligible).toBe(false); + const rejectedAttempt = Object.freeze({ + kind: 'proof-rejection' as const, + candidateOrdinal: 0, + activeCellCount: candidate.grid.cellCount, + failedProofs: candidate.privateMetrics.eligibilityFailureCodes, + }); + expect(() => verifyGreaterRealmPrivateRejectedAttempt({ + rootSeed, + rejectedAttempt, + })).not.toThrow(); + expect(() => verifyGreaterRealmPrivateRejectedAttempt({ + rootSeed, + rejectedAttempt: Object.freeze({ + ...rejectedAttempt, + failedProofs: Object.freeze([ + 'CONTROLLED_TAMPERED_PROOF', + ...rejectedAttempt.failedProofs.slice(1), + ]), + }), + })).toThrow('GREATER_REALM_PRIVATE_ATTEMPT_LEDGER_INVALID'); + expect(() => verifyGreaterRealmPrivateRejectedAttempt({ + rootSeed, + rejectedAttempt: Object.freeze({ + kind: 'geography-exhaustion', + candidateOrdinal: 0, + rejectionCode: 'GREATER_REALM_ACTIVE_MASK_EMPTY', + }), + })).toThrow('GREATER_REALM_PRIVATE_ATTEMPT_LEDGER_INVALID'); + } finally { + clearGreaterRealmPrivateCandidateBuffers(candidate); + rootSeed.fill(0); + } + }, 120_000); + + it('restricts public evidence exports to one canonical JSON basename', () => { + const expected = resolve( + repositoryRoot, + 'docs/evidence/greater-realm/candidate-review-v1.json', + ); + expect(resolveGreaterRealmPublicEvidenceDestination( + 'docs/evidence/greater-realm/candidate-review-v1.json', + )).toBe(expected); + + for (const rejected of [ + 'docs/evidence/greater-realm/nested/review.json', + 'docs/evidence/greater-realm/../review.json', + 'docs/evidence/greater-realm/Review.json', + '/tmp/greater-realm-review.json', + ]) { + expect(() => resolveGreaterRealmPublicEvidenceDestination(rejected)) + .toThrow('GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_INVALID'); + } + }); + + it('publishes exact sanitized evidence bytes with pinned public-file metadata', () => { + const fixture = publicEvidenceFixture(); + const path = 'docs/evidence/greater-realm/review-test.json'; + const destination = join(fixture.repositoryRoot, path); + const bytes = Buffer.from('{"kind":"sanitized-test","value":7}\n', 'utf8'); + + greaterRealmPublicEvidenceTestSeams.write({ + repositoryRoot: fixture.repositoryRoot, + path, + bytes, + }); + + const status = statSync(destination); + expect(readFileSync(destination)).toEqual(bytes); + expect(status.isFile()).toBe(true); + expect(status.nlink).toBe(1); + expect(status.size).toBe(bytes.byteLength); + expect(status.mode & 0o777).toBe(0o644); + if (process.getuid !== undefined) expect(status.uid).toBe(process.getuid()); + }); + + it('rejects temporary-file substitution without deleting the replacement', () => { + const fixture = publicEvidenceFixture(); + const path = 'docs/evidence/greater-realm/review-substitution.json'; + const destination = join(fixture.repositoryRoot, path); + const replacement = Buffer.from('attacker-controlled replacement\n', 'utf8'); + let temporary = ''; + + expect(() => greaterRealmPublicEvidenceTestSeams.write({ + repositoryRoot: fixture.repositoryRoot, + path, + bytes: Buffer.from('{"kind":"sanitized-test"}\n', 'utf8'), + beforeInstall: paths => { + temporary = paths.temporaryPath; + renameSync(temporary, `${temporary}.displaced`); + writeFileSync(temporary, replacement, { mode: 0o644 }); + }, + })).toThrow('GREATER_REALM_PUBLIC_EVIDENCE_WRITE_FAILED'); + + expect(existsSync(destination)).toBe(false); + expect(readFileSync(temporary)).toEqual(replacement); + }); + + it('rejects parent substitution while retaining the pinned original directory', () => { + const fixture = publicEvidenceFixture(); + const path = 'docs/evidence/greater-realm/review-parent-race.json'; + const destination = join(fixture.repositoryRoot, path); + const displacedParent = `${fixture.evidenceRoot}.displaced`; + + expect(() => greaterRealmPublicEvidenceTestSeams.write({ + repositoryRoot: fixture.repositoryRoot, + path, + bytes: Buffer.from('{"kind":"sanitized-test"}\n', 'utf8'), + beforeInstall: ({ parentPath }) => { + renameSync(parentPath, displacedParent); + mkdirSync(parentPath, { mode: 0o755 }); + }, + })).toThrow(/GREATER_REALM_PUBLIC_EVIDENCE_DESTINATION_(?:CHANGED|INVALID)/u); + + expect(existsSync(destination)).toBe(false); + expect(existsSync(join(displacedParent, 'review-parent-race.json'))).toBe(false); + }); + + it('fails closed when sanitized evidence bytes or mode drift before install', () => { + for (const mutate of [ + (temporary: string) => writeFileSync( + temporary, + Buffer.from('{"kind":"tampered-value"}\n', 'utf8'), + ), + (temporary: string) => chmodSync(temporary, 0o600), + ]) { + const fixture = publicEvidenceFixture(); + const path = 'docs/evidence/greater-realm/review-drift.json'; + const destination = join(fixture.repositoryRoot, path); + expect(() => greaterRealmPublicEvidenceTestSeams.write({ + repositoryRoot: fixture.repositoryRoot, + path, + bytes: Buffer.from('{"kind":"sanitized-test"}\n', 'utf8'), + beforeInstall: ({ temporaryPath }) => mutate(temporaryPath), + })).toThrow('GREATER_REALM_PUBLIC_EVIDENCE_WRITE_FAILED'); + expect(existsSync(destination)).toBe(false); + } + }); +}); diff --git a/tests/greaterRealmGeomorphology.test.ts b/tests/greaterRealmGeomorphology.test.ts new file mode 100644 index 00000000..71cceb9a --- /dev/null +++ b/tests/greaterRealmGeomorphology.test.ts @@ -0,0 +1,161 @@ +import { describe, expect, it } from 'vitest'; + +import { + GREATER_REALM_GEOMORPHOLOGY_VERSION, + shapeGreaterRealmGeomorphology, +} from '../scripts/atlas/greater-realm-geomorphology'; +import { + greaterRealmHexDistance, + indexGreaterRealmAxialGrid, + type AxialCoordinate, +} from '../scripts/atlas/greater-realm-terrain'; + +function syntheticFixture(radius = 28) { + const coordinates: AxialCoordinate[] = []; + for (let q = -radius; q <= radius; q += 1) { + const minimumR = Math.max(-radius, -q - radius); + const maximumR = Math.min(radius, -q + radius); + for (let r = minimumR; r <= maximumR; r += 1) coordinates.push({ q, r }); + } + const grid = indexGreaterRealmAxialGrid(coordinates); + const elevation = new Int32Array(grid.cellCount); + const tectonicUplift = new Int32Array(grid.cellCount); + const rockResistance = new Int32Array(grid.cellCount); + const volcanicPotential = new Int32Array(grid.cellCount); + const legacyReserveCell = new Uint8Array(grid.cellCount); + const temperature = new Int32Array(grid.cellCount); + const moisture = new Int32Array(grid.cellCount); + const volcanicCenter = Object.freeze({ q: 8, r: -1 }); + + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const coordinate = Object.freeze({ q: grid.q[cell]!, r: grid.r[cell]! }); + const radiusFromCenter = greaterRealmHexDistance(coordinate); + const volcanicDistance = greaterRealmHexDistance(coordinate, volcanicCenter); + let height = radiusFromCenter >= 23 + ? -800 - (radiusFromCenter - 23) * 500 + : radiusFromCenter === 22 ? 300 + : radiusFromCenter === 21 ? 1_200 + : 5_500 - radiusFromCenter * 120; + if (grid.r[cell]! <= -5 && radiusFromCenter < 23) { + height += 4_000 + Math.min(2_000, (-grid.r[cell]! - 5) * 100); + } + if (grid.r[cell]! >= 5 && radiusFromCenter < 21) height += 500; + elevation[cell] = height; + tectonicUplift[cell] = grid.r[cell]! <= -5 ? 4_500 : 1_000; + if (volcanicDistance <= 4) tectonicUplift[cell] = 8_000; + rockResistance[cell] = grid.r[cell]! >= 5 ? 3_500 : grid.q[cell]! >= 0 ? 7_500 : 4_000; + volcanicPotential[cell] = Math.max(500, 10_000 - volcanicDistance * 850); + if (radiusFromCenter <= 2) legacyReserveCell[cell] = 1; + temperature[cell] = grid.r[cell]! <= -5 ? 0 : grid.r[cell]! >= 5 ? 8_000 : 4_500; + moisture[cell] = grid.r[cell]! >= 5 ? -4_000 : grid.r[cell]! <= -5 ? 1_000 : 0; + } + return Object.freeze({ + grid, + elevation, + tectonicUplift, + rockResistance, + volcanicPotential, + legacyReserveCell, + climate: Object.freeze({ temperature, moisture }), + }); +} + +function sum(values: Int32Array): number { + let total = 0; + for (const value of values) total += value; + return total; +} + +describe('Greater Realm geomorphic shaping', () => { + it('makes deterministic, bounded, coherent changes without touching the legacy reserve', () => { + const fixture = syntheticFixture(); + const originalElevation = new Int32Array(fixture.elevation); + const input = { + ...fixture, + candidateSeed: new Uint32Array([0x1020_3040, 0x5060_7080, 0x90a0_b0c0, 0xd0e0_f001]), + } as const; + const first = shapeGreaterRealmGeomorphology(input); + const second = shapeGreaterRealmGeomorphology(input); + + expect(GREATER_REALM_GEOMORPHOLOGY_VERSION).toBe('greater-realm-geomorphology-v1'); + expect(first).toEqual(second); + expect(fixture.elevation).toEqual(originalElevation); + expect(first.metrics.changedCellCount).toBeGreaterThan(0); + expect(first.metrics.maximumAbsoluteCellDelta).toBeGreaterThan(0); + expect(first.metrics.maximumAbsoluteCellDelta).toBeLessThanOrEqual(8_192); + expect(first.metrics.protectedCellCount).toBeGreaterThan(0); + expect(first.metrics.protectedChangedCellCount).toBe(0); + expect(first.metrics.glacial.changedCellCount).toBeGreaterThan(0); + expect(first.metrics.arid.changedCellCount).toBeGreaterThan(0); + expect(first.metrics.coastal.changedCellCount).toBeGreaterThan(0); + expect(first.metrics.endogenicUpliftUnits).toBeGreaterThan(0); + expect(first.metrics.volcanicAnchorCount).toBeGreaterThan(0); + expect(first.metrics.glacial.minimumSystemCellCount).toBeGreaterThanOrEqual(6); + expect(first.metrics.arid.minimumSystemCellCount).toBeGreaterThanOrEqual(8); + expect(first.metrics.coastalClassCount).toBeGreaterThanOrEqual(2); + expect(first.metrics.glacialClimateCompatibilityBasisPoints).toBe(10_000); + expect(first.metrics.aridClimateCompatibilityBasisPoints).toBe(10_000); + expect(first.metrics.volcanicTectonicCompatibilityBasisPoints).toBe(10_000); + expect(first.metrics.coastalProximityCompatibilityBasisPoints).toBe(10_000); + expect(first.metrics.erodedMaterialUnits).toBe( + first.metrics.depositedMaterialUnits + first.metrics.exportedMaterialUnits, + ); + expect(sum(first.totalDelta)).toBe( + first.metrics.endogenicUpliftUnits - first.metrics.exportedMaterialUnits, + ); + expect(first.metrics.ridgeUpliftAlignmentBasisPoints).toBeGreaterThanOrEqual(0); + expect(first.metrics.ridgeUpliftAlignmentBasisPoints).toBeLessThanOrEqual(10_000); + expect(first.metrics.riverValleyAlignmentBasisPoints).toBeGreaterThanOrEqual(0); + expect(first.metrics.riverValleyAlignmentBasisPoints).toBeLessThanOrEqual(10_000); + + for (let cell = 0; cell < fixture.grid.cellCount; cell += 1) { + expect(first.totalDelta[cell]).toBe( + first.glacialDelta[cell]! + first.aridDelta[cell]! + first.volcanicDelta[cell]! + + first.coastalDelta[cell]!, + ); + expect(first.elevation[cell]).toBe(fixture.elevation[cell]! + first.totalDelta[cell]!); + if (fixture.legacyReserveCell[cell] === 1) { + expect(first.totalDelta[cell]).toBe(0); + expect(first.elevation[cell]).toBe(fixture.elevation[cell]); + } + } + }); + + it('removes isolated climate-compatible speckles before shaping', () => { + const fixture = syntheticFixture(); + const temperature = new Int32Array(fixture.grid.cellCount); + temperature.fill(4_000); + const moisture = new Int32Array(fixture.grid.cellCount); + const isolated = fixture.grid.indexOf({ q: -8, r: -2 }); + expect(isolated).toBeGreaterThanOrEqual(0); + temperature[isolated] = 0; + fixture.elevation[isolated] = 10_000; + const result = shapeGreaterRealmGeomorphology({ + ...fixture, + candidateSeed: new Uint32Array([1, 2, 3, 4]), + climate: Object.freeze({ temperature, moisture }), + }); + + expect(result.metrics.glacial.sourceCellCount).toBe(0); + expect(result.metrics.glacial.systemCount).toBe(0); + expect(result.metrics.arid.sourceCellCount).toBe(0); + expect(result.metrics.arid.systemCount).toBe(0); + }); + + it('derives its production climate from independent named integer fields', () => { + const { climate: _fixtureClimate, ...fixture } = syntheticFixture(); + const input = { + ...fixture, + candidateSeed: new Uint32Array([9, 8, 7, 6]), + } as const; + const first = shapeGreaterRealmGeomorphology(input); + const second = shapeGreaterRealmGeomorphology(input); + + expect(first.temperature).toEqual(second.temperature); + expect(first.moisture).toEqual(second.moisture); + expect(first.temperature.length).toBe(fixture.grid.cellCount); + expect(first.moisture.length).toBe(fixture.grid.cellCount); + expect(new Set(first.temperature).size).toBeGreaterThan(1); + expect(new Set(first.moisture).size).toBeGreaterThan(1); + }); +}); diff --git a/tests/greaterRealmLegacyLowlands.test.ts b/tests/greaterRealmLegacyLowlands.test.ts new file mode 100644 index 00000000..f7d71d17 --- /dev/null +++ b/tests/greaterRealmLegacyLowlands.test.ts @@ -0,0 +1,158 @@ +import { createHash } from 'node:crypto'; +import { describe, expect, it } from 'vitest'; + +import { + GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1, + GREATER_REALM_LOWLANDS_REGION_ID, + GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_CLASSIFICATION, + GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1, + LEGACY_LOWLANDS_AXIAL_ROTATION_STEPS, + assertAxialKeyCollisionFree, + assertGreaterRealmLegacyLowlandsPatchLocked, + inverseGlobalToLegacyLowlands, + privateAxialCoordinateKey, + privateRegionAxialCoordinateKey, + rotateAxialCoordinate60, + transformLegacyLowlandsToGlobal, + type LegacyLowlandsAtlasTransform, +} from '../scripts/atlas/greater-realm-legacy-lowlands'; +import { canonicalTierIFoodSiteDigestInput } from '../spacetimedb/src/foodSitePolicy'; +import { canonicalTierIGoldSiteDigestInput } from '../spacetimedb/src/goldSitePolicy'; +import { canonicalTierIStoneSiteDigestInput } from '../spacetimedb/src/stoneSitePolicy'; +import { canonicalTierIWoodSiteDigestInput } from '../spacetimedb/src/woodSitePolicy'; +import { + canonicalGenesisForestAssetCatalogV1DigestInput, + canonicalGenesisForestLayoutV1DigestInput, +} from '../spacetimedb/src/forestLayoutPolicy'; +import { canonicalGenesisWaterRevisionV1DigestInput } from '../spacetimedb/src/waterRevision'; + +function sha256(value: string): string { + return createHash('sha256').update(value).digest('hex'); +} + +describe('Greater Realm private legacy Lowlands bridge input', () => { + it('locks exact deployed catalogs behind a private-only descriptor', () => { + const patch = GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1; + const pins = GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1; + + expect(patch.classification).toBe(GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_CLASSIFICATION); + expect(patch.classification).toBe('private-generator-input-only-never-public-report'); + expect(patch.regionId).toBe('T1_LOWLANDS'); + expect(patch.coordinateSpace).toBe('region-local-axial'); + expect(Object.isFrozen(patch)).toBe(true); + + expect(patch.world.tiles).toHaveLength(10_000); + expect(patch.world.metadata).toHaveLength(10_000); + expect(patch.castleSlots.rows).toHaveLength(100); + expect(patch.water.cells).toHaveLength(3_680); + expect(patch.water.enabledBodies).toHaveLength(13); + expect(patch.water.enabledCells).toHaveLength(3_271); + expect(patch.water.reclaimedLakeCellKeys).toHaveLength(409); + expect(patch.resources.gold.sites).toHaveLength(24); + expect(patch.resources.food.sites).toHaveLength(96); + expect(patch.resources.wood.sites).toHaveLength(96); + expect(patch.resources.stone.sites).toHaveLength(96); + expect(patch.forest.instances).toHaveLength(210); + + expect(pins.worldGenerationDigest).toBe( + '4c111ec1f5e127c7cfd8f42f87c4085f94a4bc46bdacbdc9779866dfdb3edab6', + ); + expect(pins.castleSlotDigest).toBe( + 'd770a084b7c8f59abbc505239a026a98e17bd55d3507c204cd1517858db017ed', + ); + expect(patch.water.layoutDigest).toBe( + 'e6e3601063254a232a80bcc2921e6717b7564f8fce7b276207ffca39c1843dba', + ); + expect(patch.water.activeRevision.revisionDigest).toBe( + '82c18efe71afff1e1dcd4db17b2f6bd1815042d88c7471793bf6cd6d03780aec', + ); + expect(patch.resources.gold.catalogDigest).toBe( + '84ea3eed9ff5cd3eb7e4704aee6fb562ef3f969c490e95d3bf88645abded7d7d', + ); + expect(patch.resources.food.catalogDigest).toBe( + '10756337e27138b536a250ad6bf704c603a8c3946c72a1f0d3a041630610ce72', + ); + expect(patch.resources.wood.catalogDigest).toBe( + '3f0ae99d2052c32b7fec9aec6126e86f53031c13d619fcef12dd42a02b4063d6', + ); + expect(patch.resources.stone.catalogDigest).toBe( + '22c902d5bfb033e7faf3eaa303e89228d9aad0cff712853618dc34b994d28467', + ); + expect(patch.forest.layoutDigest).toBe( + '8a7e7c290e319f9495c3ca2485114659a52f84411e7864a4ed0127ac248b52b2', + ); + expect(patch.forest.assetCatalogDigest).toBe( + 'e544942ee29a61215c2afce360b8a19f943ff703957e84b20973452f1b93cde7', + ); + expect(() => assertGreaterRealmLegacyLowlandsPatchLocked()).not.toThrow(); + }); + + it('recomputes every existing SHA-256 catalog pin from its canonical input', () => { + const pins = GREATER_REALM_LEGACY_LOWLANDS_LOCK_PINS_V1; + expect(sha256(canonicalGenesisWaterRevisionV1DigestInput())).toBe( + pins.waterRevisionDigest, + ); + expect(sha256(canonicalTierIGoldSiteDigestInput())).toBe(pins.goldSiteDigest); + expect(sha256(canonicalTierIFoodSiteDigestInput())).toBe(pins.foodSiteDigest); + expect(sha256(canonicalTierIWoodSiteDigestInput())).toBe(pins.woodSiteDigest); + expect(sha256(canonicalTierIStoneSiteDigestInput())).toBe(pins.stoneSiteDigest); + expect(sha256(canonicalGenesisForestLayoutV1DigestInput())).toBe( + pins.forestLayoutDigest, + ); + expect(sha256(canonicalGenesisForestAssetCatalogV1DigestInput())).toBe( + pins.forestAssetCatalogDigest, + ); + }); + + it('round-trips every Lowlands tile through all six exact rotations', () => { + const tiles = GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1.world.tiles; + for (const rotationSteps of LEGACY_LOWLANDS_AXIAL_ROTATION_STEPS) { + const transform: LegacyLowlandsAtlasTransform = Object.freeze({ + rotationSteps, + globalOffsetQ: 183_271, + globalOffsetR: -97_403, + }); + const globalCoordinates = tiles.map((tile) => ( + transformLegacyLowlandsToGlobal(tile, transform) + )); + expect(assertAxialKeyCollisionFree(globalCoordinates)).toBe(10_000); + for (let index = 0; index < tiles.length; index += 1) { + const local = tiles[index]!; + const roundTrip = inverseGlobalToLegacyLowlands(globalCoordinates[index]!, transform); + expect(privateAxialCoordinateKey(roundTrip)).toBe(local.key); + } + } + }); + + it('keeps all six rotations bijective, cyclic, and region-key collision-free', () => { + const tiles = GREATER_REALM_PRIVATE_LEGACY_LOWLANDS_PATCH_V1.world.tiles; + for (const rotationSteps of LEGACY_LOWLANDS_AXIAL_ROTATION_STEPS) { + const rotated = tiles.map((tile) => rotateAxialCoordinate60(tile, rotationSteps)); + expect(assertAxialKeyCollisionFree(rotated)).toBe(10_000); + expect(new Set(rotated.map((coordinate) => ( + privateRegionAxialCoordinateKey(GREATER_REALM_LOWLANDS_REGION_ID, coordinate) + ))).size).toBe(10_000); + } + + for (const tile of tiles) { + let rotated = { q: tile.q, r: tile.r }; + for (let step = 0; step < 6; step += 1) { + rotated = rotateAxialCoordinate60(rotated, 1); + } + expect(privateAxialCoordinateKey(rotated)).toBe(tile.key); + } + }); + + it('fails closed on ambiguous rotations and axial key collisions', () => { + expect(() => rotateAxialCoordinate60({ q: 0, r: 0 }, 6 as never)).toThrow( + 'GREATER_REALM_LEGACY_LOWLANDS_ROTATION_STEPS', + ); + expect(() => assertAxialKeyCollisionFree([ + { q: 3, r: -2 }, + { q: 3, r: -2 }, + ])).toThrow('GREATER_REALM_LEGACY_LOWLANDS_AXIAL_KEY_COLLISION'); + expect(() => privateRegionAxialCoordinateKey('T1_LOWLANDS:LEAK', { q: 0, r: 0 })).toThrow( + 'GREATER_REALM_LEGACY_LOWLANDS_REGION_ID', + ); + }); +}); diff --git a/tests/greaterRealmOrdinaryYield.test.ts b/tests/greaterRealmOrdinaryYield.test.ts new file mode 100644 index 00000000..da79b11a --- /dev/null +++ b/tests/greaterRealmOrdinaryYield.test.ts @@ -0,0 +1,37 @@ +// @vitest-environment node + +import { createHash } from 'node:crypto'; + +import { describe, expect, it } from 'vitest'; + +import { generateGreaterRealmCandidate } from '../scripts/atlas/greater-realm-candidate-generator'; +import { clearGreaterRealmPrivateCandidateBuffers } from '../scripts/atlas/greater-realm-candidate-package'; + +function ordinaryRoot(label: string): Uint8Array { + return Uint8Array.from(createHash('sha256').update(`${label}\0`, 'utf8').digest()); +} + +describe('Greater Realm ordinary deterministic candidate yield', () => { + it('retains a deterministic eligible candidate after generator hardening', () => { + const cases = [ + ['greater-realm-ordinary-parent-a', 9], + ['greater-realm-yield-regression-b', 0], + ] as const; + + for (const [label, candidateOrdinal] of cases) { + const rootSeed = ordinaryRoot(label); + let candidate: ReturnType | undefined; + try { + candidate = generateGreaterRealmCandidate({ rootSeed, candidateOrdinal }); + expect(candidate.aggregate.eligible, `${label}/${candidateOrdinal}`).toBe(true); + expect(candidate.privateMetrics.eligibilityFailureCodes).toEqual([]); + expect(candidate.gates).toHaveLength(18); + expect(candidate.grid.cellCount).toBeGreaterThanOrEqual(100_000); + expect(candidate.grid.cellCount).toBeLessThanOrEqual(150_000); + } finally { + rootSeed.fill(0); + if (candidate) clearGreaterRealmPrivateCandidateBuffers(candidate); + } + } + }, 30_000); +}); diff --git a/tests/greaterRealmPrivateBoundary.test.ts b/tests/greaterRealmPrivateBoundary.test.ts new file mode 100644 index 00000000..308c3cc8 --- /dev/null +++ b/tests/greaterRealmPrivateBoundary.test.ts @@ -0,0 +1,1132 @@ +import { + chmodSync, + existsSync, + linkSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + renameSync, + rmSync, + statSync, + symlinkSync, + truncateSync, + writeFileSync, +} from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; + +import { + assertGreaterRealmPrivateInvocation, + openGreaterRealmPrivateWorkspace, +} from '../scripts/atlas/greater-realm-private-workspace'; +import { encodeGreaterRealmPrivateSeed } from '../scripts/atlas/greater-realm-private-seed'; +import { + GREATER_REALM_PROOF_KEYS, + type GreaterRealmSanitizedCandidateSource, + type GreaterRealmSanitizedReviewSource, +} from '../scripts/atlas/greater-realm-contracts'; +import { + createGreaterRealmSanitizedReview, + serializeGreaterRealmSanitizedReview, +} from '../scripts/atlas/greater-realm-sanitized-review'; + +// @ts-expect-error Executable ESM verifier exposes named test seams. +import { verifyGreaterRealmPublicBoundary } from '../scripts/atlas/verify-public-boundary.mjs'; + +const temporaryRoots: string[] = []; + +function isolatedPaths() { + const root = mkdtempSync(join(tmpdir(), 'warpkeep-greater-realm-boundary-')); + temporaryRoots.push(root); + const repositoryRoot = join(root, 'repository'); + const workspaceRoot = join(root, 'private-workspace'); + mkdirSync(repositoryRoot, { mode: 0o700 }); + return { repositoryRoot, root, workspaceRoot }; +} + +function scannerRepository() { + const paths = isolatedPaths(); + for (const directory of ['public', 'src', 'dist', 'docs']) { + mkdirSync(join(paths.repositoryRoot, directory)); + } + writeFileSync(join(paths.repositoryRoot, 'public', 'ordinary.txt'), 'ordinary fixture\n'); + return paths; +} + +function sanitizedReviewEvidence(): string { + const candidates = Array.from({ length: 8 }, (_, index) => Object.freeze({ + candidateHandle: `GR-A-${String.fromCharCode(65 + index)}AAAAAAAAAAAAAAA`, + eligible: true, + activeCellCount: 120_000, + landCellCount: 80_000, + waterCellCount: 40_000, + tierCellCounts: Object.freeze({ tierI: 86_400, tierII: 28_800, tierIII: 4_800 }), + regionSizeRanges: Object.freeze({ + tierI: Object.freeze({ minimum: 14_000, maximum: 15_000 }), + tierII: Object.freeze({ minimum: 9_000, maximum: 10_000 }), + tierIII: Object.freeze({ minimum: 4_800, maximum: 4_800 }), + }), + hydrology: Object.freeze({ + majorOceanSeaBodies: 5, + majorRivers: 60, + minorStreams: 180, + lakes: 72, + }), + geology: Object.freeze({ + pseudoTectonicDomains: 10, + mountainSystems: 14, + watersheds: 60, + }), + topography: Object.freeze({ + signedElevationMinimum: -18_400, + signedElevationMaximum: 31_600, + slopeP50: 420, + slopeP95: 3_900, + ridgeCellCount: 9_200, + plateauCellCount: 11_400, + basinCellCount: 8_600, + coastCellCount: 4_800, + }), + biomes: Object.freeze({ + visualClassCount: 14, + minimumPerRegionVisualClassCount: 4, + minimumTierIVisualClassCount: 6, + minimumTierIIVisualClassCount: 5, + tierIIIVisualClassCount: 4, + minimumTierIMajorVisualClassCount: 4, + minimumTierITransitionVisualClassCount: 2, + minimumTierIIMajorVisualClassCount: 5, + tierIIIMajorVisualClassCount: 3, + maximumTierISingleBiomeShareBasisPoints: 3_800, + incompatibleVisualAdjacencyCount: 0, + incompatibleBiomeLandformPairCount: 0, + }), + quality: Object.freeze({ + naturalnessBasisPoints: 8_720, + axialArtifactBasisPoints: 410, + ridgeContinuityBasisPoints: 8_440, + hydrologyCoherenceBasisPoints: 9_010, + }), + gateCount: 18, + castleSlotCount: 600, + proofs: Object.freeze(Object.fromEntries( + GREATER_REALM_PROOF_KEYS.map(key => [key, true]), + )) as GreaterRealmSanitizedCandidateSource['proofs'], + performance: Object.freeze({ + generationMillisecondsRounded: 180_000, + processPeakMemoryMiBRounded: 768, + }), + })) as GreaterRealmSanitizedCandidateSource[]; + const source: GreaterRealmSanitizedReviewSource = Object.freeze({ + generatorVersion: 'greater-realm-v2-natural-continent', + sourceCommit: 'a'.repeat(40), + reviewBatchHandle: 'GR-B-AAAAAAAAAAAAAAAA', + selectionStatus: 'pending', + selectedCandidateHandle: null, + candidates: Object.freeze(candidates), + }); + return serializeGreaterRealmSanitizedReview(createGreaterRealmSanitizedReview(source)); +} + +function runFixtureGit(repositoryRoot: string, arguments_: string[]) { + const nullPath = process.platform === 'win32' ? 'NUL' : '/dev/null'; + const environment: NodeJS.ProcessEnv = { + GIT_CONFIG_GLOBAL: nullPath, + GIT_CONFIG_NOSYSTEM: '1', + GIT_NO_REPLACE_OBJECTS: '1', + GIT_OPTIONAL_LOCKS: '0', + GIT_TERMINAL_PROMPT: '0', + LANG: 'C', + LC_ALL: 'C', + PATH: process.env.PATH, + }; + if (process.platform === 'win32') { + environment.SystemRoot = process.env.SystemRoot ?? 'C:\\Windows'; + environment.WINDIR = process.env.WINDIR ?? 'C:\\Windows'; + } + const result = spawnSync( + 'git', + ['--no-pager', '-c', `core.hooksPath=${nullPath}`, ...arguments_], + { + cwd: repositoryRoot, + encoding: 'utf8', + env: environment, + shell: false, + windowsHide: true, + }, + ); + if (result.error || result.status !== 0 || result.signal !== null) { + throw new Error('GREATER_REALM_TEST_GIT_SETUP_FAILED'); + } +} + +function writeProbeExecutable(directory: string, name: string, markerPath: string) { + mkdirSync(directory, { recursive: true }); + if (process.platform === 'win32') { + const path = join(directory, `${name}.cmd`); + writeFileSync( + path, + `@echo off\r\ntype nul > "${markerPath.replaceAll('"', '""')}"\r\nexit /b 0\r\n`, + ); + return path; + } + const path = join(directory, name); + writeFileSync( + path, + `#!${process.execPath}\nrequire('node:fs').writeFileSync(${JSON.stringify(markerPath)}, 'ran');\n`, + { mode: 0o700 }, + ); + chmodSync(path, 0o700); + return path; +} + +afterEach(() => { + for (const root of temporaryRoots.splice(0)) { + rmSync(root, { force: true, recursive: true }); + } +}); + +describe('Greater Realm private generation workspace', () => { + it('keeps every owner-only package basename ignored by Git', () => { + const ignore = readFileSync(join(import.meta.dirname, '..', '.gitignore'), 'utf8') + .split(/\r?\n/u); + expect(ignore).toEqual(expect.arrayContaining([ + 'seed.bin', + 'batch-seed.bin', + 'manifest.private.json', + 'batch.private.json', + 'selection.private.json', + 'shortlist.private.json', + ])); + }); + + it('writes immutable owner-only files atomically outside the repository', () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + workspace.ensureDirectory('batches/review-one'); + const bytes = Uint8Array.from({ length: 96 }, (_, index) => (index * 17) & 0xff); + + workspace.writeFileAtomic('batches/review-one/checkpoint.wk-private-test', bytes); + + expect(workspace.readFile('batches/review-one/checkpoint.wk-private-test')) + .toEqual(Buffer.from(bytes)); + expect(statSync(workspace.root).mode & 0o077).toBe(0); + expect(statSync(join( + workspace.root, + 'batches/review-one/checkpoint.wk-private-test', + )).mode & 0o077).toBe(0); + expect(workspace.attestTree()).toMatchObject({ fileCount: 1, byteCount: bytes.length }); + expect(() => workspace.writeFileAtomic( + 'batches/review-one/checkpoint.wk-private-test', + bytes, + )).toThrow('GREATER_REALM_PRIVATE_DESTINATION_EXISTS'); + }); + + it('publishes a complete private directory in one transaction', async () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + const batch = 'batches/review-staged'; + const destination = join(workspace.root, batch); + const first = Uint8Array.from([11, 22, 33]); + const second = Uint8Array.from([44, 55, 66]); + + const result = await workspace.withAtomicDirectoryPublish(batch, async staged => { + expect(existsSync(destination)).toBe(false); + staged.writeFileAtomic(`${batch}/first.bin`, first); + staged.writeFileAtomic(`${batch}/nested/second.bin`, second); + expect(staged.readFile(`${batch}/first.bin`)).toEqual(Buffer.from(first)); + expect(existsSync(destination)).toBe(false); + return 'published'; + }); + + expect(result).toBe('published'); + expect(workspace.readFile(`${batch}/first.bin`)).toEqual(Buffer.from(first)); + expect(workspace.readFile(`${batch}/nested/second.bin`)).toEqual(Buffer.from(second)); + const envelopeEntries = readdirSync(destination).sort(); + const payloadName = envelopeEntries.find(name => name.startsWith('.wk-publish-payload-')); + expect(payloadName).toMatch(/^\.wk-publish-payload-/u); + expect(envelopeEntries).toEqual([ + '.wk-publish-commit-v1', + '.wk-publish-envelope-v1', + payloadName, + ].sort()); + expect(readFileSync(join(destination, payloadName!, 'first.bin'))).toEqual(Buffer.from(first)); + workspace.writeFileAtomic(`${batch}/post-publication.bin`, Uint8Array.of(77)); + expect(workspace.readFile(`${batch}/post-publication.bin`)).toEqual(Buffer.from([77])); + expect(readFileSync(join(destination, payloadName!, 'post-publication.bin'))) + .toEqual(Buffer.from([77])); + expect(readdirSync(join(workspace.root, '.pending'))).toEqual([]); + }); + + it('removes every staged file when a private package operation fails', async () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + const batch = 'batches/review-failed'; + + await expect(workspace.withAtomicDirectoryPublish(batch, async staged => { + staged.writeFileAtomic(`${batch}/batch-seed.bin`, Uint8Array.from([1, 2, 3])); + staged.writeFileAtomic(`${batch}/candidate/atlas.bin`, Uint8Array.from([4, 5, 6])); + throw new Error('CONTROLLED_PRIVATE_PACKAGE_FAILURE'); + })).rejects.toThrow('CONTROLLED_PRIVATE_PACKAGE_FAILURE'); + + expect(existsSync(join(workspace.root, batch))).toBe(false); + expect(readdirSync(join(workspace.root, '.pending'))).toEqual([]); + }); + + it('confines a staged workspace to its exact publication subtree', async () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + const batch = 'batches/review-confined'; + + await expect(workspace.withAtomicDirectoryPublish(batch, async staged => { + staged.writeFileAtomic('batches/review-other/escape.bin', Uint8Array.of(1)); + })).rejects.toThrow('GREATER_REALM_PRIVATE_STAGING_SCOPE_INVALID'); + + expect(existsSync(join(workspace.root, batch))).toBe(false); + expect(readdirSync(join(workspace.root, '.pending'))).toEqual([]); + }); + + it('never replaces an existing private publication directory', async () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + const batch = 'batches/review-existing'; + workspace.writeFileAtomic(`${batch}/sentinel.bin`, Uint8Array.of(91)); + let entered = false; + + await expect(workspace.withAtomicDirectoryPublish(batch, async () => { + entered = true; + })).rejects.toThrow('GREATER_REALM_PRIVATE_DESTINATION_EXISTS'); + + expect(entered).toBe(false); + expect(workspace.readFile(`${batch}/sentinel.bin`)).toEqual(Buffer.from([91])); + }); + + it('never replaces an empty destination created while a package is staged', async () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + const batch = 'batches/review-concurrent-destination'; + const destination = join(workspace.root, batch); + + await expect(workspace.withAtomicDirectoryPublish(batch, async staged => { + staged.writeFileAtomic(`${batch}/candidate.bin`, Uint8Array.of(7, 8, 9)); + mkdirSync(destination, { mode: 0o700 }); + })).rejects.toThrow('GREATER_REALM_PRIVATE_DESTINATION_EXISTS'); + + expect(statSync(destination).isDirectory()).toBe(true); + expect(readdirSync(destination)).toEqual([]); + expect(readdirSync(join(workspace.root, '.pending'))).toEqual([]); + expect(readdirSync(join(workspace.root, 'batches'))) + .toEqual(['review-concurrent-destination']); + }); + + it('fails closed when a private publication envelope has no commit marker', () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + const batch = 'batches/review-partial-envelope'; + const destination = join(workspace.root, batch); + mkdirSync(destination, { recursive: true, mode: 0o700 }); + chmodSync(destination, 0o700); + writeFileSync( + join(destination, '.wk-publish-envelope-v1'), + 'warpkeep-greater-realm-private-directory-envelope-v1\n', + { mode: 0o600 }, + ); + mkdirSync(join( + destination, + '.wk-publish-payload-aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + ), { mode: 0o700 }); + + expect(() => workspace.hasFile(`${batch}/candidate.bin`)) + .toThrow('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + expect(() => workspace.attestTree(batch)) + .toThrow('GREATER_REALM_PRIVATE_PUBLICATION_INCOMPLETE'); + }); + + it('rejects a staged symbolic link without following its external target', async () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + const batch = 'batches/review-linked'; + const external = join(paths.root, 'external-private-fixture'); + writeFileSync(external, 'must remain unchanged', { mode: 0o600 }); + + await expect(workspace.withAtomicDirectoryPublish(batch, async staged => { + symlinkSync(external, join(staged.root, 'replacement')); + })).rejects.toThrow('GREATER_REALM_PRIVATE_PATH_SYMLINK'); + + expect(readFileSync(external, 'utf8')).toBe('must remain unchanged'); + expect(existsSync(join(workspace.root, batch))).toBe(false); + expect(readdirSync(join(workspace.root, '.pending'))).toEqual([]); + }); + + it('rejects repository overlap and every symbolic-link workspace boundary', () => { + const paths = isolatedPaths(); + expect(() => openGreaterRealmPrivateWorkspace({ + repositoryRoot: paths.repositoryRoot, + workspaceRoot: join(paths.repositoryRoot, '.ignored-private'), + })).toThrow('GREATER_REALM_PRIVATE_ROOT_REPOSITORY_OVERLAP'); + + const real = join(paths.root, 'real-private'); + const linked = join(paths.root, 'linked-private'); + mkdirSync(real, { mode: 0o700 }); + symlinkSync(real, linked, 'dir'); + expect(() => openGreaterRealmPrivateWorkspace({ + repositoryRoot: paths.repositoryRoot, + workspaceRoot: linked, + })).toThrow('GREATER_REALM_PRIVATE_PATH_SYMLINK'); + }); + + it('rejects permissive or replaced entries during a complete tree attestation', () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + workspace.ensureDirectory('candidate'); + const external = join(paths.root, 'external'); + writeFileSync(external, 'controlled fixture', { mode: 0o600 }); + symlinkSync(external, join(workspace.root, 'candidate', 'replacement')); + + expect(() => workspace.attestTree()).toThrow('GREATER_REALM_PRIVATE_PATH_SYMLINK'); + + rmSync(join(workspace.root, 'candidate', 'replacement')); + writeFileSync(join(workspace.root, 'candidate', 'permissive'), 'fixture', { mode: 0o644 }); + expect(() => workspace.attestTree()).toThrow('GREATER_REALM_PRIVATE_FILE_INVALID'); + }); + + it('fails closed on existing directory permission drift without repairing it', () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + workspace.ensureDirectory('candidate'); + const candidateDirectory = join(workspace.root, 'candidate'); + chmodSync(candidateDirectory, 0o755); + + expect(() => workspace.ensureDirectory('candidate/previews')) + .toThrow('GREATER_REALM_PRIVATE_DIRECTORY_PERMISSIONS'); + expect(statSync(candidateDirectory).mode & 0o777).toBe(0o755); + + chmodSync(candidateDirectory, 0o700); + chmodSync(workspace.root, 0o750); + expect(() => workspace.hasFile('missing.bin')) + .toThrow('GREATER_REALM_PRIVATE_DIRECTORY_PERMISSIONS'); + expect(statSync(workspace.root).mode & 0o777).toBe(0o750); + }); + + it('rejects a workspace root replaced after its identity was pinned', () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + const displaced = `${workspace.root}.displaced`; + renameSync(workspace.root, displaced); + mkdirSync(workspace.root, { mode: 0o700 }); + + expect(() => workspace.ensureDirectory('candidate')) + .toThrow('GREATER_REALM_PRIVATE_DIRECTORY_CHANGED'); + }); + + it('refuses to create a private root through an untrusted writable ancestor', () => { + const paths = isolatedPaths(); + const untrusted = join(paths.root, 'untrusted'); + mkdirSync(untrusted, { mode: 0o700 }); + chmodSync(untrusted, 0o777); + + expect(() => openGreaterRealmPrivateWorkspace({ + repositoryRoot: paths.repositoryRoot, + workspaceRoot: join(untrusted, 'private-workspace'), + })).toThrow('GREATER_REALM_PRIVATE_PATH_UNTRUSTED_ANCESTOR'); + expect(statSync(untrusted).mode & 0o777).toBe(0o777); + }); + + it('rejects hard-linked regular files from every private read path', () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + workspace.ensureDirectory('candidate'); + workspace.writeFileAtomic('candidate/atlas.bin', Uint8Array.from([1, 2, 3, 4])); + linkSync( + join(workspace.root, 'candidate', 'atlas.bin'), + join(workspace.root, 'candidate', 'atlas-copy.bin'), + ); + + expect(() => workspace.hasFile('candidate/atlas.bin')) + .toThrow('GREATER_REALM_PRIVATE_FILE_INVALID'); + expect(() => workspace.readFile('candidate/atlas.bin')) + .toThrow('GREATER_REALM_PRIVATE_FILE_INVALID'); + expect(() => workspace.attestTree('candidate')) + .toThrow('GREATER_REALM_PRIVATE_FILE_INVALID'); + }); + + it('never accepts generation secrets from process arguments or environment', () => { + const generatedSecret = Buffer.from( + Uint8Array.from({ length: 32 }, (_, index) => (index * 29 + 7) & 0xff), + ).toString('hex'); + + expect(() => assertGreaterRealmPrivateInvocation( + [`--seed=${generatedSecret}`], + {}, + )).toThrow('GREATER_REALM_PRIVATE_INVOCATION_REJECTED'); + expect(() => assertGreaterRealmPrivateInvocation( + ['generate-candidates'], + { WARPKEEP_GREATER_REALM_SEED: generatedSecret }, + )).toThrow('GREATER_REALM_PRIVATE_INVOCATION_REJECTED'); + expect(() => assertGreaterRealmPrivateInvocation( + ['generate-candidates'], + { GENERIC_SECRET: generatedSecret }, + )).toThrow('GREATER_REALM_PRIVATE_INVOCATION_REJECTED'); + expect(() => assertGreaterRealmPrivateInvocation( + ['generate-candidates', '--candidate-count', '12'], + { HOME: '/controlled/home' }, + )).not.toThrow(); + }); + + it('serializes operations with an inode-safe exclusive lock', async () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + let entered = false; + + await workspace.withExclusiveLock('locks/generate.lock', async () => { + entered = true; + await expect(workspace.withExclusiveLock('locks/generate.lock', async () => true)) + .rejects.toThrow('GREATER_REALM_PRIVATE_ALREADY_RUNNING'); + }); + + expect(entered).toBe(true); + await expect(workspace.withExclusiveLock('locks/generate.lock', async () => 'released')) + .resolves.toBe('released'); + }); + + it('fails a locked operation if the lock inode is hard-linked', async () => { + const paths = isolatedPaths(); + const workspace = openGreaterRealmPrivateWorkspace(paths); + + await expect(workspace.withExclusiveLock('locks/generate.lock', async () => { + linkSync( + join(workspace.root, 'locks', 'generate.lock'), + join(workspace.root, 'locks', 'copied.lock'), + ); + return 'must-not-resolve'; + })).rejects.toThrow('GREATER_REALM_PRIVATE_FILE_CHANGED'); + expect(workspace.hasFile('locks/generate.lock')).toBe(false); + }); +}); + +describe('Greater Realm public and release boundary', () => { + it('scans the newly built release after Vite emits production output', () => { + const packageJson = JSON.parse(readFileSync( + join(process.cwd(), 'package.json'), + 'utf8', + )) as { scripts?: { build?: unknown } }; + const build = packageJson.scripts?.build; + expect(typeof build).toBe('string'); + const viteIndex = (build as string).indexOf('vite build'); + const boundaryIndex = (build as string) + .indexOf('node scripts/atlas/verify-public-boundary.mjs'); + expect(viteIndex).toBeGreaterThanOrEqual(0); + expect(boundaryIndex).toBeGreaterThan(viteIndex); + }); + + it('accepts an ordinary tracked and deployable tree', () => { + const paths = scannerRepository(); + expect(verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['public/ordinary.txt'], + })).toMatchObject({ trackedPathCount: 1 }); + }); + + it('accepts only an exact sanitized review document in the Greater Realm evidence directory', () => { + const paths = scannerRepository(); + const relativePath = 'docs/evidence/greater-realm/candidate-review-v1.json'; + mkdirSync(join(paths.repositoryRoot, 'docs', 'evidence', 'greater-realm'), { + recursive: true, + }); + writeFileSync(join(paths.repositoryRoot, relativePath), sanitizedReviewEvidence()); + + expect(verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [relativePath], + })).toMatchObject({ trackedPathCount: 1 }); + }); + + it('accepts only the immutable canonical Greater Realm evidence README bytes', () => { + const paths = scannerRepository(); + const relativePath = 'docs/evidence/greater-realm/README.md'; + const evidenceDirectory = join(paths.repositoryRoot, 'docs', 'evidence', 'greater-realm'); + mkdirSync(evidenceDirectory, { recursive: true }); + writeFileSync( + join(paths.repositoryRoot, relativePath), + readFileSync(join(import.meta.dirname, '..', relativePath)), + ); + + expect(verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [relativePath], + })).toMatchObject({ trackedPathCount: 1 }); + }); + + it.each([ + ['coordinate', '\nUnreviewed coordinate: q=42, r=-7.\n'], + ['seed', '\nUnreviewed seed material belongs here.\n'], + ['path', '\nUnreviewed path: /private/owner/review.json\n'], + ])('rejects appended %s-like data in the canonical evidence README', (_label, suffix) => { + const paths = scannerRepository(); + const relativePath = 'docs/evidence/greater-realm/README.md'; + const evidenceDirectory = join(paths.repositoryRoot, 'docs', 'evidence', 'greater-realm'); + mkdirSync(evidenceDirectory, { recursive: true }); + const canonical = readFileSync(join(import.meta.dirname, '..', relativePath)); + writeFileSync( + join(paths.repositoryRoot, relativePath), + Buffer.concat([canonical, Buffer.from(suffix, 'utf8')]), + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [relativePath], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_SANITIZED_REVIEW_INVALID'); + }); + + it.each([ + 'docs/evidence/greater-realm/leak.txt', + 'docs/evidence/greater-realm/leak.JSON', + 'docs/evidence/greater-realm/nested/leak.json', + ])('fails closed on noncanonical Greater Realm evidence path %s', (relativePath) => { + const paths = scannerRepository(); + mkdirSync(dirname(join(paths.repositoryRoot, relativePath)), { recursive: true }); + writeFileSync(join(paths.repositoryRoot, relativePath), '{"firstQ":42,"firstR":-7}\n'); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [relativePath], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_SANITIZED_REVIEW_INVALID'); + }); + + it('rejects duplicate-key shadow payloads even when JSON.parse keeps a valid final value', () => { + const paths = scannerRepository(); + const relativePath = 'docs/evidence/greater-realm/candidate-review-v1.json'; + mkdirSync(join(paths.repositoryRoot, 'docs', 'evidence', 'greater-realm'), { + recursive: true, + }); + const shadowed = sanitizedReviewEvidence().replace( + ' "activeCellCount": 120000,', + ' "activeCellCount": 42,\n "activeCellCount": 120000,', + ); + expect(shadowed).not.toBe(sanitizedReviewEvidence()); + writeFileSync(join(paths.repositoryRoot, relativePath), shadowed); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [relativePath], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_SANITIZED_REVIEW_INVALID'); + }); + + it.each(['firstQ', 'firstR'])( + 'rejects the unreviewed camel-case coordinate field %s from public evidence', + (coordinateKey) => { + const paths = scannerRepository(); + const relativePath = 'docs/evidence/greater-realm/candidate-review-v1.json'; + mkdirSync(join(paths.repositoryRoot, 'docs', 'evidence', 'greater-realm'), { + recursive: true, + }); + const review = JSON.parse(sanitizedReviewEvidence()) as { + candidates: Array>; + }; + review.candidates[0]![coordinateKey] = 42; + writeFileSync(join(paths.repositoryRoot, relativePath), `${JSON.stringify(review)}\n`); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [relativePath], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_SANITIZED_REVIEW_INVALID'); + }, + ); + + it('rejects aggregate-looking fields that are outside the exact review schema', () => { + const paths = scannerRepository(); + const relativePath = 'docs/evidence/greater-realm/candidate-review-v1.json'; + mkdirSync(join(paths.repositoryRoot, 'docs', 'evidence', 'greater-realm'), { + recursive: true, + }); + const review = JSON.parse(sanitizedReviewEvidence()) as { + candidates: Array<{ quality: Record }>; + }; + review.candidates[0]!.quality.terrainVarianceBasisPoints = 500; + writeFileSync(join(paths.repositoryRoot, relativePath), `${JSON.stringify(review)}\n`); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [relativePath], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_SANITIZED_REVIEW_INVALID'); + }); + + it('schema-validates staged Greater Realm evidence blobs independently of the worktree', () => { + const paths = scannerRepository(); + const relativePath = 'docs/evidence/greater-realm/candidate-review-v1.json'; + const absolutePath = join(paths.repositoryRoot, relativePath); + mkdirSync(join(paths.repositoryRoot, 'docs', 'evidence', 'greater-realm'), { + recursive: true, + }); + runFixtureGit(paths.repositoryRoot, ['init', '--quiet']); + const stagedReview = JSON.parse(sanitizedReviewEvidence()) as { + candidates: Array>; + }; + stagedReview.candidates[0]!.firstQ = 7; + writeFileSync(absolutePath, `${JSON.stringify(stagedReview)}\n`); + runFixtureGit(paths.repositoryRoot, ['add', '--', relativePath]); + writeFileSync(absolutePath, sanitizedReviewEvidence()); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + scanRoots: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_SANITIZED_REVIEW_INVALID'); + }); + + it('scans exact staged blob bytes when safer worktree bytes differ', () => { + const paths = scannerRepository(); + runFixtureGit(paths.repositoryRoot, ['init', '--quiet']); + const trackedFile = join(paths.repositoryRoot, 'public', 'ordinary.txt'); + writeFileSync(trackedFile, ['WKGR-PRIVATE-', 'PACKAGE-V1'].join('')); + runFixtureGit(paths.repositoryRoot, ['add', '--', 'public/ordinary.txt']); + writeFileSync(trackedFile, 'ordinary unstaged replacement\n'); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + scanRoots: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + }); + + it('rejects a missing worktree file even when its private blob remains staged', () => { + const paths = scannerRepository(); + runFixtureGit(paths.repositoryRoot, ['init', '--quiet']); + const trackedFile = join(paths.repositoryRoot, 'public', 'ordinary.txt'); + writeFileSync(trackedFile, ['WKGR-PRIVATE-', 'PACKAGE-V1'].join('')); + runFixtureGit(paths.repositoryRoot, ['add', '--', 'public/ordinary.txt']); + rmSync(trackedFile); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + scanRoots: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_SPECIAL_ENTRY'); + }); + + it('ignores hostile PATH, Git environment, and helper-bearing configuration', () => { + const paths = scannerRepository(); + runFixtureGit(paths.repositoryRoot, ['init', '--quiet']); + mkdirSync(join(paths.repositoryRoot, 'review')); + writeFileSync(join(paths.repositoryRoot, 'review', 'seed.bin'), 'tracked fixture\n'); + runFixtureGit(paths.repositoryRoot, ['add', '--', 'public/ordinary.txt', 'review/seed.bin']); + + const hostileBin = join(paths.root, 'hostile-bin'); + const fakeGitMarker = join(paths.root, 'fake-git-ran'); + writeProbeExecutable(hostileBin, 'git', fakeGitMarker); + const helperMarker = join(paths.root, 'git-helper-ran'); + const helper = writeProbeExecutable(paths.root, 'hostile-git-helper', helperMarker); + const hookMarker = join(paths.root, 'git-hook-ran'); + const hooksDirectory = join(paths.root, 'hostile-hooks'); + writeProbeExecutable(hooksDirectory, 'post-index-change', hookMarker); + const decoyWorktree = join(paths.root, 'decoy-worktree'); + mkdirSync(decoyWorktree); + + runFixtureGit(paths.repositoryRoot, ['config', '--local', 'core.fsmonitor', helper]); + runFixtureGit(paths.repositoryRoot, ['config', '--local', 'core.hooksPath', hooksDirectory]); + runFixtureGit(paths.repositoryRoot, ['config', '--local', 'core.worktree', decoyWorktree]); + runFixtureGit(paths.repositoryRoot, ['config', '--local', 'core.bare', 'true']); + + const hostileConfig = join(paths.root, 'hostile.gitconfig'); + runFixtureGit(paths.repositoryRoot, [ + 'config', '--file', hostileConfig, 'core.fsmonitor', helper, + ]); + runFixtureGit(paths.repositoryRoot, [ + 'config', '--file', hostileConfig, 'core.hooksPath', hooksDirectory, + ]); + runFixtureGit(paths.repositoryRoot, [ + 'config', '--file', hostileConfig, 'alias.ls-files', `!${helper}`, + ]); + + const hostileEnvironment: Record = { + GIT_ALTERNATE_OBJECT_DIRECTORIES: join(paths.root, 'hostile-objects'), + GIT_ASKPASS: helper, + GIT_CONFIG_COUNT: '1', + GIT_CONFIG_GLOBAL: hostileConfig, + GIT_CONFIG_KEY_0: 'core.fsmonitor', + GIT_CONFIG_SYSTEM: hostileConfig, + GIT_CONFIG_VALUE_0: helper, + GIT_DIR: join(paths.root, 'missing-git-directory'), + GIT_EXEC_PATH: hostileBin, + GIT_EXTERNAL_DIFF: helper, + GIT_INDEX_FILE: join(paths.root, 'missing-index'), + GIT_OBJECT_DIRECTORY: join(paths.root, 'hostile-objects'), + GIT_PAGER: helper, + GIT_WORK_TREE: decoyWorktree, + HOME: paths.root, + PATH: hostileBin, + XDG_CONFIG_HOME: paths.root, + }; + const originalEnvironment = new Map( + Object.keys(hostileEnvironment).map(key => [key, process.env[key]]), + ); + try { + Object.assign(process.env, hostileEnvironment); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + scanRoots: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_PATH'); + } finally { + for (const [key, value] of originalEnvironment) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + } + + expect(existsSync(fakeGitMarker)).toBe(false); + expect(existsSync(helperMarker)).toBe(false); + expect(existsSync(hookMarker)).toBe(false); + }); + + it.each([ + '.warpkeep-private/candidate/manifest.json', + 'review/seed.bin', + 'review/batch-seed.bin', + 'review/manifest.private.json', + 'review/batch.private.json', + 'review/selection.private.json', + 'review/shortlist.private.json', + 'review/private-preview-hillshade.png', + 'review/candidate.wkgr-private', + 'review/candidate.wkgr-checkpoint', + 'review/candidate.wkgr-atlas', + 'docs/evidence/greater-realm/private-preview.png', + ])('rejects a tracked private generation coordinate: %s', (trackedPath) => { + const paths = scannerRepository(); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [trackedPath], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_PATH'); + }); + + it('rejects private package markers and fields from source or release output', () => { + const paths = scannerRepository(); + writeFileSync( + join(paths.repositoryRoot, 'dist', 'candidate.js'), + ['WKGR-PRIVATE-', 'PACKAGE-V1'].join(''), + ); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + + rmSync(join(paths.repositoryRoot, 'dist', 'candidate.js')); + writeFileSync( + join(paths.repositoryRoot, 'src', 'leak.ts'), + `export const leaked = { ${['seed', 'Material'].join('')}: 'not-a-real-secret' };`, + ); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_FIELD'); + }); + + it('scans complete binary artifacts instead of trusting an ordinary prefix', () => { + const paths = scannerRepository(); + const bytes = Buffer.alloc(96 * 1_024, 0x41); + Buffer.from(['WKGR-PRIVATE-', 'ATLAS-V1'].join(''), 'utf8').copy(bytes, 80 * 1_024); + writeFileSync(join(paths.repositoryRoot, 'public', 'ordinary-looking.bin'), bytes); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + }); + + it('bounds public binary scanning work', () => { + const paths = scannerRepository(); + const oversized = join(paths.repositoryRoot, 'public', 'oversized.bin'); + writeFileSync(oversized, ''); + truncateSync(oversized, 128 * 1_024 * 1_024 + 1); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_BINARY_LIMIT'); + }); + + it('scans tracked binaries outside ordinary deploy roots for private package markers', () => { + const paths = scannerRepository(); + mkdirSync(join(paths.repositoryRoot, 'tools')); + writeFileSync( + join(paths.repositoryRoot, 'tools', 'ordinary-cache.bin'), + Buffer.from(['WKGR-PRIVATE-', 'PACKAGE-V1'].join(''), 'utf8'), + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['tools/ordinary-cache.bin'], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + }); + + it('rejects a renamed private preview by its embedded marker', () => { + const paths = scannerRepository(); + mkdirSync(join(paths.repositoryRoot, 'tools')); + writeFileSync( + join(paths.repositoryRoot, 'tools', 'ordinary-map.png'), + Buffer.from(['synthetic-png-fixture\0WKGR-PRIVATE-', 'PREVIEW-V1'].join(''), 'utf8'), + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['tools/ordinary-map.png'], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + }); + + it('rejects a renamed private seed envelope outside deploy roots', () => { + const paths = scannerRepository(); + mkdirSync(join(paths.repositoryRoot, 'tools')); + const seed = Uint8Array.from({ length: 32 }, (_, index) => index); + const envelope = encodeGreaterRealmPrivateSeed(seed, 'batch'); + try { + writeFileSync(join(paths.repositoryRoot, 'tools', 'ordinary-key.dat'), envelope); + } finally { + envelope.fill(0); + seed.fill(0); + } + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['tools/ordinary-key.dat'], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + }); + + it('scans reviewed generator sources beyond their exact literal allowances', () => { + const paths = scannerRepository(); + const atlasDirectory = join(paths.repositoryRoot, 'scripts', 'atlas'); + mkdirSync(atlasDirectory, { recursive: true }); + const source = readFileSync( + join(import.meta.dirname, '..', 'scripts', 'atlas', 'greater-realm-cli.ts'), + 'utf8', + ); + writeFileSync( + join(atlasDirectory, 'greater-realm-cli.ts'), + `${source}\nconst accidentallyCommittedSeed = "${'ab'.repeat(32)}";\n` + + `const accidentallyCommittedMarker = "${[ + 'WKGR-PRIVATE-', + 'SEED-V1', + ].join('')}";\n`, + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['scripts/atlas/greater-realm-cli.ts'], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + }); + + it('rejects renamed raw seeds in known and extensionless text while allowing a digest', () => { + const paths = scannerRepository(); + mkdirSync(join(paths.repositoryRoot, 'tools')); + for (const name of ['raw-seed.py', 'raw-seed.script']) { + writeFileSync( + join(paths.repositoryRoot, 'tools', name), + `rootSeed = "${'cd'.repeat(32)}"\n`, + ); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [`tools/${name}`], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_FIELD'); + rmSync(join(paths.repositoryRoot, 'tools', name)); + } + + const base64Seed = Buffer.from( + Uint8Array.from({ length: 32 }, (_, index) => (index * 37 + 11) & 0xff), + ).toString('base64url'); + writeFileSync( + join(paths.repositoryRoot, 'tools', 'raw-seed.toml'), + `rootSeed = "${base64Seed}"\n`, + ); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['tools/raw-seed.toml'], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_FIELD'); + rmSync(join(paths.repositoryRoot, 'tools', 'raw-seed.toml')); + + writeFileSync( + join(paths.repositoryRoot, 'tools', 'ordinary-digest.ts'), + `const reportDigest = "${'ef'.repeat(32)}";\n`, + ); + expect(verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['tools/ordinary-digest.ts'], + })).toMatchObject({ trackedPathCount: 1 }); + }); + + it('scans exact extensionless text names and template-style suffixes', () => { + const paths = scannerRepository(); + mkdirSync(join(paths.repositoryRoot, 'tools')); + const cases = [ + ['Dockerfile', `ENV ROOT_SEED="${'9a'.repeat(32)}"\n`], + ['.env.example', `privateSeed="${'b7'.repeat(32)}"\n`], + ] as const; + for (const [name, source] of cases) { + const relativePath = `tools/${name}`; + writeFileSync(join(paths.repositoryRoot, relativePath), source); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [relativePath], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_FIELD'); + } + }); + + it('treats production source maps as text and rejects embedded seed material', () => { + const paths = scannerRepository(); + writeFileSync( + join(paths.repositoryRoot, 'dist', 'application.js.map'), + JSON.stringify({ + version: 3, + sources: ['private-generator.ts'], + sourcesContent: [`const candidateSeed = "${'12'.repeat(32)}";`], + }), + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_FIELD'); + }); + + it('normalizes nested source-map escapes before checking private identifiers', () => { + const paths = scannerRepository(); + writeFileSync( + join(paths.repositoryRoot, 'dist', 'escaped-application.js.map'), + JSON.stringify({ + version: 3, + sources: ['private-generator.ts'], + sourcesContent: [ + `const root\\u0053eed = "${'21'.repeat(16)}" + "${'21'.repeat(16)}";`, + ], + }), + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_FIELD'); + }); + + it('normalizes common concatenation and ASCII-escape seed obfuscation', () => { + const paths = scannerRepository(); + mkdirSync(join(paths.repositoryRoot, 'tools')); + const cases = [ + `const rootSeed = "${'34'.repeat(16)}" + "${'34'.repeat(16)}";\n`, + `const root\\u0053eed = "${'56'.repeat(32)}";\n`, + ]; + for (const [index, source] of cases.entries()) { + const relativePath = `tools/escaped-seed-${index}.js`; + writeFileSync(join(paths.repositoryRoot, relativePath), source); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [relativePath], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_FIELD'); + } + }); + + it('rejects an inline data source map before its payload can conceal private source', () => { + const paths = scannerRepository(); + const directive = ['sourceMappingURL', 'data:application/json;base64'].join('='); + const encoded = Buffer.from( + `const rootSeed = "${'78'.repeat(32)}";`, + 'utf8', + ).toString('base64'); + writeFileSync( + join(paths.repositoryRoot, 'dist', 'inline-map.js'), + `//# ${directive},${encoded}\n`, + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_FIELD'); + }); + + it('rejects renamed private JSON outside the ordinary deploy roots', () => { + const paths = scannerRepository(); + mkdirSync(join(paths.repositoryRoot, 'tools')); + const privateBatchKind = ['warpkeep.greater-realm.private-', 'batch.v1'].join(''); + writeFileSync( + join(paths.repositoryRoot, 'tools', 'ordinary-review.json'), + `${JSON.stringify({ kind: privateBatchKind, candidates: [] })}\n`, + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['tools/ordinary-review.json'], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + }); + + it('rejects a renamed private owner shortlist outside deploy roots', () => { + const paths = scannerRepository(); + mkdirSync(join(paths.repositoryRoot, 'tools')); + const privateShortlistKind = [ + 'warpkeep.greater-realm.private-owner-', + 'shortlist.v1', + ].join(''); + writeFileSync( + join(paths.repositoryRoot, 'tools', 'ordinary-comparison.json'), + `${JSON.stringify({ kind: privateShortlistKind, candidateHandles: [] })}\n`, + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['tools/ordinary-comparison.json'], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + }); + + it.each([ + ['chunk-', 'manifest.v1'], + ['topography-', 'patch.v1'], + ])('rejects a renamed private %s manifest outside deploy roots', (prefix, suffix) => { + const paths = scannerRepository(); + mkdirSync(join(paths.repositoryRoot, 'tools')); + writeFileSync( + join(paths.repositoryRoot, 'tools', 'ordinary-manifest.json'), + `${JSON.stringify({ + kind: ['warpkeep.greater-realm.private-', prefix, suffix].join(''), + })}\n`, + ); + + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: ['tools/ordinary-manifest.json'], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_MARKER'); + }); + + it('rejects owner preview images and symlinks beneath public scan roots', () => { + const paths = scannerRepository(); + const evidence = join(paths.repositoryRoot, 'docs', 'evidence', 'greater-realm'); + mkdirSync(evidence, { recursive: true }); + writeFileSync(join(evidence, 'candidate.webp'), Uint8Array.from([1, 2, 3])); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_PATH'); + + rmSync(join(paths.repositoryRoot, 'docs'), { recursive: true }); + mkdirSync(join(paths.repositoryRoot, 'docs')); + const external = join(paths.root, 'external.txt'); + writeFileSync(external, 'fixture'); + symlinkSync(external, join(paths.repositoryRoot, 'public', 'linked.txt')); + expect(() => verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + })).toThrow('GREATER_REALM_PUBLIC_BOUNDARY_SPECIAL_ENTRY'); + }); + + it('does not reveal local paths or candidate values in diagnostics', () => { + const paths = scannerRepository(); + const privatePath = join(paths.repositoryRoot, 'public', 'seed.bin'); + writeFileSync(privatePath, 'controlled fixture'); + try { + verifyGreaterRealmPublicBoundary({ + repositoryRoot: paths.repositoryRoot, + trackedPaths: [], + }); + throw new Error('expected boundary failure'); + } catch (error) { + expect(String(error)).toBe('GreaterRealmPublicBoundaryError: GREATER_REALM_PUBLIC_BOUNDARY_PRIVATE_PATH'); + expect(String(error)).not.toContain(paths.root); + expect(String(error)).not.toContain(readFileSync(privatePath, 'utf8')); + } + }); +}); diff --git a/tests/greaterRealmPrivateSeed.test.ts b/tests/greaterRealmPrivateSeed.test.ts new file mode 100644 index 00000000..9a333fc0 --- /dev/null +++ b/tests/greaterRealmPrivateSeed.test.ts @@ -0,0 +1,54 @@ +// @vitest-environment node + +import { describe, expect, it } from 'vitest'; + +import { + GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES, + GREATER_REALM_PRIVATE_SEED_MARKER, + decodeGreaterRealmPrivateSeed, + encodeGreaterRealmPrivateSeed, +} from '../scripts/atlas/greater-realm-private-seed'; + +describe('Greater Realm private seed envelope', () => { + it('round-trips exactly while carrying a scan-visible kind tag', () => { + const seed = Uint8Array.from({ length: 32 }, (_, index) => index + 1); + const envelope = encodeGreaterRealmPrivateSeed(seed, 'batch'); + let decoded: Buffer | undefined; + try { + expect(envelope).toHaveLength(GREATER_REALM_PRIVATE_SEED_ENVELOPE_BYTES); + expect(envelope.includes(Buffer.from(GREATER_REALM_PRIVATE_SEED_MARKER, 'ascii'))) + .toBe(true); + decoded = decodeGreaterRealmPrivateSeed(envelope, 'batch'); + expect(decoded).toEqual(Buffer.from(seed)); + expect(() => decodeGreaterRealmPrivateSeed(envelope, 'candidate')) + .toThrow('GREATER_REALM_PRIVATE_SEED_ENVELOPE_INVALID'); + } finally { + decoded?.fill(0); + envelope.fill(0); + seed.fill(0); + } + }); + + it('rejects changed markers and non-exact lengths', () => { + const seed = new Uint8Array(32); + const envelope = encodeGreaterRealmPrivateSeed(seed, 'candidate'); + const changed = Buffer.from(envelope); + const extended = Buffer.concat([envelope, Buffer.from([0])]); + changed[0] = changed[0]! ^ 0xff; + try { + expect(() => decodeGreaterRealmPrivateSeed(changed, 'candidate')) + .toThrow('GREATER_REALM_PRIVATE_SEED_ENVELOPE_INVALID'); + expect(() => decodeGreaterRealmPrivateSeed(extended, 'candidate')) + .toThrow('GREATER_REALM_PRIVATE_SEED_ENVELOPE_INVALID'); + expect(() => encodeGreaterRealmPrivateSeed(seed, '__proto__' as never)) + .toThrow('GREATER_REALM_PRIVATE_SEED_ENVELOPE_INVALID'); + expect(() => encodeGreaterRealmPrivateSeed(seed.subarray(0, 31), 'candidate')) + .toThrow('GREATER_REALM_PRIVATE_SEED_ENVELOPE_INVALID'); + } finally { + changed.fill(0); + extended.fill(0); + envelope.fill(0); + seed.fill(0); + } + }); +}); diff --git a/tests/greaterRealmSanitizedReview.test.ts b/tests/greaterRealmSanitizedReview.test.ts new file mode 100644 index 00000000..8a5fd107 --- /dev/null +++ b/tests/greaterRealmSanitizedReview.test.ts @@ -0,0 +1,300 @@ +import { describe, expect, it } from 'vitest'; + +import { + GREATER_REALM_CANDIDATE_HANDLE_PATTERN, + GREATER_REALM_PROOF_KEYS, + GREATER_REALM_REVIEW_BATCH_HANDLE_PATTERN, + createGreaterRealmCandidateHandle, + createGreaterRealmReviewBatchHandle, + type GreaterRealmSanitizedCandidateSource, + type GreaterRealmSanitizedReviewSource, +} from '../scripts/atlas/greater-realm-contracts'; +import { + createGreaterRealmSanitizedReview, + greaterRealmSanitizedReviewDigest, + parseGreaterRealmSanitizedReview, + serializeGreaterRealmSanitizedReview, +} from '../scripts/atlas/greater-realm-sanitized-review'; + +function candidate(handle = createGreaterRealmCandidateHandle()): GreaterRealmSanitizedCandidateSource { + return Object.freeze({ + candidateHandle: handle, + eligible: true, + activeCellCount: 120_000, + landCellCount: 80_000, + waterCellCount: 40_000, + tierCellCounts: Object.freeze({ tierI: 86_400, tierII: 28_800, tierIII: 4_800 }), + regionSizeRanges: Object.freeze({ + tierI: Object.freeze({ minimum: 14_000, maximum: 15_000 }), + tierII: Object.freeze({ minimum: 9_000, maximum: 10_000 }), + tierIII: Object.freeze({ minimum: 4_800, maximum: 4_800 }), + }), + hydrology: Object.freeze({ + majorOceanSeaBodies: 5, + majorRivers: 60, + minorStreams: 180, + lakes: 72, + }), + geology: Object.freeze({ + pseudoTectonicDomains: 10, + mountainSystems: 14, + watersheds: 60, + }), + topography: Object.freeze({ + signedElevationMinimum: -18_400, + signedElevationMaximum: 31_600, + slopeP50: 420, + slopeP95: 3_900, + ridgeCellCount: 9_200, + plateauCellCount: 11_400, + basinCellCount: 8_600, + coastCellCount: 4_800, + }), + biomes: Object.freeze({ + visualClassCount: 14, + minimumPerRegionVisualClassCount: 4, + minimumTierIVisualClassCount: 6, + minimumTierIIVisualClassCount: 5, + tierIIIVisualClassCount: 4, + minimumTierIMajorVisualClassCount: 4, + minimumTierITransitionVisualClassCount: 2, + minimumTierIIMajorVisualClassCount: 5, + tierIIIMajorVisualClassCount: 3, + maximumTierISingleBiomeShareBasisPoints: 3_800, + incompatibleVisualAdjacencyCount: 0, + incompatibleBiomeLandformPairCount: 0, + }), + quality: Object.freeze({ + naturalnessBasisPoints: 8_720, + axialArtifactBasisPoints: 410, + ridgeContinuityBasisPoints: 8_440, + hydrologyCoherenceBasisPoints: 9_010, + }), + gateCount: 18, + castleSlotCount: 600, + proofs: Object.freeze(Object.fromEntries( + GREATER_REALM_PROOF_KEYS.map(key => [key, true]), + )) as GreaterRealmSanitizedCandidateSource['proofs'], + performance: Object.freeze({ + generationMillisecondsRounded: 180_000, + processPeakMemoryMiBRounded: 768, + }), + }); +} + +function source(): GreaterRealmSanitizedReviewSource { + return Object.freeze({ + generatorVersion: 'greater-realm-v2-natural-continent', + sourceCommit: 'a'.repeat(40), + reviewBatchHandle: createGreaterRealmReviewBatchHandle(), + selectionStatus: 'pending', + selectedCandidateHandle: null, + candidates: Object.freeze(Array.from({ length: 8 }, () => candidate())), + }); +} + +function mutableClone(value: T): T { + return structuredClone(value); +} + +describe('Greater Realm sanitized candidate review', () => { + it('constructs only aggregate public evidence with opaque independent handles', () => { + const review = createGreaterRealmSanitizedReview(source()); + + expect(review.reviewBatchHandle).toMatch(GREATER_REALM_REVIEW_BATCH_HANDLE_PATTERN); + expect(review.candidates).toHaveLength(8); + expect(new Set(review.candidates.map(entry => entry.candidateHandle)).size).toBe(8); + expect(review.candidates.every(entry => ( + GREATER_REALM_CANDIDATE_HANDLE_PATTERN.test(entry.candidateHandle) + && entry.insideApprovedRange + && entry.landBasisPoints + entry.waterBasisPoints === 10_000 + && entry.tierBasisPoints.tierI + + entry.tierBasisPoints.tierII + + entry.tierBasisPoints.tierIII === 10_000 + ))).toBe(true); + expect(JSON.stringify(review)).not.toMatch( + /(?:coordinate|seed|transform|chunk|layoutDigest|stageDigest|packageDigest|preview|imagePath)/i, + ); + expect(parseGreaterRealmSanitizedReview(JSON.parse( + serializeGreaterRealmSanitizedReview(review), + ))).toEqual(review); + }); + + it('sorts candidates canonically and binds every public field into the digest', () => { + const input = source(); + const reversed = { ...input, candidates: [...input.candidates].reverse() }; + const review = createGreaterRealmSanitizedReview(reversed); + const { reportDigest: _digest, ...body } = review; + + expect(review.candidates.map(entry => entry.candidateHandle)).toEqual( + [...review.candidates.map(entry => entry.candidateHandle)].sort(), + ); + expect(review.reportDigest).toBe(greaterRealmSanitizedReviewDigest(body)); + + const tampered = mutableClone(review) as unknown as { + candidates: Array<{ quality: { naturalnessBasisPoints: number } }>; + }; + tampered.candidates[0]!.quality.naturalnessBasisPoints -= 1; + expect(() => parseGreaterRealmSanitizedReview(tampered)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_DIGEST_MISMATCH', + ); + }); + + it('rejects unknown keys recursively instead of redacting them', () => { + const input = mutableClone(source()) as GreaterRealmSanitizedReviewSource & { + candidates: Array; + }; + input.candidates[0]!.hydrology.tributaryMap = 'aggregate-looking-but-unreviewed'; + + expect(() => createGreaterRealmSanitizedReview(input)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_INVALID', + ); + }); + + it('labels maxRSS honestly as a process-lifetime peak', () => { + const input = mutableClone(source()) as unknown as { + candidates: Array<{ + performance: Record; + }>; + }; + const performance = input.candidates[0]!.performance; + performance.peakMemoryMiBRounded = performance.processPeakMemoryMiBRounded; + delete performance.processPeakMemoryMiBRounded; + + expect(() => createGreaterRealmSanitizedReview( + input as unknown as GreaterRealmSanitizedReviewSource, + )).toThrow('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + }); + + it('strictly validates aggregate topography and biome evidence', () => { + const unknownMetric = mutableClone(source()) as GreaterRealmSanitizedReviewSource & { + candidates: Array; + }; + unknownMetric.candidates[0]!.topography.elevationHistogram = [1, 2, 3]; + expect(() => createGreaterRealmSanitizedReview(unknownMetric)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_INVALID', + ); + + const invalidSlope = mutableClone(source()) as unknown as { + candidates: Array<{ topography: { slopeP50: number; slopeP95: number } }>; + }; + invalidSlope.candidates[0]!.topography.slopeP50 = 4_000; + invalidSlope.candidates[0]!.topography.slopeP95 = 3_900; + expect(() => createGreaterRealmSanitizedReview(invalidSlope)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_INVALID', + ); + + const invalidElevation = mutableClone(source()) as unknown as { + candidates: Array<{ topography: { signedElevationMinimum: number } }>; + }; + invalidElevation.candidates[0]!.topography.signedElevationMinimum = -1_000_001; + expect(() => createGreaterRealmSanitizedReview(invalidElevation)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_INVALID', + ); + + const biomeMonoculture = mutableClone(source()) as unknown as { + candidates: Array<{ + biomes: { maximumTierISingleBiomeShareBasisPoints: number }; + }>; + }; + biomeMonoculture.candidates[0]!.biomes.maximumTierISingleBiomeShareBasisPoints = 5_501; + expect(() => createGreaterRealmSanitizedReview(biomeMonoculture)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_INVALID', + ); + + const incompatibleTransition = mutableClone(source()) as unknown as { + candidates: Array<{ biomes: { incompatibleVisualAdjacencyCount: number } }>; + }; + incompatibleTransition.candidates[0]!.biomes.incompatibleVisualAdjacencyCount = 1; + expect(() => createGreaterRealmSanitizedReview(incompatibleTransition)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_INVALID', + ); + + const incompatiblePairing = mutableClone(source()) as unknown as { + candidates: Array<{ biomes: { incompatibleBiomeLandformPairCount: number } }>; + }; + incompatiblePairing.candidates[0]!.biomes.incompatibleBiomeLandformPairCount = 1; + expect(() => createGreaterRealmSanitizedReview(incompatiblePairing)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_INVALID', + ); + }); + + it('keeps recursive privacy rejection active within new aggregate objects', () => { + const input = mutableClone(source()) as unknown as { + candidates: Array<{ topography: Record }>; + }; + input.candidates[0]!.topography.coordinateSamples = [[1, 2]]; + expect(() => createGreaterRealmSanitizedReview(input)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_PRIVATE_MATERIAL', + ); + }); + + it('rejects accessors without executing them and rejects sparse candidate arrays', () => { + const accessorInput = mutableClone(source()); + let getterExecuted = false; + Object.defineProperty(accessorInput.candidates[0]!.quality, 'naturalnessBasisPoints', { + enumerable: true, + get: () => { + getterExecuted = true; + return 8_720; + }, + }); + expect(() => createGreaterRealmSanitizedReview(accessorInput)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_PRIVATE_MATERIAL', + ); + expect(getterExecuted).toBe(false); + + const sparseInput = mutableClone(source()) as unknown as { + candidates: Array; + }; + delete sparseInput.candidates[0]; + expect(() => createGreaterRealmSanitizedReview(sparseInput)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_INVALID', + ); + }); + + it.each([ + ['coordinate', Object.freeze({ q: 42, r: -7 })], + ['seedMaterial', Uint8Array.from({ length: 32 }, (_, index) => index)], + ['transform', Object.freeze({ rotation: 2, translation: [4, 8] })], + ['chunkKeys', Object.freeze(['hidden-1'])], + ['layoutDigest', 'f'.repeat(64)], + ['stageDigest', 'e'.repeat(64)], + ['packageDigest', 'd'.repeat(64)], + ['imagePath', 'private/maps/candidate.png'], + ])('rejects the private field %s before public construction', (key, privateValue) => { + const input = mutableClone(source()) as unknown as Record; + input[key] = privateValue; + expect(() => createGreaterRealmSanitizedReview(input)).toThrow( + 'GREATER_REALM_SANITIZED_REVIEW_PRIVATE_MATERIAL', + ); + }); + + it('requires at least eight eligible candidates and an eligible exact selection', () => { + const input = source(); + expect(() => createGreaterRealmSanitizedReview({ + ...input, + candidates: input.candidates.slice(0, 7), + })).toThrow('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + + const selectedHandle = input.candidates[0]!.candidateHandle; + const selected = createGreaterRealmSanitizedReview({ + ...input, + selectionStatus: 'selected', + selectedCandidateHandle: selectedHandle, + }); + expect(selected.selectedCandidateHandle).toBe(selectedHandle); + + expect(() => createGreaterRealmSanitizedReview({ + ...input, + selectionStatus: 'selected', + selectedCandidateHandle: createGreaterRealmCandidateHandle(), + })).toThrow('GREATER_REALM_SANITIZED_REVIEW_INVALID'); + }); +}); diff --git a/tests/greaterRealmTerrain.test.ts b/tests/greaterRealmTerrain.test.ts new file mode 100644 index 00000000..836fc9da --- /dev/null +++ b/tests/greaterRealmTerrain.test.ts @@ -0,0 +1,258 @@ +import { Buffer } from 'node:buffer'; + +import { describe, expect, it, vi } from 'vitest'; + +import { + accumulateGreaterRealmSingleFlow, + assertGreaterRealmSingleFlow, + createGreaterRealmMultiscaleIntegerField, + digestGreaterRealmTerrainStage, + erodeGreaterRealmThermally, + GREATER_REALM_AXIAL_DIRECTIONS, + greaterRealmAxialNeighbors, + greaterRealmCounterRandomU32, + greaterRealmHexDistance, + greaterRealmTerrainChannelId, + indexGreaterRealmAxialGrid, + priorityFloodGreaterRealmHexGrid, + routeGreaterRealmSingleFlow, + type AxialCoordinate, +} from '../scripts/atlas/greater-realm-terrain'; + +function axialDistance(coordinate: AxialCoordinate): number { + return greaterRealmHexDistance(coordinate); +} + +function hexDisc(radius: number): AxialCoordinate[] { + const coordinates: AxialCoordinate[] = []; + for (let q = -radius; q <= radius; q += 1) { + for (let r = -radius; r <= radius; r += 1) { + const coordinate = { q, r }; + if (axialDistance(coordinate) <= radius) coordinates.push(coordinate); + } + } + return coordinates; +} + +function valuesByCoordinate( + grid: ReturnType, + values: Readonly, +): Readonly> { + return Object.fromEntries(Array.from({ length: grid.cellCount }, (_, index) => [ + `${grid.q[index]},${grid.r[index]}`, + values[index], + ])); +} + +describe('Greater Realm deterministic terrain core', () => { + it('canonically indexes axial cells and stores the six established neighbor directions', () => { + const coordinates = hexDisc(1); + const grid = indexGreaterRealmAxialGrid([...coordinates].reverse()); + const center = grid.indexOf({ q: 0, r: 0 }); + + expect(grid.cellCount).toBe(7); + expect(GREATER_REALM_AXIAL_DIRECTIONS).toEqual([ + { q: 1, r: 0 }, + { q: 1, r: -1 }, + { q: 0, r: -1 }, + { q: -1, r: 0 }, + { q: -1, r: 1 }, + { q: 0, r: 1 }, + ]); + expect(greaterRealmAxialNeighbors({ q: 0, r: 0 })).toEqual( + GREATER_REALM_AXIAL_DIRECTIONS, + ); + expect(greaterRealmHexDistance({ q: -2, r: 1 }, { q: 1, r: -1 })).toBe(3); + expect(Array.from(grid.neighbors.slice(center * 6, center * 6 + 6))) + .toEqual(GREATER_REALM_AXIAL_DIRECTIONS.map((coordinate) => grid.indexOf(coordinate))); + }); + + it('can retire the private coordinate lookup without mutating canonical arrays', () => { + const grid = indexGreaterRealmAxialGrid(hexDisc(1)); + const q = new Int32Array(grid.q); + const r = new Int32Array(grid.r); + const neighbors = new Int32Array(grid.neighbors); + + expect(grid.indexOf({ q: 0, r: 0 })).toBeGreaterThanOrEqual(0); + expect(grid.clearIndex).toBeTypeOf('function'); + grid.clearIndex?.(); + + expect(grid.indexOf({ q: 0, r: 0 })).toBe(-1); + expect(grid.q).toEqual(q); + expect(grid.r).toEqual(r); + expect(grid.neighbors).toEqual(neighbors); + }); + + it('uses counter-addressed randomness without Math.random or traversal-order dependence', () => { + const randomSpy = vi.spyOn(Math, 'random').mockImplementation(() => { + throw new Error('MATH_RANDOM_MUST_NOT_BE_USED'); + }); + try { + const coordinates = hexDisc(3); + const forwardGrid = indexGreaterRealmAxialGrid(coordinates); + const reverseGrid = indexGreaterRealmAxialGrid([...coordinates].reverse()); + const layers = [ + { channel: 'macro-uplift', amplitude: 12_000, smoothingPasses: 8 }, + { channel: 'meso-relief', amplitude: 3_000, smoothingPasses: 2, selfWeight: 3 }, + { channel: 'local-relief', amplitude: 400, smoothingPasses: 0 }, + ] as const; + const forward = createGreaterRealmMultiscaleIntegerField(forwardGrid, 0x1234_5678, layers); + const reverse = createGreaterRealmMultiscaleIntegerField(reverseGrid, 0x1234_5678, layers); + + expect(valuesByCoordinate(forwardGrid, forward)).toEqual(valuesByCoordinate(reverseGrid, reverse)); + expect(digestGreaterRealmTerrainStage('relief', forwardGrid, { elevation: forward })) + .toBe(digestGreaterRealmTerrainStage('relief', reverseGrid, { elevation: reverse })); + + const channel = greaterRealmTerrainChannelId('order-proof'); + const addresses = coordinates.map((coordinate, index) => ({ ...coordinate, index })); + const first = new Map(addresses.map((address) => [ + `${address.q},${address.r},${address.index}`, + greaterRealmCounterRandomU32(7, channel, address.q, address.r, address.index), + ])); + const second = new Map([...addresses].reverse().map((address) => [ + `${address.q},${address.r},${address.index}`, + greaterRealmCounterRandomU32(7, channel, address.q, address.r, address.index), + ])); + expect(second).toEqual(first); + expect(greaterRealmCounterRandomU32( + [7, 0x1111_1111, 0x2222_2222, 0x3333_3333], + channel, + 4, + -2, + )).not.toBe(greaterRealmCounterRandomU32([7, 0, 0, 0], channel, 4, -2)); + expect(greaterRealmCounterRandomU32( + [0x0123_4567, 0x89ab_cdef, 0xfedc_ba98, 0x7654_3210], + 0xdeca_fbad, + -42, + 77, + 9, + )).toBe(4_088_950_175); + expect(randomSpy).not.toHaveBeenCalled(); + } finally { + randomSpy.mockRestore(); + } + }); + + it('fills an enclosed depression and gives every cell a flat-safe path to an outlet', () => { + const grid = indexGreaterRealmAxialGrid(hexDisc(2)); + const elevation = new Int32Array(grid.cellCount); + for (let index = 0; index < grid.cellCount; index += 1) { + const distance = axialDistance({ q: grid.q[index]!, r: grid.r[index]! }); + elevation[index] = distance === 0 ? -8 : distance === 1 ? 12 : 0; + } + + const flood = priorityFloodGreaterRealmHexGrid(grid, elevation); + const routing = routeGreaterRealmSingleFlow(grid, flood); + const center = grid.indexOf({ q: 0, r: 0 }); + + expect(elevation[center]).toBe(-8); + expect(flood.filledElevation[center]).toBe(12); + assertGreaterRealmSingleFlow(grid, flood.filledElevation, routing); + + for (let start = 0; start < grid.cellCount; start += 1) { + let cell = start; + let steps = 0; + while (routing.receiver[cell]! >= 0) { + const receiver = routing.receiver[cell]!; + expect(flood.filledElevation[receiver]).toBeLessThanOrEqual(flood.filledElevation[cell]); + expect(routing.rank[receiver]).toBeLessThan(routing.rank[cell]); + cell = receiver; + steps += 1; + expect(steps).toBeLessThan(grid.cellCount); + } + expect(routing.outlets[cell]).toBe(1); + } + }); + + it('builds an acyclic accumulation graph whose outlet totals conserve all runoff', () => { + const grid = indexGreaterRealmAxialGrid(hexDisc(3)); + const elevation = createGreaterRealmMultiscaleIntegerField(grid, 99, [ + { channel: 'drainage-test', amplitude: 10_000, smoothingPasses: 3 }, + ]); + const flood = priorityFloodGreaterRealmHexGrid(grid, elevation); + const routing = routeGreaterRealmSingleFlow(grid, flood); + const local = new Uint32Array(grid.cellCount); + local.fill(7); + const accumulation = accumulateGreaterRealmSingleFlow( + grid, + flood.filledElevation, + routing, + local, + ); + + let outletTotal = 0n; + for (let index = 0; index < grid.cellCount; index += 1) { + if (routing.outlets[index] === 1) outletTotal += accumulation[index]!; + const receiver = routing.receiver[index]!; + if (receiver < 0) continue; + expect(flood.filledElevation[receiver]).toBeLessThanOrEqual(flood.filledElevation[index]); + expect(routing.rank[receiver]).toBeLessThan(routing.rank[index]); + } + expect(outletTotal).toBe(BigInt(grid.cellCount * 7)); + }); + + it('applies thermal erosion synchronously without mutating input or changing total material', () => { + const grid = indexGreaterRealmAxialGrid(hexDisc(2)); + const elevation = new Int32Array(grid.cellCount); + const center = grid.indexOf({ q: 0, r: 0 }); + elevation[center] = 12_000; + const original = new Int32Array(elevation); + + const result = erodeGreaterRealmThermally(grid, elevation, { + iterations: 12, + talus: 100, + transferNumerator: 1, + transferDenominator: 16, + }); + + expect(elevation).toEqual(original); + expect(result.elevation).not.toEqual(original); + expect(result.elevation[center]).toBeLessThan(original[center]); + expect(result.movedMaterial).toBeGreaterThan(0n); + expect(result.initialMass).toBe(12_000n); + expect(result.finalMass).toBe(result.initialMass); + expect(Array.from(result.elevation).reduce((sum, value) => sum + BigInt(value), 0n)) + .toBe(result.initialMass); + }); + + it('makes stage evidence sensitive to field values and integer array types', () => { + const grid = indexGreaterRealmAxialGrid(hexDisc(1)); + const elevation = new Int32Array(grid.cellCount); + elevation.fill(5); + const same = digestGreaterRealmTerrainStage('bedrock', grid, { elevation }); + const changed = new Int32Array(elevation); + changed[0] += 1; + + expect(same).toMatch(/^[0-9a-f]{64}$/); + expect(digestGreaterRealmTerrainStage('bedrock', grid, { elevation })).toBe(same); + expect(digestGreaterRealmTerrainStage('bedrock', grid, { elevation: changed })).not.toBe(same); + expect(digestGreaterRealmTerrainStage('bedrock', grid, { + elevation: new Uint32Array(elevation), + })).not.toBe(same); + expect(digestGreaterRealmTerrainStage('bedrock', grid, { + zeta: new Uint8Array(grid.cellCount), + alpha: elevation, + })).toBe(digestGreaterRealmTerrainStage('bedrock', grid, { + alpha: elevation, + zeta: new Uint8Array(grid.cellCount), + })); + }); + + it('zeroes every owned metadata and encoded-field buffer after stage hashing', () => { + const grid = indexGreaterRealmAxialGrid(hexDisc(1)); + const elevation = new Int32Array(grid.cellCount); + elevation.fill(0x1020_3040); + const fillSpy = vi.spyOn(Buffer.prototype, 'fill'); + + try { + expect(digestGreaterRealmTerrainStage('zeroization-proof', grid, { elevation })) + .toMatch(/^[0-9a-f]{64}$/); + const clearedBuffers = fillSpy.mock.instances.filter(Buffer.isBuffer); + expect(clearedBuffers.length).toBeGreaterThanOrEqual(12); + expect(clearedBuffers.some(buffer => buffer.length === elevation.byteLength)).toBe(true); + expect(clearedBuffers.every(buffer => buffer.every(byte => byte === 0))).toBe(true); + } finally { + fillSpy.mockRestore(); + } + }); +}); diff --git a/tests/greaterRealmToolchainSecurity.test.ts b/tests/greaterRealmToolchainSecurity.test.ts new file mode 100644 index 00000000..a5854df3 --- /dev/null +++ b/tests/greaterRealmToolchainSecurity.test.ts @@ -0,0 +1,321 @@ +// @vitest-environment node + +import { spawnSync } from 'node:child_process'; +import { + chmodSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + symlinkSync, + writeFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { isAbsolute, join, resolve } from 'node:path'; + +import { afterEach, describe, expect, it } from 'vitest'; + +// @ts-expect-error Executable ESM bootstrap exposes named test seams. +import { computeGreaterRealmPackageTree, verifyGreaterRealmTrustedToolchain } from '../scripts/atlas/greater-realm-toolchain-bootstrap.mjs'; + +import { + inspectGreaterRealmTrustedGit, + runGreaterRealmTrustedGit, + sha256GreaterRealmAttestedFile, +} from '../scripts/atlas/greater-realm-git'; + +const repositoryRoot = resolve(import.meta.dirname, '..'); +const temporaryRoots: string[] = []; + +function temporaryRoot(): string { + const root = mkdtempSync(join(tmpdir(), 'warpkeep-toolchain-security-')); + temporaryRoots.push(root); + return root; +} + +const FIXTURE_COMMON_PACKAGES = Object.freeze([ + '@img/colour', + 'detect-libc', + 'esbuild', + 'semver', + 'sharp', + 'tsx', + 'typescript', +]); +const FIXTURE_PLATFORM_PACKAGES = Object.freeze([ + '@esbuild/darwin-arm64', + '@img/sharp-darwin-arm64', + '@img/sharp-libvips-darwin-arm64', + '@typescript/typescript-darwin-arm64', + '@esbuild/linux-x64', + '@img/sharp-linux-x64', + '@img/sharp-libvips-linux-x64', + '@typescript/typescript-linux-x64', + 'fsevents', +]); + +function toolchainFixture() { + const root = temporaryRoot(); + const repositoryRoot = join(root, 'repository'); + const nodeModules = join(repositoryRoot, 'node_modules'); + const packageNames = [...FIXTURE_COMMON_PACKAGES, ...FIXTURE_PLATFORM_PACKAGES]; + mkdirSync(join(repositoryRoot, 'scripts', 'atlas'), { recursive: true, mode: 0o755 }); + mkdirSync(nodeModules, { mode: 0o755 }); + for (const name of packageNames) { + const packageRoot = join(nodeModules, ...name.split('/')); + mkdirSync(packageRoot, { recursive: true, mode: 0o755 }); + const packageMetadata: Record = { + name, + version: '1.0.0', + main: 'runtime.js', + }; + if (/^@img\/sharp-(?:darwin-arm64|linux-x64)$/u.test(name)) { + packageMetadata.exports = { './sharp.node': './runtime.js' }; + } else if (/^@img\/sharp-libvips-(?:darwin-arm64|linux-x64)$/u.test(name)) { + packageMetadata.exports = { + './lib': './runtime.js', + './package': './package.json', + './versions': './runtime.js', + }; + } + writeFileSync( + join(packageRoot, 'package.json'), + `${JSON.stringify(packageMetadata)}\n`, + { mode: 0o644 }, + ); + writeFileSync( + join(packageRoot, 'runtime.js'), + `export const packageName = ${JSON.stringify(name)};\n`, + { mode: 0o644 }, + ); + if (name === 'tsx') { + mkdirSync(join(packageRoot, 'dist'), { mode: 0o755 }); + writeFileSync(join(packageRoot, 'dist', 'cli.mjs'), 'process.exitCode = 0;\n', { + mode: 0o644, + }); + } + } + const integrity = `sha512-${Buffer.alloc(64).toString('base64')}`; + const packages = Object.fromEntries(packageNames.map(name => [name, { + name, + version: '1.0.0', + integrity, + ...computeGreaterRealmPackageTree(join(nodeModules, ...name.split('/'))), + }])); + const rootDevDependencies = Object.fromEntries( + ['sharp', 'tsx', 'typescript'].map(name => [name, '1.0.0']), + ); + writeFileSync(join(repositoryRoot, 'package.json'), `${JSON.stringify({ + packageManager: 'npm@10.9.8', + engines: { node: '>=22.13 <23' }, + devDependencies: rootDevDependencies, + })}\n`, { mode: 0o644 }); + const lockedPackages: Record = { + '': { devDependencies: rootDevDependencies }, + }; + for (const name of packageNames) { + lockedPackages[`node_modules/${name}`] = { + version: '1.0.0', + resolved: `https://registry.npmjs.org/${name}/-/fixture-1.0.0.tgz`, + integrity, + }; + } + writeFileSync(join(repositoryRoot, 'package-lock.json'), `${JSON.stringify({ + lockfileVersion: 3, + packages: lockedPackages, + })}\n`, { mode: 0o644 }); + writeFileSync( + join(repositoryRoot, 'scripts', 'atlas', 'greater-realm-toolchain-lock.json'), + `${JSON.stringify({ + kind: 'warpkeep.greater-realm.trusted-toolchain.v1', + configuredNodeEngine: '>=22.13 <23', + configuredPackageManager: 'npm@10.9.8', + packages, + profiles: { + 'darwin-arm64': { + esbuildPackage: '@esbuild/darwin-arm64', + sharpPackage: '@img/sharp-darwin-arm64', + libvipsPackage: '@img/sharp-libvips-darwin-arm64', + typescriptPackage: '@typescript/typescript-darwin-arm64', + tsxOptionalPackages: ['fsevents'], + }, + 'linux-x64': { + esbuildPackage: '@esbuild/linux-x64', + sharpPackage: '@img/sharp-linux-x64', + libvipsPackage: '@img/sharp-libvips-linux-x64', + typescriptPackage: '@typescript/typescript-linux-x64', + tsxOptionalPackages: [], + }, + }, + })}\n`, + { mode: 0o644 }, + ); + return Object.freeze({ nodeModules, repositoryRoot }); +} + +afterEach(() => { + for (const root of temporaryRoots.splice(0)) { + rmSync(root, { force: true, recursive: true }); + } +}); + +describe('Greater Realm toolchain provenance', () => { + it('verifies the locked local executable package trees before TypeScript or Sharp loads', () => { + const receipt = verifyGreaterRealmTrustedToolchain({ runtimeNode: '22.13.0' }); + + expect(receipt.profile).toBe(`${process.platform}-${process.arch}`); + expect(receipt.manifestSha256).toMatch(/^[0-9a-f]{64}$/u); + expect(receipt.verifiedPackageCount).toBe(process.platform === 'darwin' ? 12 : 11); + expect(receipt.tsxCli).toMatch(/[/\\]tsx[/\\]dist[/\\]cli\.mjs$/u); + }); + + it('rejects a malicious tsx tree before its injected module can execute', () => { + const fixture = toolchainFixture(); + const marker = join(fixture.repositoryRoot, 'malicious-tsx-ran'); + const tsxCli = join(fixture.nodeModules, 'tsx', 'dist', 'cli.mjs'); + writeFileSync( + tsxCli, + `import { writeFileSync } from 'node:fs'; writeFileSync(${JSON.stringify(marker)}, 'ran');\n`, + { mode: 0o644 }, + ); + + expect(() => verifyGreaterRealmTrustedToolchain({ + repositoryRoot: fixture.repositoryRoot, + runtimeNode: '22.13.0', + platform: 'darwin', + architecture: 'arm64', + })).toThrow('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_PACKAGE_TAMPERED'); + expect(existsSync(marker)).toBe(false); + }); + + it('accepts the Linux closure only when the macOS-only optional module is absent', () => { + const fixture = toolchainFixture(); + rmSync(join(fixture.nodeModules, 'fsevents'), { recursive: true }); + + const receipt = verifyGreaterRealmTrustedToolchain({ + repositoryRoot: fixture.repositoryRoot, + runtimeNode: '22.13.0', + platform: 'linux', + architecture: 'x64', + }); + + expect(receipt.profile).toBe('linux-x64'); + expect(receipt.verifiedPackageCount).toBe(11); + }); + + it('rejects an undeclared optional-module shadow in the Linux resolution path', () => { + const fixture = toolchainFixture(); + + expect(() => verifyGreaterRealmTrustedToolchain({ + repositoryRoot: fixture.repositoryRoot, + runtimeNode: '22.13.0', + platform: 'linux', + architecture: 'x64', + })).toThrow('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_RESOLUTION_INVALID'); + }); + + it('rejects lockfile integrity drift before inspecting runtime versions', () => { + const fixture = toolchainFixture(); + const lockPath = join(fixture.repositoryRoot, 'package-lock.json'); + const lock = JSON.parse(readFileSync(lockPath, 'utf8')) as { + packages: Record; + }; + lock.packages['node_modules/tsx']!.integrity = `sha512-${Buffer.alloc(64, 7).toString('base64')}`; + writeFileSync(lockPath, `${JSON.stringify(lock)}\n`, { mode: 0o644 }); + + expect(() => verifyGreaterRealmTrustedToolchain({ + repositoryRoot: fixture.repositoryRoot, + runtimeNode: '22.13.0', + platform: 'darwin', + architecture: 'arm64', + })).toThrow('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_LOCK_MISMATCH'); + }); + + it('rejects dependency and native-loader overrides before runtime inspection', () => { + const bootstrap = join( + repositoryRoot, + 'scripts', + 'atlas', + 'greater-realm-toolchain-bootstrap.mjs', + ); + const result = spawnSync(process.execPath, [bootstrap, '--verify-only'], { + cwd: repositoryRoot, + encoding: 'utf8', + env: { + ESBUILD_BINARY_PATH: join(temporaryRoot(), 'untrusted-esbuild'), + }, + }); + + expect(result.status).toBe(1); + expect(result.stdout).toBe(''); + expect(result.stderr).toBe('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_ENVIRONMENT_INVALID\n'); + }); + + it('uses an attested absolute Git binary and disables inherited executable config', () => { + const root = temporaryRoot(); + const fakeGit = join(root, 'git'); + const marker = join(root, 'fake-git-ran'); + const maliciousConfig = join(root, 'malicious.gitconfig'); + writeFileSync(fakeGit, `#!/bin/sh\ntouch "${marker}"\nexit 99\n`, { mode: 0o700 }); + chmodSync(fakeGit, 0o700); + writeFileSync(maliciousConfig, '[core]\n\tfsmonitor = malicious-monitor\n\thooksPath = malicious-hooks\n'); + const prior = Object.freeze({ + PATH: process.env.PATH, + GIT_CONFIG_GLOBAL: process.env.GIT_CONFIG_GLOBAL, + GIT_CONFIG_SYSTEM: process.env.GIT_CONFIG_SYSTEM, + GIT_EXEC_PATH: process.env.GIT_EXEC_PATH, + }); + try { + process.env.PATH = root; + process.env.GIT_CONFIG_GLOBAL = maliciousConfig; + process.env.GIT_CONFIG_SYSTEM = maliciousConfig; + process.env.GIT_EXEC_PATH = root; + const attestation = inspectGreaterRealmTrustedGit(); + expect(isAbsolute(attestation.binaryPath)).toBe(true); + expect(isAbsolute(attestation.execPath)).toBe(true); + expect(attestation.binaryPath).not.toBe(fakeGit); + expect(attestation.binarySha256).toMatch(/^[0-9a-f]{64}$/u); + expect(attestation.version).toMatch(/^git version /u); + const hooks = runGreaterRealmTrustedGit( + ['config', '--get', 'core.hooksPath'], + repositoryRoot, + ); + const fsmonitor = runGreaterRealmTrustedGit( + ['config', '--get', 'core.fsmonitor'], + repositoryRoot, + ); + const untrackedCache = runGreaterRealmTrustedGit( + ['config', '--get', 'core.untrackedCache'], + repositoryRoot, + ); + expect(hooks.status).toBe(0); + expect(hooks.stdout.trim()).toBe(process.platform === 'win32' ? 'NUL' : '/dev/null'); + expect(fsmonitor.status).toBe(0); + expect(fsmonitor.stdout.trim()).toBe('false'); + expect(untrackedCache.status).toBe(0); + expect(untrackedCache.stdout.trim()).toBe('false'); + expect(() => sha256GreaterRealmAttestedFile(fakeGit, root)).not.toThrow(); + expect(() => sha256GreaterRealmAttestedFile(fakeGit, repositoryRoot)) + .toThrow('GREATER_REALM_TOOLCHAIN_INVALID'); + } finally { + for (const [key, value] of Object.entries(prior)) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + } + }); + + it('rejects an attested-file symlink that escapes its allowed root', () => { + const root = temporaryRoot(); + const allowed = join(root, 'allowed'); + mkdirSync(allowed, { mode: 0o700 }); + const outside = join(root, 'outside.bin'); + const link = join(allowed, 'escaped.bin'); + writeFileSync(outside, 'controlled-toolchain-artifact', { mode: 0o600 }); + symlinkSync(outside, link); + + expect(() => sha256GreaterRealmAttestedFile(link, allowed)) + .toThrow('GREATER_REALM_TOOLCHAIN_INVALID'); + }); +}); diff --git a/tests/greaterRealmTopography.test.ts b/tests/greaterRealmTopography.test.ts new file mode 100644 index 00000000..2e3f27af --- /dev/null +++ b/tests/greaterRealmTopography.test.ts @@ -0,0 +1,218 @@ +import { createHash } from 'node:crypto'; + +import { describe, expect, it } from 'vitest'; + +import { + clearGreaterRealmCandidateSecret, + generateGreaterRealmCandidate, +} from '../scripts/atlas/greater-realm-candidate-generator'; +import { indexGreaterRealmAxialGrid } from '../scripts/atlas/greater-realm-terrain'; +import { deriveGreaterRealmTopography } from '../scripts/atlas/greater-realm-topography'; + +function root(index: number): Uint8Array { + return Uint8Array.from(createHash('sha256') + .update('greater-realm-test-root\0', 'utf8') + .update(String(index), 'utf8') + .digest()); +} + +const DRY_COMPATIBLE_PAIRS = new Set([ + '1:3', '1:4', + '2:3', '2:5', + '3:1', + '4:3', '4:5', + '5:1', '5:3', '5:5', '5:15', + '6:7', '6:14', + '7:6', '7:7', '7:14', + '8:3', '8:4', '8:14', + '9:3', '9:4', + '10:3', '10:4', + '11:9', '11:13', + '12:6', '12:9', '12:13', + '13:9', + '14:7', '14:12', + '15:3', '15:4', '15:7', '15:12', + '16:12', + '17:12', + '18:11', + '19:5', '19:6', '19:7', + '23:0', '23:17', +]); + +function independentlyCompatiblePair(regime: number, biome: number, landform: number): boolean { + if (regime === 1 || regime === 5) return biome === 20 && landform === 16; + if (regime === 2) return biome === 21 && landform === 10; + if (regime === 3 || regime === 4) return biome === 22 && landform === 2; + return regime === 0 && DRY_COMPATIBLE_PAIRS.has(`${biome}:${landform}`); +} + +describe('Greater Realm derived topography', () => { + it('derives deterministic bounded geomorphology from final routed authority', () => { + const seed = root(52); + const candidate = generateGreaterRealmCandidate({ rootSeed: seed, candidateOrdinal: 3 }); + try { + const input = { + grid: candidate.grid, + elevation: candidate.elevation, + flowReceiver: candidate.flowReceiver, + flowAccumulation: candidate.flowAccumulation, + waterRegime: candidate.waterRegime, + geologyId: candidate.geologyId, + tectonicUplift: candidate.tectonicUplift, + rockResistance: candidate.rockResistance, + regionId: candidate.regionId, + tierId: candidate.tierId, + legacyProtectedCell: candidate.legacyLowlandsProtectedCell, + protectedBiomeId: candidate.biomeId, + protectedLandformId: candidate.landformId, + geomorphicTemperature: candidate.geomorphologyTemperature, + geomorphicMoisture: candidate.geomorphologyMoisture, + geomorphicGlacialMask: candidate.geomorphologyGlacialMask, + geomorphicAridMask: candidate.geomorphologyAridMask, + geomorphicVolcanicMask: candidate.geomorphologyVolcanicMask, + geomorphicCoastalClass: candidate.geomorphologyCoastalClass, + } as const; + const first = deriveGreaterRealmTopography(input); + const second = deriveGreaterRealmTopography(input); + + expect(first).toEqual(second); + for (const field of [ + first.slope, + first.aspect, + first.profileCurvature, + first.planCurvature, + first.wetnessIndex, + first.exposure, + first.distanceToCoast, + first.distanceToFreshwater, + first.watershedId, + first.ridgeId, + first.temperature, + first.moisture, + first.biomeId, + first.landformId, + ]) expect(field.length).toBe(candidate.grid.cellCount); + expect(first.topographyMetrics.elevationMinimum).toBeLessThan(0); + expect(first.topographyMetrics.elevationMaximum).toBeGreaterThan(0); + expect(first.topographyMetrics.slopeP95).toBeGreaterThanOrEqual( + first.topographyMetrics.slopeP50, + ); + expect(first.topographyMetrics.ridgeCells).toBeGreaterThan(0); + expect(first.topographyMetrics.coastCells).toBeGreaterThan(0); + expect(first.biomeMetrics.visualBiomeClassCount).toBeGreaterThanOrEqual(8); + expect(first.biomeMetrics.minimumRegionVisualBiomeClassCount).toBeGreaterThanOrEqual(3); + expect(first.biomeMetrics.minimumTierIVisualBiomeClassCount).toBeGreaterThanOrEqual(6); + expect(first.biomeMetrics.minimumTierIIVisualBiomeClassCount).toBeGreaterThanOrEqual(5); + expect(first.biomeMetrics.tierIIIVisualBiomeClassCount).toBeGreaterThanOrEqual(3); + expect(first.biomeMetrics.minimumTierIMajorVisualBiomeClassCount).toBeGreaterThanOrEqual(4); + expect(first.biomeMetrics.minimumTierITransitionVisualBiomeClassCount).toBeGreaterThanOrEqual(2); + expect(first.biomeMetrics.minimumTierIIMajorVisualBiomeClassCount).toBeGreaterThanOrEqual(5); + expect(first.biomeMetrics.tierIIIMajorVisualBiomeClassCount).toBeGreaterThanOrEqual(3); + expect(first.biomeMetrics.maximumTierISingleBiomeShareBasisPoints).toBeLessThanOrEqual(5_500); + expect(first.biomeMetrics.incompatibleVisualBiomeAdjacencyCount).toBe(0); + expect(first.biomeMetrics.incompatibleBiomeLandformPairCount).toBe(0); + expect([...first.aspect].every(value => value <= 6)).toBe(true); + expect([...first.watershedId].every(value => value > 0)).toBe(true); + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + expect(independentlyCompatiblePair( + candidate.waterRegime[cell]!, + first.biomeId[cell]!, + first.landformId[cell]!, + )).toBe(true); + if (candidate.legacyLowlandsProtectedCell[cell] !== 1) continue; + expect(first.biomeId[cell]).toBe(candidate.biomeId[cell]); + expect(first.landformId[cell]).toBe(candidate.landformId[cell]); + } + } finally { + seed.fill(0); + clearGreaterRealmCandidateSecret(candidate); + } + }, 30_000); + + it('smooths visual classification as a compatible pair without moving protected process cells', () => { + const coordinates: Array<{ q: number; r: number }> = []; + for (let q = -2; q <= 2; q += 1) { + for (let r = Math.max(-2, -q - 2); r <= Math.min(2, -q + 2); r += 1) { + coordinates.push({ q, r }); + } + } + const grid = indexGreaterRealmAxialGrid(coordinates); + const center = grid.indexOf({ q: 0, r: 0 }); + const protectedCell = grid.indexOf({ q: -1, r: 0 }); + const glacialCell = grid.indexOf({ q: 1, r: 0 }); + const aridCell = grid.indexOf({ q: 0, r: 2 }); + const volcanicCell = grid.indexOf({ q: 0, r: -2 }); + const coastalCell = grid.indexOf({ q: 2, r: -2 }); + const oceanCell = grid.indexOf({ q: 2, r: 0 }); + const seaCell = grid.indexOf({ q: 2, r: -1 }); + const lakeCell = grid.indexOf({ q: -2, r: 0 }); + const waterRegime = new Uint8Array(grid.cellCount); + waterRegime[oceanCell] = 1; + waterRegime[seaCell] = 5; + waterRegime[lakeCell] = 2; + const legacyProtectedCell = new Uint8Array(grid.cellCount); + legacyProtectedCell[protectedCell] = 1; + const protectedBiomeId = new Uint8Array(grid.cellCount); + const protectedLandformId = new Uint8Array(grid.cellCount); + protectedBiomeId[protectedCell] = 4; + protectedLandformId[protectedCell] = 3; + const geomorphicGlacialMask = new Uint8Array(grid.cellCount); + geomorphicGlacialMask[glacialCell] = 1; + const geomorphicAridMask = new Uint8Array(grid.cellCount); + geomorphicAridMask[aridCell] = 1; + const geomorphicVolcanicMask = new Uint8Array(grid.cellCount); + geomorphicVolcanicMask[volcanicCell] = 1; + const geomorphicCoastalClass = new Uint8Array(grid.cellCount); + geomorphicCoastalClass[coastalCell] = 2; + const geomorphicMoisture = new Int32Array(grid.cellCount); + geomorphicMoisture.fill(1_500); + geomorphicMoisture[center] = -3_000; + + const result = deriveGreaterRealmTopography({ + grid, + elevation: new Int32Array(grid.cellCount).fill(1_000), + flowReceiver: new Int32Array(grid.cellCount).fill(-1), + flowAccumulation: new BigUint64Array(grid.cellCount).fill(1n), + waterRegime, + geologyId: new Uint8Array(grid.cellCount), + tectonicUplift: new Int32Array(grid.cellCount), + rockResistance: new Int32Array(grid.cellCount), + regionId: new Uint8Array(grid.cellCount), + tierId: new Uint8Array(grid.cellCount).fill(1), + legacyProtectedCell, + protectedBiomeId, + protectedLandformId, + geomorphicTemperature: new Int32Array(grid.cellCount).fill(4_000), + geomorphicMoisture, + geomorphicGlacialMask, + geomorphicAridMask, + geomorphicVolcanicMask, + geomorphicCoastalClass, + }); + + // Four ordinary forest neighbors outvote the center's initial grassland + // class. The matching forest landform must move with the biome. + expect(result.biomeId[center]).toBe(2); + expect(result.landformId[center]).toBe(5); + expect(result.biomeId[protectedCell]).toBe(4); + expect(result.landformId[protectedCell]).toBe(3); + expect(result.biomeId[glacialCell]).toBe(5); + expect(result.landformId[glacialCell]).toBe(15); + expect(result.biomeId[aridCell]).toBe(11); + expect(result.landformId[aridCell]).toBe(13); + expect(result.biomeId[volcanicCell]).toBe(14); + expect(result.landformId[volcanicCell]).toBe(12); + expect(result.biomeId[coastalCell]).toBe(23); + expect(result.landformId[coastalCell]).toBe(17); + expect(result.biomeId[seaCell]).toBe(20); + expect(result.landformId[seaCell]).toBe(16); + expect(result.biomeMetrics.incompatibleBiomeLandformPairCount).toBe(0); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + expect(independentlyCompatiblePair( + waterRegime[cell]!, + result.biomeId[cell]!, + result.landformId[cell]!, + )).toBe(true); + } + }); +}); diff --git a/tests/localVitePublicBoundary.test.ts b/tests/localVitePublicBoundary.test.ts index c6eac7bc..17931c67 100644 --- a/tests/localVitePublicBoundary.test.ts +++ b/tests/localVitePublicBoundary.test.ts @@ -4,6 +4,7 @@ import { mkdtempSync, rmSync, symlinkSync, + truncateSync, writeFileSync, } from 'node:fs'; import { tmpdir } from 'node:os'; @@ -13,6 +14,11 @@ import { WARPKEEP_LOCAL_VITE_FS_DENY, warpkeepLocalPublicBoundaryPlugin, } from '../scripts/qa-observer/local-vite-fs-deny.mjs'; +// @ts-expect-error Executable ESM scanner exposes immutable marker text. +import { GREATER_REALM_PRIVATE_MARKER_TEXT } from '../scripts/atlas/greater-realm-private-markers.mjs'; + +const PRIVATE_MARKER_CASES = (GREATER_REALM_PRIVATE_MARKER_TEXT as readonly string[]) + .map((marker, index): [number, string] => [index, marker]); type BoundaryMiddleware = ( request: Readonly<{ url?: string }>, @@ -70,6 +76,16 @@ describe('local Vite public boundary', () => { '**/.cache/**', '**/.wrangler/**', '**/.secrets/**', + '**/.warpkeep-private/**', + '**/greater-realm-private/**', + 'seed.bin', + 'batch-seed.bin', + 'manifest.private.json', + 'batch.private.json', + 'selection.private.json', + 'shortlist.private.json', + '*private-preview*', + '*.{wkgr-atlas,wkgr-checkpoint,wkgr-private}', ])); }); @@ -79,6 +95,15 @@ describe('local Vite public boundary', () => { 'operator.trace', 'session.sqlite3', 'recovery.tar.gz', + 'candidate.wkgr-atlas', + 'checkpoint.wkgr-checkpoint', + 'seed.bin', + 'batch-seed.bin', + 'manifest.private.json', + 'batch.private.json', + 'selection.private.json', + 'shortlist.private.json', + 'private-preview-silhouette.png', ])('refuses a pre-existing sensitive public artifact: %s', (filename) => { const root = mkdtempSync(join(tmpdir(), 'warpkeep-public-boundary-')); const publicDirectory = join(root, 'public'); @@ -93,6 +118,93 @@ describe('local Vite public boundary', () => { } }); + it.each(PRIVATE_MARKER_CASES)( + 'refuses renamed Greater Realm private marker family %i', + (index, marker) => { + const root = mkdtempSync(join(tmpdir(), 'warpkeep-public-marker-')); + const publicDirectory = join(root, 'public'); + mkdirSync(publicDirectory); + writeFileSync(join(publicDirectory, `ordinary-${index}.bin`), `prefix\0${marker}\0suffix`); + try { + expect(() => configuredBoundary(publicDirectory)).toThrow( + 'Warpkeep public directory contains a prohibited local artifact.', + ); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }, + ); + + it('detects a renamed private marker spanning public-file read chunks', () => { + const root = mkdtempSync(join(tmpdir(), 'warpkeep-public-split-marker-')); + const publicDirectory = join(root, 'public'); + mkdirSync(publicDirectory); + const marker = Buffer.from(GREATER_REALM_PRIVATE_MARKER_TEXT[7]!, 'utf8'); + const bytes = Buffer.alloc(64 * 1024 + marker.length, 0x41); + marker.copy(bytes, 64 * 1024 - Math.floor(marker.length / 2)); + writeFileSync(join(publicDirectory, 'ordinary-map.bin'), bytes); + try { + expect(() => configuredBoundary(publicDirectory)).toThrow( + 'Warpkeep public directory contains a prohibited local artifact.', + ); + } finally { + bytes.fill(0); + marker.fill(0); + rmSync(root, { force: true, recursive: true }); + } + }); + + it('fails closed before reading an oversized public file', () => { + const root = mkdtempSync(join(tmpdir(), 'warpkeep-public-oversized-')); + const publicDirectory = join(root, 'public'); + mkdirSync(publicDirectory); + const oversized = join(publicDirectory, 'ordinary-map.bin'); + writeFileSync(oversized, ''); + truncateSync(oversized, 128 * 1024 * 1024 + 1); + try { + expect(() => configuredBoundary(publicDirectory)).toThrow( + 'Warpkeep public directory contains a prohibited local artifact.', + ); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }); + + it('blocks a renamed private artifact created after startup', () => { + const root = mkdtempSync(join(tmpdir(), 'warpkeep-public-late-marker-')); + const publicDirectory = join(root, 'public'); + mkdirSync(publicDirectory); + try { + const middleware = configuredBoundary(publicDirectory); + writeFileSync( + join(publicDirectory, 'ordinary-map.bin'), + GREATER_REALM_PRIVATE_MARKER_TEXT[2]!, + ); + const { next, response } = invoke(middleware, '/ordinary-map.bin'); + expect(next).not.toHaveBeenCalled(); + expect(response.statusCode).toBe(404); + expect(response.setHeader).toHaveBeenCalledWith('cache-control', 'no-store'); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }); + + it('rejects a renamed public symlink before scanning its target', () => { + const root = mkdtempSync(join(tmpdir(), 'warpkeep-public-linked-marker-')); + const publicDirectory = join(root, 'public'); + const outside = join(root, 'outside.bin'); + mkdirSync(publicDirectory); + writeFileSync(outside, 'ordinary external fixture'); + symlinkSync(outside, join(publicDirectory, 'ordinary-map.bin')); + try { + expect(() => configuredBoundary(publicDirectory)).toThrow( + 'Warpkeep public directory contains a prohibited local artifact.', + ); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }); + it('blocks sensitive and encoded filenames created after startup', () => { const root = mkdtempSync(join(tmpdir(), 'warpkeep-public-request-')); const publicDirectory = join(root, 'public'); @@ -100,7 +212,13 @@ describe('local Vite public boundary', () => { try { const middleware = configuredBoundary(publicDirectory); writeFileSync(join(publicDirectory, 'private-session.har'), 'controlled non-secret fixture'); - for (const requestPath of ['/private-session.har', '/private-session%2Ehar']) { + writeFileSync(join(publicDirectory, 'batch-seed.bin'), 'controlled non-secret fixture'); + for (const requestPath of [ + '/private-session.har', + '/private-session%2Ehar', + '/batch-seed.bin', + '/batch-seed%2Ebin', + ]) { const { next, response } = invoke(middleware, requestPath); expect(next).not.toHaveBeenCalled(); expect(response.statusCode).toBe(404); diff --git a/tests/renderedWebglBrowserProbe.test.ts b/tests/renderedWebglBrowserProbe.test.ts index 6be6ce7c..13250eb3 100644 --- a/tests/renderedWebglBrowserProbe.test.ts +++ b/tests/renderedWebglBrowserProbe.test.ts @@ -98,7 +98,17 @@ const EXPECTED_LOCAL_VITE_FS_DENY = Object.freeze([ '**/.git/**', '**/.cache/**', '**/.wrangler/**', - '**/.secrets/**' + '**/.secrets/**', + '**/.warpkeep-private/**', + '**/greater-realm-private/**', + 'seed.bin', + 'batch-seed.bin', + 'manifest.private.json', + 'batch.private.json', + 'selection.private.json', + 'shortlist.private.json', + '*private-preview*', + '*.{wkgr-atlas,wkgr-checkpoint,wkgr-private}' ]); function renderedWebglSfxSession(options: Readonly<{ From 5c24811d612945104fcd33e289dbedb054ad4054 Mon Sep 17 00:00:00 2001 From: Ael Date: Tue, 4 Aug 2026 13:40:49 +0200 Subject: [PATCH 2/6] fix(atlas): attest npm-installed esbuild binary --- .../greater-realm-private-generation.md | 6 ++- .../greater-realm-toolchain-bootstrap.mjs | 41 ++++++++++++++++++- .../atlas/greater-realm-toolchain-lock.json | 6 +-- tests/greaterRealmCandidateGenerator.test.ts | 2 +- tests/greaterRealmToolchainSecurity.test.ts | 26 +++++++++++- tests/greaterRealmTopography.test.ts | 2 +- 6 files changed, 74 insertions(+), 9 deletions(-) diff --git a/docs/security/greater-realm-private-generation.md b/docs/security/greater-realm-private-generation.md index 0ec63e06..fd54cb0f 100644 --- a/docs/security/greater-realm-private-generation.md +++ b/docs/security/greater-realm-private-generation.md @@ -94,7 +94,11 @@ TypeScript, esbuild, Sharp, libvips, or their JavaScript dependencies. It: before launching the pinned absolute `tsx` entrypoint. Package-manager `.bin` launch shims are not part of registry package trees and are excluded; the child receives only the trusted Node executable directory in `PATH`, so - those shims cannot participate in command resolution. + those shims cannot participate in command resolution. npm's reviewed + esbuild postinstall copy is handled explicitly: the stable JavaScript tree + is hashed without `bin/esbuild`, while that installed executable must be + byte-identical, executable, and size-identical to the separately locked + platform-native esbuild package; The bootstrap also rejects Node/module-resolution, native-loader, esbuild, and Sharp override environment variables before verification and removes them diff --git a/scripts/atlas/greater-realm-toolchain-bootstrap.mjs b/scripts/atlas/greater-realm-toolchain-bootstrap.mjs index 81d78c82..dc1fbdbf 100644 --- a/scripts/atlas/greater-realm-toolchain-bootstrap.mjs +++ b/scripts/atlas/greater-realm-toolchain-bootstrap.mjs @@ -292,7 +292,25 @@ function hashPinnedPackageFile(path) { } } -export function computeGreaterRealmPackageTree(packageRoot) { +export function computeGreaterRealmPackageTree(packageRoot, options = {}) { + if ( + options === null + || typeof options !== 'object' + || Array.isArray(options) + || Object.keys(options).some(key => key !== 'excludedFiles') + || (options.excludedFiles !== undefined && !Array.isArray(options.excludedFiles)) + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); + const excludedFiles = new Set(options.excludedFiles ?? []); + if ( + excludedFiles.size !== (options.excludedFiles?.length ?? 0) + || [...excludedFiles].some(path => ( + typeof path !== 'string' + || path.length === 0 + || path.startsWith('/') + || path.includes('\\') + || path.split('/').some(segment => segment === '' || segment === '.' || segment === '..') + )) + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); const canonicalRoot = realpathSync(packageRoot); const records = []; let byteCount = 0; @@ -320,6 +338,7 @@ export function computeGreaterRealmPackageTree(packageRoot) { } if (!entry.isFile()) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); const file = hashPinnedPackageFile(path); + if (excludedFiles.has(relativePath)) continue; byteCount += file.byteCount; records.push(Object.freeze({ path: relativePath, @@ -519,7 +538,13 @@ function verifyPackage(alias, expected, expectedName) { if (beforeCanonical !== alias.canonical) { fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_FILESYSTEM_INVALID'); } - const observed = computeGreaterRealmPackageTree(alias.canonical); + // npm's reviewed esbuild postinstall replaces `bin/esbuild` with the exact + // selected native-package executable. Attest the stable package tree without + // that platform-specific copy here, then bind the installed copy byte-for- + // byte to the separately locked native package below. + const observed = computeGreaterRealmPackageTree(alias.canonical, { + excludedFiles: expectedName === 'esbuild' ? ['bin/esbuild'] : [], + }); if ( observed.treeSha256 !== expected.treeSha256 || observed.fileCount !== expected.fileCount @@ -702,6 +727,18 @@ export function verifyGreaterRealmTrustedToolchain(input = {}) { if (alias === undefined) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_INVALID'); return verifyPackage(alias, expected(name), name); }); + const installedEsbuild = hashPinnedPackageFile(resolve(esbuildAlias.canonical, 'bin', 'esbuild')); + const nativeEsbuild = hashPinnedPackageFile(resolve( + aliases[profile.packages.esbuildPackage].canonical, + 'bin', + 'esbuild', + )); + if ( + !installedEsbuild.executable + || !nativeEsbuild.executable + || installedEsbuild.byteCount !== nativeEsbuild.byteCount + || installedEsbuild.sha256 !== nativeEsbuild.sha256 + ) fail('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_PACKAGE_TAMPERED'); const tsxCli = resolve(tsxAlias.canonical, 'dist', 'cli.mjs'); const tsxCliStatus = lstatSync(tsxCli); assertTrustedStatus(tsxCliStatus, 'file'); diff --git a/scripts/atlas/greater-realm-toolchain-lock.json b/scripts/atlas/greater-realm-toolchain-lock.json index fbe7e3d6..bc690b5a 100644 --- a/scripts/atlas/greater-realm-toolchain-lock.json +++ b/scripts/atlas/greater-realm-toolchain-lock.json @@ -87,9 +87,9 @@ "name": "esbuild", "version": "0.28.1", "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "byteCount": 146953, - "fileCount": 7, - "treeSha256": "b8bac9fbb12dda03ef60557fa29614cabba79e16be45c50690be1bd038298e8f" + "byteCount": 137603, + "fileCount": 6, + "treeSha256": "96193464822bf1503f5fb88a2b9abd159aa669aa33e5119d1f5101395d5dab57" }, "fsevents": { "name": "fsevents", diff --git a/tests/greaterRealmCandidateGenerator.test.ts b/tests/greaterRealmCandidateGenerator.test.ts index 7b48865c..b6abbfe5 100644 --- a/tests/greaterRealmCandidateGenerator.test.ts +++ b/tests/greaterRealmCandidateGenerator.test.ts @@ -209,7 +209,7 @@ beforeAll(() => { collisionRoot.fill(0); secondRoot.fill(0); } -}, 60_000); +}, 120_000); afterAll(() => { if (pinned) clearGreaterRealmCandidateSecret(pinned); diff --git a/tests/greaterRealmToolchainSecurity.test.ts b/tests/greaterRealmToolchainSecurity.test.ts index a5854df3..cec09a24 100644 --- a/tests/greaterRealmToolchainSecurity.test.ts +++ b/tests/greaterRealmToolchainSecurity.test.ts @@ -89,6 +89,12 @@ function toolchainFixture() { `export const packageName = ${JSON.stringify(name)};\n`, { mode: 0o644 }, ); + if (name === 'esbuild' || /^@esbuild\/(?:darwin-arm64|linux-x64)$/u.test(name)) { + mkdirSync(join(packageRoot, 'bin'), { mode: 0o755 }); + writeFileSync(join(packageRoot, 'bin', 'esbuild'), 'fixture-esbuild-binary\n', { + mode: 0o755, + }); + } if (name === 'tsx') { mkdirSync(join(packageRoot, 'dist'), { mode: 0o755 }); writeFileSync(join(packageRoot, 'dist', 'cli.mjs'), 'process.exitCode = 0;\n', { @@ -101,7 +107,9 @@ function toolchainFixture() { name, version: '1.0.0', integrity, - ...computeGreaterRealmPackageTree(join(nodeModules, ...name.split('/'))), + ...computeGreaterRealmPackageTree(join(nodeModules, ...name.split('/')), { + excludedFiles: name === 'esbuild' ? ['bin/esbuild'] : [], + }), }])); const rootDevDependencies = Object.fromEntries( ['sharp', 'tsx', 'typescript'].map(name => [name, '1.0.0']), @@ -189,6 +197,22 @@ describe('Greater Realm toolchain provenance', () => { expect(existsSync(marker)).toBe(false); }); + it('binds npm\'s installed esbuild executable to the locked native package', () => { + const fixture = toolchainFixture(); + writeFileSync( + join(fixture.nodeModules, 'esbuild', 'bin', 'esbuild'), + 'substituted-native-binary\n', + { mode: 0o755 }, + ); + + expect(() => verifyGreaterRealmTrustedToolchain({ + repositoryRoot: fixture.repositoryRoot, + runtimeNode: '22.13.0', + platform: 'darwin', + architecture: 'arm64', + })).toThrow('GREATER_REALM_TOOLCHAIN_BOOTSTRAP_PACKAGE_TAMPERED'); + }); + it('accepts the Linux closure only when the macOS-only optional module is absent', () => { const fixture = toolchainFixture(); rmSync(join(fixture.nodeModules, 'fsevents'), { recursive: true }); diff --git a/tests/greaterRealmTopography.test.ts b/tests/greaterRealmTopography.test.ts index 2e3f27af..31895430 100644 --- a/tests/greaterRealmTopography.test.ts +++ b/tests/greaterRealmTopography.test.ts @@ -127,7 +127,7 @@ describe('Greater Realm derived topography', () => { seed.fill(0); clearGreaterRealmCandidateSecret(candidate); } - }, 30_000); + }, 60_000); it('smooths visual classification as a compatible pair without moving protected process cells', () => { const coordinates: Array<{ q: number; r: number }> = []; From e9aee287cf45f5586127124b0e20a968dfe56eb0 Mon Sep 17 00:00:00 2001 From: Ael Date: Tue, 4 Aug 2026 13:52:29 +0200 Subject: [PATCH 3/6] test(atlas): bound Greater Realm CI concurrency --- tests/greaterRealmCandidateGenerator.test.ts | 2 +- tests/greaterRealmCandidatePackage.test.ts | 18 +++++++++--------- tests/greaterRealmCliSecurity.test.ts | 2 +- tests/greaterRealmOrdinaryYield.test.ts | 2 +- tests/hermesAdminSecurity.test.ts | 2 +- vite.config.ts | 6 ++++++ 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/tests/greaterRealmCandidateGenerator.test.ts b/tests/greaterRealmCandidateGenerator.test.ts index b6abbfe5..d3e01938 100644 --- a/tests/greaterRealmCandidateGenerator.test.ts +++ b/tests/greaterRealmCandidateGenerator.test.ts @@ -209,7 +209,7 @@ beforeAll(() => { collisionRoot.fill(0); secondRoot.fill(0); } -}, 120_000); +}, 180_000); afterAll(() => { if (pinned) clearGreaterRealmCandidateSecret(pinned); diff --git a/tests/greaterRealmCandidatePackage.test.ts b/tests/greaterRealmCandidatePackage.test.ts index 0e59171b..3cc57266 100644 --- a/tests/greaterRealmCandidatePackage.test.ts +++ b/tests/greaterRealmCandidatePackage.test.ts @@ -65,7 +65,7 @@ const priorToolchainProfile = process.env.WKGR_TOOLCHAIN_PREFLIGHT_PROFILE; // package verification intentionally regenerates a 100k+ cell candidate, so // retain strict assertions while giving those fail-closed checks enough wall // time under shared CI load. -vi.setConfig({ testTimeout: 20_000 }); +vi.setConfig({ testTimeout: 45_000 }); const temporaryRoots: string[] = []; let repositoryRoot = ''; @@ -225,7 +225,7 @@ beforeAll(async () => { }); atlasDigest = written.atlasDigest; manifestDigest = written.manifestDigest; -}, 30_000); +}, 60_000); afterAll(() => { if (priorToolchainReceipt === undefined) delete process.env.WKGR_TOOLCHAIN_PREFLIGHT_RECEIPT; @@ -312,7 +312,7 @@ describe('Greater Realm owner-only candidate package', () => { first.fill(0); second.fill(0); } - }, 30_000); + }, 60_000); it('binds deterministic 15-by-15 axial bins, topography patches, and toolchain pins', () => { const fixture = requireFixture(); @@ -583,7 +583,7 @@ describe('Greater Realm owner-only candidate package', () => { await expect(verifyFixture({ expectedAtlasDigest: corruptedDigest })) .rejects.toThrow('GREATER_REALM_PRIVATE_ATLAS_INVALID'); }); - }, 15_000); + }, 45_000); it('rejects geological-barrier-band inventory-name tampering with an updated digest', async () => { const relativePath = candidateRelativePath('atlas.wkgr-atlas'); @@ -601,7 +601,7 @@ describe('Greater Realm owner-only candidate package', () => { await expect(verifyFixture({ expectedAtlasDigest: corruptedDigest })) .rejects.toThrow('GREATER_REALM_PRIVATE_ATLAS_INVALID'); }); - }, 15_000); + }, 45_000); it('rejects a manifest whose contents and expected digest were changed together', async () => { const relativePath = candidateRelativePath('manifest.private.json'); @@ -616,7 +616,7 @@ describe('Greater Realm owner-only candidate package', () => { await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); }); - }, 15_000); + }, 45_000); it('rejects unknown manifest fields even when the expected digest is updated', async () => { const relativePath = candidateRelativePath('manifest.private.json'); @@ -631,7 +631,7 @@ describe('Greater Realm owner-only candidate package', () => { await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); }); - }, 15_000); + }, 45_000); it.each<[ string, @@ -670,7 +670,7 @@ describe('Greater Realm owner-only candidate package', () => { await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); }); - }, 15_000); + }, 45_000); it('rejects duplicate private-manifest keys even when the final parsed value is valid', async () => { const relativePath = candidateRelativePath('manifest.private.json'); @@ -871,7 +871,7 @@ describe('Greater Realm owner-only candidate package', () => { await expect(verifyFixture({ expectedPerformance: Object.freeze({ ...PERFORMANCE, processPeakMemoryMiB: 520 }), })).rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); - }, 30_000); + }, 60_000); it('rejects a topography field whose runtime array type does not match its encoding', () => { const fixture = requireFixture(); diff --git a/tests/greaterRealmCliSecurity.test.ts b/tests/greaterRealmCliSecurity.test.ts index 108b7cf9..84f95bc7 100644 --- a/tests/greaterRealmCliSecurity.test.ts +++ b/tests/greaterRealmCliSecurity.test.ts @@ -319,7 +319,7 @@ describe('Greater Realm atlas CLI security boundary', () => { clearGreaterRealmPrivateCandidateBuffers(candidate); rootSeed.fill(0); } - }, 120_000); + }, 180_000); it('restricts public evidence exports to one canonical JSON basename', () => { const expected = resolve( diff --git a/tests/greaterRealmOrdinaryYield.test.ts b/tests/greaterRealmOrdinaryYield.test.ts index da79b11a..ea70bbdf 100644 --- a/tests/greaterRealmOrdinaryYield.test.ts +++ b/tests/greaterRealmOrdinaryYield.test.ts @@ -33,5 +33,5 @@ describe('Greater Realm ordinary deterministic candidate yield', () => { if (candidate) clearGreaterRealmPrivateCandidateBuffers(candidate); } } - }, 30_000); + }, 60_000); }); diff --git a/tests/hermesAdminSecurity.test.ts b/tests/hermesAdminSecurity.test.ts index 11d8185c..f1170c4e 100644 --- a/tests/hermesAdminSecurity.test.ts +++ b/tests/hermesAdminSecurity.test.ts @@ -1486,7 +1486,7 @@ describe('Hermes credential destination policy', () => { expect(water.stdout).toContain('"credentialsAccessed":false'); expect(water.stdout).toContain('"mutationSubmitted":false'); expect(water.stderr).toBe(''); - }, 15_000); + }, 30_000); it('validates and dry-runs the resource backfill without credentials or network use', () => { const result = runHermes(['backfill-resources', '4', '--dry-run', '--confirm'], { diff --git a/vite.config.ts b/vite.config.ts index 5b0a8a82..cd0f5869 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -119,6 +119,12 @@ export default defineConfig(({ command }) => ({ test: { environment: 'jsdom', globals: true, + // Greater Realm invariants exercise several 100k–150k-cell candidates. + // Bound file-level concurrency so those CPU/memory-heavy proofs cannot + // starve unrelated five-second renderer, Git, or operator regressions in + // CI while still retaining parallel coverage. + maxWorkers: 4, + testTimeout: 10_000, // The Worker and SpacetimeDB module maintain their own isolated test // runners. Keeping the browser suite rooted here avoids running Node's // `node:test` module fixtures under jsdom/Vitest. From e1cf7f4fb85c02840b5a5aceddac745971e24ed0 Mon Sep 17 00:00:00 2001 From: Ael Date: Tue, 4 Aug 2026 19:24:36 +0200 Subject: [PATCH 4/6] feat(atlas): enforce organic Greater Realm composition --- .../design/greater-realm-natural-continent.md | 86 +- docs/evidence/greater-realm/README.md | 16 +- .../greater-realm-private-generation.md | 43 +- .../greater-realm-candidate-generator.ts | 549 +++++- .../atlas/greater-realm-candidate-package.ts | 105 +- scripts/atlas/greater-realm-cli.ts | 17 +- scripts/atlas/greater-realm-composition.ts | 1532 +++++++++++++++++ scripts/atlas/greater-realm-contracts.ts | 11 +- scripts/atlas/greater-realm-topography.ts | 89 + scripts/atlas/verify-public-boundary.mjs | 18 +- tests/greaterRealmAdvancedInvariants.test.ts | 4 +- tests/greaterRealmCandidateGenerator.test.ts | 4 +- tests/greaterRealmCandidatePackage.test.ts | 164 +- tests/greaterRealmCliSecurity.test.ts | 16 +- tests/greaterRealmComposition.test.ts | 611 +++++++ tests/greaterRealmOrdinaryYield.test.ts | 1 - tests/greaterRealmPrivateBoundary.test.ts | 2 +- tests/greaterRealmSanitizedReview.test.ts | 11 +- tests/greaterRealmTopography.test.ts | 7 +- 19 files changed, 3149 insertions(+), 137 deletions(-) create mode 100644 scripts/atlas/greater-realm-composition.ts create mode 100644 tests/greaterRealmComposition.test.ts diff --git a/docs/design/greater-realm-natural-continent.md b/docs/design/greater-realm-natural-continent.md index c4c9b8a6..1281cc81 100644 --- a/docs/design/greater-realm-natural-continent.md +++ b/docs/design/greater-realm-natural-continent.md @@ -4,7 +4,8 @@ Status: candidate-generation design only Atlas: `GENESIS_001_GREATER_REALM` -Generator: `greater-realm-v2-natural-continent-pr-a.3` +Generator algorithm: `greater-realm-v2-natural-continent-pr-a.5` +Terrain-seed namespace: `greater-realm-v2-natural-continent-pr-a.3` Production state: unchanged This document defines the offline, owner-reviewed candidate stage for a future @@ -60,8 +61,11 @@ hexes from every previously accepted slot, including the deployed Lowlands. Authority decisions use canonical cell order, integer or fixed-point fields, stable complete tie-breakers, and counter-addressed random channels. A sample -is a pure function of the private candidate seed, generator version, stage, -axial coordinate, and sample ordinal. There is no mutable random stream and no +is a pure function of the private candidate seed, stage, axial coordinate, and +sample ordinal. Root-seed ordinals derive that candidate seed through an +explicit terrain-seed namespace, which changes only for an intentional world +reroll; the separate generator algorithm version still identifies and binds +the package implementation. There is no mutable random stream and no `Math.random` in generation authority. Each candidate runs independently through these stage families: @@ -93,6 +97,10 @@ Each candidate runs independently through these stage families: curvature, wetness, exposure, coast/freshwater distance, watersheds, ridges, temperature, moisture, landforms, and biome classes; visual classifications cannot drift away from the process evidence that produced the terrain. + Generated forest components smaller than 28 connected cells resolve into + compatible meadow/heath transitions, leaving broad clustered woods and + meaningful open country. The frozen Lowlands surface is excluded from this + cleanup and from new-generator composition scoring. 8. Grow natural geographic basins, bind the working region identities by climate/process character, assign the fixed strategic graph, and align tier barriers and gate saddles with coherent highlands rather than radial bands. @@ -132,6 +140,25 @@ A candidate is ineligible if any of these proofs fail: - land does not retain a meaningful deep-ocean/fog buffer; - the topographic land mask does not contain 2–4 major landmasses and 3–8 large islands; +- the dominant continental system contains less than 55% or more than 90% of + land, lacks a meaningful secondary mass, or fails to carry at least 80% of + both Tier II and Tier III land. This explicitly implements the requested + primary-continent plus secondary-landmass policy rather than making a lone + monolith eligible; +- production-scale 64px/256px silhouette checks detect excessive rotational + similarity, long axial coast runs, implausible convex solidity, or either + over-smoothed or one-cell-noisy coast detail; +- the surrounding ocean is not saltwater at every active boundary cell, lacks + the global and 12-sector land clearance floors, or visually crowds the + fog-ready frame; +- generated forests are outside the 8%–42% dry-land envelope, remain dominated + by tiny/confetti components, lack at least three broad patches, or collapse + into one blanket mass. Glacial landform 15 and the immutable Lowlands patch + are not misclassified as generated forest; +- generated mountain authority is outside the 3%–28% dry-land envelope, lacks + at least two coherent systems, remains dominated by speckles or one massif, + or has no sufficiently long, anisotropic, off-centre belt. Ancient-stone + Lowlands cells are not counted as mountains; - region count, tier ratios, region balance, fixed adjacency, 18 gates, or 600 total castle capacity differs from the contract; - a Tier-II parent lacks a dry outer-frontier anchor joined to its dry inner @@ -183,16 +210,16 @@ route surfaces; oceans and lakes are not. This does not activate movement, bridges, ferries, or any cross-region mechanic. Castle sites and sealed gate endpoints themselves must still be dry. -Quality is a vector, not an automatic winner. After each complete private -package has been regenerated and byte-for-byte verified, the owner-only -comparison joins its public aggregate evidence to a coordinate-free private -metric vector. That vector spans outer-boundary/coastal artifacts, passable -region coherence and route fragmentation, throne-route clearance, chunk -population balance, geological and landform alignment, climate/coastal -compatibility, hydrology, and biome diversity/balance. Raw coordinates, seeds, -transforms, hidden-site identities, package paths, and digests never enter the -shortlist. The tooling may produce a diverse shortlist, but only the owner can -select a candidate. +Quality remains a vector, not an automatic decision. After the private package +has been regenerated and byte-for-byte verified, the owner-review record joins +its public aggregate evidence to coordinate-free private metrics. Those metrics +span outer-boundary/coastal artifacts, passable-region coherence and route +fragmentation, throne-route clearance, chunk population balance, geological +and landform alignment, climate/coastal compatibility, hydrology, biome +diversity/balance, multiscale silhouette, directional ocean clearance, and +forest/mountain clustering. Raw coordinates, seeds, transforms, hidden-site +identities, package paths, and digests never enter that record. The tool carries +no recommendation; only the owner can approve the candidate. ## Private and public outputs @@ -223,16 +250,29 @@ hidden sites, maps, screenshots, previews, paths, or reconstructive data. ## Review and future pull requests -The candidate batch contains 8–16 eligible worlds; twelve is the preferred -review set. Private owner review includes comparable silhouette, hillshade, -biome, hydrology, topology/fog, and mountain/gate views. The comparison tool -deterministically produces an unranked, diverse three-to-five-candidate -shortlist using Pareto/vector separation across verified private package -aggregates as well as the sanitized public metrics. It records `pending`, -carries no recommendation, and has no automatic-selection side effect. The -private shortlist stores only opaque candidate handles, objective directions, -and hard-constraint labels—never metric values or reconstructive material. No -scalar score makes the final choice. +Per the owner’s direction, PR A produces one eligible world for review, not an +eight-world comparison batch. Private owner review includes silhouette, +hillshade, biome, hydrology, region-topology/outer-ocean, and mountain/gate +views. The region view uses a fixed opaque fog exterior and review-only +outer-ocean bands; its watermark explicitly identifies it as a composition +proxy, not shipped runtime fog or server fog-of-war authority. The silhouette +follows topographic land at sea level, so rivers and streams remain features +within the landmass instead of punching false coastline gaps. + +Owner-supplied map references are composition guidance, never source assets or +pixel targets. Review asks whether the candidate has a strong irregular macro +landmass, meaningful bays, peninsulas and inland water, coherent forest and +mountain belts rather than confetti, hydrology that organizes the terrain, +substantial ocean breathing room, and an outer boundary fully consumed by fog. +Neither the land nor its fog envelope may resolve into a disc, hexagon, radial +flower or repeated rotated pattern under close inspection. + +The review tool deterministically emits a one-candidate, unranked private review +record from the verified private package and sanitized public metrics. It +records `pending`, carries no recommendation, and has no automatic-selection +side effect. The record stores only an opaque candidate handle, objective +directions, and hard-constraint labels—never metric values or reconstructive +material. No scalar score makes the final choice. Only an explicit owner approval may be recorded as a private selection receipt. After that approval, a separate pull request may bind the selected private diff --git a/docs/evidence/greater-realm/README.md b/docs/evidence/greater-realm/README.md index 8e3315e9..208d9f13 100644 --- a/docs/evidence/greater-realm/README.md +++ b/docs/evidence/greater-realm/README.md @@ -13,11 +13,11 @@ cell or chunk payloads, process fields, topography patches, layout/stage/package/private digests, exact region geometry, transforms, gates, sites, candidate previews, screenshots, or paths to the owner-review workspace. -The owner-only source packages, marked seed envelopes, exact chunk/topography -authority bindings, and six-map preview sets live outside every Git worktree -under a mode-0700 private workspace. Private comparison may create a -deterministic, unranked and diverse three-to-five-candidate shortlist, but it -cannot select a world. A candidate remains `pending` until the owner records a -separate explicit approval; no report in this directory is a selection, -activation, schema/runtime change, deployment instruction, or production -record. PR A leaves the deployed Lowlands untouched. +The owner-only source package, marked seed envelopes, exact chunk/topography +authority bindings, and six-map preview set live outside every Git worktree +under a mode-0700 private workspace. Per the owner's direction, PR A creates +one eligible world and a deterministic, unranked single-candidate review +record; it cannot select the world. The candidate remains `pending` until the +owner records a separate explicit approval; no report in this directory is a +selection, activation, schema/runtime change, deployment instruction, or +production record. PR A leaves the deployed Lowlands untouched. diff --git a/docs/security/greater-realm-private-generation.md b/docs/security/greater-realm-private-generation.md index fd54cb0f..a661205b 100644 --- a/docs/security/greater-realm-private-generation.md +++ b/docs/security/greater-realm-private-generation.md @@ -41,8 +41,9 @@ The private workspace must: - store batch and candidate seeds in strict, type-tagged private envelopes so renamed seed files retain a scan-visible private marker while derivation continues to use only the extracted 32-byte payload; -- bind every package to generator version, source commit, parameters, seed - identity, stage digests, and exact file inventory; +- bind every package to generator algorithm version, the independently pinned + terrain-seed namespace, source commit, parameters, seed identity, stage + digests, and exact file inventory; - bind every private chunk to its canonical cell-index digest and full authority field payload, then bind its referenced topography patch to the same cells, generation/topography/partition versions, exact process-and-derived field @@ -68,6 +69,14 @@ serialization or persistence. Private candidate retirement also clears the coordinate lookup captured by its indexed grid before clearing canonical field arrays. +Natural-composition review derives only coordinate-free scalar summaries and +five public proof booleans from final private terrain authority. Temporary land, +saltwater, dry-ground, forest, mountain, distance, component, queue, and raster +buffers are scoped to the measurement and overwritten on both success and +failure. Owner-supplied visual references remain outside the repository and are +used only as review criteria; their files, names, paths, and pixels never enter +a candidate package or sanitized report. + This is defense in depth, not a secure-erasure guarantee: V8 strings and garbage-collected/native-library copies cannot be reliably overwritten, and unlinking cannot erase copies retained by journaled, copy-on-write, or SSD @@ -165,6 +174,15 @@ passes use double buffers rather than scan-order mutation. Persistable fields use checked integers/fixed point; visual previews may use floating-point presentation only. +The generator algorithm version and deterministic terrain-seed namespace are +separate authorities. Algorithm/package revisions may change how a rejected +candidate is evaluated without silently rerolling every root-seed ordinal; a +world reroll requires an explicit seed-namespace change. The private manifest +binds both values, and verification re-derives the candidate seed from the +declared namespace. The namespace constant is public source, but its package +binding is owner-only provenance and is not admitted to sanitized candidate +evidence. + Every batch attempt ordinal is regenerated before review. The rejection ledger is an exact discriminated union: a completed candidate may record a `proof-rejection` with its active-cell count and failed proofs, while only an @@ -181,17 +199,18 @@ match the regenerated marked PNG before its bounded decode/dimension check. A candidate generated alone, first, or last must have the same authoritative output. -Comparison is also deterministic but is not selection. Only metrics captured -from regenerated, byte-for-byte verified private candidate packages may enter -the comparison vector. It covers coordinate-free outer-boundary/coastal, +Owner review is deterministic but is not selection. Only metrics captured from +the regenerated, byte-for-byte verified private candidate package may enter its +review vector. It covers coordinate-free outer-boundary/coastal, route/playability, chunk-balance, geological/topographic, climate/landform, and -biome axes. It may write only a private, unranked three-to-five-candidate -shortlist containing opaque handles and axis/constraint labels—not the metric -values—after enforcing route redundancy, 4–8-cell barrier width, zero -incompatible visual adjacencies, and zero incompatible biome/landform pairs. -The shortlist remains `selectionStatus: pending`, with no recommendation and no -automatic selection. Recording a choice requires a separate explicit owner -approval and private receipt; PR A does not record one. +biome axes. Per the owner’s one-world direction, it writes a private, unranked +single-candidate review record containing one opaque handle and +axis/constraint labels—not metric values—after enforcing route redundancy, +4–8-cell barrier width, zero incompatible visual adjacencies, and zero +incompatible biome/landform pairs. The record remains +`selectionStatus: pending`, with no recommendation and no automatic selection. +Recording a choice requires a separate explicit owner approval and private +receipt; PR A does not record one. ## Required negative tests diff --git a/scripts/atlas/greater-realm-candidate-generator.ts b/scripts/atlas/greater-realm-candidate-generator.ts index 36269684..840c034e 100644 --- a/scripts/atlas/greater-realm-candidate-generator.ts +++ b/scripts/atlas/greater-realm-candidate-generator.ts @@ -38,8 +38,16 @@ import { import { deriveGreaterRealmTopography, } from './greater-realm-topography'; +import { + measureGreaterRealmNaturalComposition, + type GreaterRealmNaturalCompositionMetrics, +} from './greater-realm-composition'; export const GREATER_REALM_GENERATOR_VERSION = + 'greater-realm-v2-natural-continent-pr-a.5' as const; +// Package/algorithm revisions must not silently reroll root-seed ordinals. +// Bump this namespace only for an explicitly approved deterministic world reroll. +export const GREATER_REALM_TERRAIN_SEED_NAMESPACE = 'greater-realm-v2-natural-continent-pr-a.3' as const; export const GREATER_REALM_PRIVATE_PACKAGE_MAGIC = 'WKGR-PRIVATE-ATLAS-V1' as const; export const GREATER_REALM_PRIVATE_MANIFEST_KIND = @@ -229,6 +237,7 @@ export type GreaterRealmPrivateCandidate = Readonly<{ highlandBarrierShareBasisPoints: number; barrierMeanElevationAdvantage: number; barrierMeanUpliftAdvantage: number; + naturalComposition: GreaterRealmNaturalCompositionMetrics; geomorphology: GreaterRealmGeomorphologyMetrics; throneAnchorBarrierClearance: number; tierThreePassableLandCells: number; @@ -276,7 +285,7 @@ function integerSquareRoot(value: number): number { return high; } -function deriveCandidateSeedMaterial( +export function deriveGreaterRealmCandidateSeedMaterial( rootSeed: Uint8Array, candidateOrdinal: number, ): Buffer { @@ -285,7 +294,7 @@ function deriveCandidateSeedMaterial( fail('GREATER_REALM_CANDIDATE_ORDINAL_INVALID'); } return createHmac('sha256', rootSeed) - .update(GREATER_REALM_GENERATOR_VERSION, 'utf8') + .update(GREATER_REALM_TERRAIN_SEED_NAMESPACE, 'utf8') .update('\0candidate\0', 'utf8') .update(String(candidateOrdinal), 'utf8') .digest(); @@ -2752,44 +2761,83 @@ function repairNaturalRegionLandCoherence( } } const transfer = foothold.filter(cell => keep[cell] !== 1); - const contacts = new Uint32Array(REGION_COUNT); - for (const cell of transfer) { - for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { - const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + const transferMask = new Uint8Array(grid.cellCount); + for (const cell of transfer) transferMask[cell] = 1; + const transferPieces = [...connectedComponents(grid, transferMask)] + .sort((first, second) => first[0]! - second[0]!); + const assignments: Array> = []; + const swapCounts = new Uint32Array(REGION_COUNT); + let assignmentValid = transferPieces.length > 0; + for (const cells of transferPieces) { + const contacts = new Uint32Array(REGION_COUNT); + for (const cell of cells) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && strategicallyPassableSurface(waterRegime[neighbor]!) + && tierId[neighbor] === tierId[cell] + && regionId[neighbor] !== sourceRegion + ) contacts[regionId[neighbor]!] += 1; + } + } + let targetRegion = -1; + let bestContacts = 0; + for (let region = 0; region < REGION_COUNT; region += 1) { if ( - neighbor >= 0 - && tierId[neighbor] === tierId[cell] - && regionId[neighbor] !== sourceRegion - ) contacts[regionId[neighbor]!] += 1; + region !== sourceRegion + && GREATER_REALM_REGION_SPECS[region]!.tier + === GREATER_REALM_REGION_SPECS[sourceRegion]!.tier + && contacts[region]! > bestContacts + ) { + targetRegion = region; + bestContacts = contacts[region]!; + } + } + if (targetRegion < 0) { + assignmentValid = false; + break; } + assignments.push(Object.freeze({ cells, targetRegion })); + swapCounts[targetRegion] += cells.length; } - let targetRegion = -1; - for (let region = 0; region < REGION_COUNT; region += 1) { - if ( - region !== sourceRegion - && GREATER_REALM_REGION_SPECS[region]!.tier - === GREATER_REALM_REGION_SPECS[sourceRegion]!.tier - && (targetRegion < 0 || contacts[region]! > contacts[targetRegion]!) - ) targetRegion = region; + // Validate every connected piece and every compensating water swap + // before the first political ownership mutation. + const plannedWaterSwaps = new Map(); + if (assignmentValid) { + for (let region = 0; region < REGION_COUNT; region += 1) { + const swapCount = swapCounts[region]!; + if (swapCount === 0) continue; + const waterSwapCandidates = Array.from( + { length: grid.cellCount }, + (_, cell) => cell, + ).filter(cell => ( + regionId[cell] === region + && !strategicallyPassableSurface(waterRegime[cell]!) + && legacyProtectedCell[cell] === 0 + )); + if (waterSwapCandidates.length < swapCount) { + assignmentValid = false; + break; + } + plannedWaterSwaps.set( + region, + Object.freeze(waterSwapCandidates.slice(0, swapCount)), + ); + } } - if (targetRegion >= 0) { - const waterSwapCandidates = Array.from( - { length: grid.cellCount }, - (_, cell) => cell, - ).filter(cell => ( - regionId[cell] === targetRegion - && !strategicallyPassableSurface(waterRegime[cell]!) - && legacyProtectedCell[cell] === 0 - )); - if (waterSwapCandidates.length >= transfer.length) { - waterSwapCandidates.sort((first, second) => first - second); - for (const cell of transfer) { + if (assignmentValid) { + for (const { cells, targetRegion } of assignments) { + for (const cell of cells) { regionId[cell] = targetRegion; regionRepairLocked[cell] = 1; } - for (let index = 0; index < transfer.length; index += 1) { - regionId[waterSwapCandidates[index]!] = sourceRegion; - } + } + for (const cells of plannedWaterSwaps.values()) { + for (const cell of cells) regionId[cell] = sourceRegion; } } } @@ -4426,7 +4474,11 @@ function reconcileBarrierMeasuredRegionCoherence( } } const detachable = component.filter(cell => keep[cell] === 0); - return detachable.length === 0 ? undefined : Object.freeze(detachable); + if (detachable.length === 0) return undefined; + const detachableMask = new Uint8Array(grid.cellCount); + for (const cell of detachable) detachableMask[cell] = 1; + return [...connectedComponents(grid, detachableMask)] + .sort((first, second) => second.length - first.length || first[0]! - second[0]!)[0]; }; // Four fixed sweeps let a recipient consolidate a component donated after @@ -4499,17 +4551,21 @@ function reconcileBarrierMeasuredRegionCoherence( let targetRegion = -1; let bestContacts = -1; let bestProjectedShare = -1; + let selectedJoinedSize = -1; + let selectedProjectedLargest = -1; for (let region = 0; region < REGION_COUNT; region += 1) { if ( region === sourceRegion || GREATER_REALM_REGION_SPECS[region]!.tier !== GREATER_REALM_REGION_SPECS[sourceRegion]!.tier || availableSwapWater[region]! < transferableComponent.length + || contacts[region] === 0 ) continue; const projectedTotal = topology.passableCounts[region]! + transferableComponent.length; + const joinedRecipientComponents = [...contactedComponents[region]!]; const joinedSize = transferableComponent.length - + [...contactedComponents[region]!] + + joinedRecipientComponents .reduce((sum, componentId) => sum + topology.componentSizes[componentId]!, 0); const projectedLargest = Math.max(topology.largestCounts[region]!, joinedSize); const projectedShare = projectedTotal === 0 @@ -4530,6 +4586,8 @@ function reconcileBarrierMeasuredRegionCoherence( targetRegion = region; bestContacts = contacts[region]!; bestProjectedShare = projectedShare; + selectedJoinedSize = joinedSize; + selectedProjectedLargest = projectedLargest; } } if (targetRegion < 0) continue; @@ -4554,9 +4612,42 @@ function reconcileBarrierMeasuredRegionCoherence( }; return sourceContacts(second) - sourceContacts(first) || first - second; }); + const waterSwaps = waterSwapCandidates.slice(0, transferableComponent.length); for (const cell of transferableComponent) regionId[cell] = targetRegion; - for (let index = 0; index < transferableComponent.length; index += 1) { - regionId[waterSwapCandidates[index]!] = sourceRegion; + for (const cell of waterSwaps) regionId[cell] = sourceRegion; + const projectedTopology = passableRegionTopology( + grid, + regionId, + waterRegime, + barrier, + ); + const transferRepresentative = transferableComponent[0]!; + const transferTargetComponent = projectedTopology.componentId[transferRepresentative]!; + const projectedRegionCounts = Array(REGION_COUNT).fill(0); + for (const assignedRegion of regionId) projectedRegionCounts[assignedRegion] += 1; + const projectionExact = regionId[transferRepresentative] === targetRegion + && transferTargetComponent >= 0 + && projectedTopology.componentSizes[transferTargetComponent] === selectedJoinedSize + && projectedTopology.passableCounts[targetRegion] + === topology.passableCounts[targetRegion]! + transferableComponent.length + && projectedTopology.passableCounts[sourceRegion] + === topology.passableCounts[sourceRegion]! - transferableComponent.length + && projectedTopology.largestCounts[targetRegion] === selectedProjectedLargest + && projectedRegionCounts.every((count, region) => count === initialCounts[region]) + && projectedTopology.passableCounts.every((count, region) => ( + region === sourceRegion + || region === targetRegion + || ( + count === topology.passableCounts[region] + && projectedTopology.largestCounts[region] === topology.largestCounts[region] + ) + )); + if (!projectionExact) { + // A projection is advisory until the recomputed topology matches it + // exactly; rollback both sides before considering another transfer. + for (const cell of transferableComponent) regionId[cell] = sourceRegion; + for (const cell of waterSwaps) regionId[cell] = targetRegion; + continue; } remainingTotal -= transferableComponent.length; if (component.length < 64) remainingMinor -= transferableComponent.length; @@ -4794,10 +4885,27 @@ function barriersAndGates( } const gates: GreaterRealmPrivateGate[] = []; const baseRobustTopology = robustRegionTopology(grid, regionId, waterRegime, barrier); + const preGateBarrier = new Uint8Array(barrier); const usedGateCells = new Set(); const protectedApproachCells = new Set(); const requiredApproachCells = new Set(); const gateChannel = greaterRealmTerrainChannelId('sealed-gate-saddle'); + type GateApproaches = Readonly<{ + first: readonly number[]; + firstAlternate: readonly number[]; + second: readonly number[]; + secondAlternate: readonly number[]; + all: readonly (readonly number[])[]; + }>; + type GateSelection = Readonly<{ + edge: readonly [number, number]; + approaches: GateApproaches; + }>; + type GateApproachCandidate = Readonly<{ + edge: readonly [number, number]; + first: readonly (readonly number[])[]; + second: readonly (readonly number[])[]; + }>; for (const [firstRegion, secondRegion] of gateGraph) { const key = `${Math.min(firstRegion, secondRegion)}:${Math.max(firstRegion, secondRegion)}`; const candidates = [...(edgesByPair.get(key) ?? [])].filter(([first, second]) => ( @@ -4818,21 +4926,6 @@ function barriersAndGates( || firstA - firstB || secondA - secondB; }); - type GateSelection = Readonly<{ - edge: readonly [number, number]; - approaches: Readonly<{ - first: readonly number[]; - firstAlternate: readonly number[]; - second: readonly number[]; - secondAlternate: readonly number[]; - all: readonly (readonly number[])[]; - }>; - }>; - type GateApproachCandidate = Readonly<{ - edge: readonly [number, number]; - first: readonly (readonly number[])[]; - second: readonly (readonly number[])[]; - }>; const approachCandidates: GateApproachCandidate[] = []; for (const edge of candidates) { if ( @@ -5033,6 +5126,289 @@ function barriersAndGates( })); } } + const greedyGateAssignmentComplete = gateGraph.every(([firstRegion, secondRegion]) => ( + gates.filter(gate => ( + gate.firstRegion === firstRegion && gate.secondRegion === secondRegion + )).length === 2 + )); + if (!greedyGateAssignmentComplete) { + // The ordinary path above remains authoritative whenever it succeeds. + // Only an incomplete greedy assignment reaches this transactional fallback; + // all search state is derived from the immutable pre-gate barrier and is + // committed only after all nine region pairs have two valid gates. + const MAX_OPTIONS_PER_EDGE = 32; + const MAX_CANDIDATE_EDGES_PER_PAIR = 128; + const MAX_BUNDLES_PER_PAIR = 128; + const MAX_PAIR_ENUMERATION_NODES = 131_072; + const MAX_GLOBAL_SEARCH_NODES = 32_768; + type GateBundle = Readonly<{ + graphIndex: number; + selections: readonly [GateSelection, GateSelection]; + }>; + const selectionsAreGloballyCompatible = ( + selections: readonly GateSelection[], + ): boolean => { + const gateMate = new Map(); + for (const { edge: [firstEndpoint, secondEndpoint] } of selections) { + if (gateMate.has(firstEndpoint) || gateMate.has(secondEndpoint)) return false; + gateMate.set(firstEndpoint, secondEndpoint); + gateMate.set(secondEndpoint, firstEndpoint); + } + const carved = new Set(); + for (const selection of selections) { + for (const path of selection.approaches.all) { + for (const cell of path) { + if (gateMate.has(cell)) return false; + carved.add(cell); + } + } + } + // Opening any selected endpoint must expose only its reviewed mate. A + // neighbouring gate endpoint or required approach would become a second + // entrance and therefore invalidates the whole partial assignment. + for (const [endpoint, mate] of gateMate) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || neighbor === mate + || tierId[endpoint] === tierId[neighbor] + || !strategicallyPassableSurface(waterRegime[endpoint]!) + || !strategicallyPassableSurface(waterRegime[neighbor]!) + ) continue; + if (gateMate.has(neighbor) || carved.has(neighbor)) return false; + } + } + // Pair-local validation is insufficient when routes from different graph + // arms combine. Re-evaluate the complete carved set at every DFS node so + // two individually safe bundles cannot jointly open an ungated bypass. + for (const cell of carved) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 + && tierId[cell] !== tierId[neighbor] + && strategicallyPassableSurface(waterRegime[cell]!) + && strategicallyPassableSurface(waterRegime[neighbor]!) + && (preGateBarrier[cell] === 0 || carved.has(cell)) + && (preGateBarrier[neighbor] === 0 || carved.has(neighbor)) + ) return false; + } + } + return true; + }; + const enumeratePairBundles = ( + graphIndex: number, + firstRegion: number, + secondRegion: number, + ): readonly GateBundle[] => { + const key = `${Math.min(firstRegion, secondRegion)}:${Math.max(firstRegion, secondRegion)}`; + const candidates = [...(edgesByPair.get(key) ?? [])].filter(([first, second]) => ( + waterRegime[first] === 0 + && waterRegime[second] === 0 + && preGateBarrier[first] === 1 + && preGateBarrier[second] === 1 + )); + candidates.sort(([firstA, secondA], [firstB, secondB]) => { + const score = (first: number, second: number) => elevation[first]! + elevation[second]! + + (greaterRealmCounterRandomU32( + candidateSeed, + gateChannel, + grid.q[first]!, + grid.r[first]!, + ) % 4_001); + return score(firstA, secondA) - score(firstB, secondB) + || firstA - firstB + || secondA - secondB; + }); + const options: Array> = []; + for ( + let candidateIndex = 0; + candidateIndex < Math.min(candidates.length, MAX_CANDIDATE_EDGES_PER_PAIR); + candidateIndex += 1 + ) { + const edge = candidates[candidateIndex]!; + const firstApproaches = barrierApproachPaths( + grid, + edge[0], + firstRegion, + regionId, + waterRegime, + preGateBarrier, + baseRobustTopology.componentId, + baseRobustTopology.componentSizes, + ); + const secondApproaches = barrierApproachPaths( + grid, + edge[1], + secondRegion, + regionId, + waterRegime, + preGateBarrier, + baseRobustTopology.componentId, + baseRobustTopology.componentSizes, + ); + if (!firstApproaches || !secondApproaches) continue; + const approachOptions: GateApproaches[] = []; + for (let optionOrdinal = 0; optionOrdinal < MAX_OPTIONS_PER_EDGE; optionOrdinal += 1) { + const approaches = compatibleGateApproaches( + grid, + tierId, + waterRegime, + preGateBarrier, + baseRobustTopology.componentId, + baseRobustTopology.componentSizes, + firstApproaches, + secondApproaches, + optionOrdinal, + ); + if (!approaches) break; + approachOptions.push(approaches); + } + if (approachOptions.length > 0) { + options.push(Object.freeze({ + edge, + approaches: Object.freeze(approachOptions), + })); + } + } + const bundles: GateBundle[] = []; + const endpointQuartetCounts = new Map(); + let enumerationNodes = 0; + pairEnumeration: + for (let firstIndex = 0; firstIndex < options.length; firstIndex += 1) { + const first = options[firstIndex]!; + for (let secondIndex = firstIndex + 1; secondIndex < options.length; secondIndex += 1) { + const second = options[secondIndex]!; + const endpoints = [...first.edge, ...second.edge]; + if (new Set(endpoints).size !== 4) continue; + const quartetKey = [...endpoints].sort((left, right) => left - right).join(':'); + const maximumDiagonal = first.approaches.length + second.approaches.length - 2; + for (let diagonal = 0; diagonal <= maximumDiagonal; diagonal += 1) { + const firstOptionMaximum = Math.min(diagonal, first.approaches.length - 1); + const firstOptionMinimum = Math.max(0, diagonal - second.approaches.length + 1); + for ( + let firstOption = firstOptionMaximum; + firstOption >= firstOptionMinimum; + firstOption -= 1 + ) { + const secondOption = diagonal - firstOption; + if (enumerationNodes >= MAX_PAIR_ENUMERATION_NODES) break pairEnumeration; + enumerationNodes += 1; + if ((endpointQuartetCounts.get(quartetKey) ?? 0) >= 2) continue; + const firstApproaches = first.approaches[firstOption]!; + const secondApproaches = second.approaches[secondOption]!; + const selections = Object.freeze([ + Object.freeze({ edge: first.edge, approaches: firstApproaches }), + Object.freeze({ edge: second.edge, approaches: secondApproaches }), + ] as const); + if (!selectionsAreGloballyCompatible(selections)) continue; + endpointQuartetCounts.set( + quartetKey, + (endpointQuartetCounts.get(quartetKey) ?? 0) + 1, + ); + bundles.push(Object.freeze({ graphIndex, selections })); + if (bundles.length >= MAX_BUNDLES_PER_PAIR) break pairEnumeration; + } + } + } + } + return Object.freeze(bundles); + }; + const plans = gateGraph.map(([firstRegion, secondRegion], graphIndex) => Object.freeze({ + graphIndex, + bundles: enumeratePairBundles(graphIndex, firstRegion, secondRegion), + })); + let fallbackSolution: readonly GateBundle[] | undefined; + if (plans.every(plan => plan.bundles.length > 0)) { + const constrainedPlans = [...plans].sort((first, second) => ( + first.bundles.length - second.bundles.length + || first.graphIndex - second.graphIndex + )); + const chosen: GateBundle[] = []; + let searchNodes = 0; + const search = (depth: number): boolean => { + if (depth === constrainedPlans.length) { + fallbackSolution = Object.freeze([...chosen]); + return true; + } + const plan = constrainedPlans[depth]!; + for (const bundle of plan.bundles) { + if (searchNodes >= MAX_GLOBAL_SEARCH_NODES) return false; + searchNodes += 1; + const selections = [ + ...chosen.flatMap(selected => selected.selections), + ...bundle.selections, + ]; + if (!selectionsAreGloballyCompatible(selections)) continue; + chosen.push(bundle); + if (search(depth + 1)) return true; + chosen.pop(); + } + return false; + }; + search(0); + } + if (fallbackSolution?.length === gateGraph.length) { + const solutionByGraphIndex = new Map( + fallbackSolution.map(bundle => [bundle.graphIndex, bundle] as const), + ); + // Selected greedy routes are private coordinate buffers. Retire them + // before replacing the incomplete assignment, then restore the exact + // pre-gate barrier and commit the complete solution in graph order. + const retiredPaths = new Set(); + for (const gate of gates) { + for (const path of [ + gate.firstApproachPath, + gate.firstAlternateApproachPath, + gate.secondApproachPath, + gate.secondAlternateApproachPath, + ]) retiredPaths.add(path); + } + for (const path of retiredPaths) (path as number[]).fill(0); + barrier.set(preGateBarrier); + gates.length = 0; + usedGateCells.clear(); + protectedApproachCells.clear(); + requiredApproachCells.clear(); + for (let graphIndex = 0; graphIndex < gateGraph.length; graphIndex += 1) { + const bundle = solutionByGraphIndex.get(graphIndex)!; + const [firstRegion, secondRegion] = gateGraph[graphIndex]!; + for (const selection of bundle.selections) { + usedGateCells.add(selection.edge[0]); + usedGateCells.add(selection.edge[1]); + for (const cell of selection.approaches.first) requiredApproachCells.add(cell); + for (const cell of selection.approaches.firstAlternate) { + requiredApproachCells.add(cell); + } + for (const cell of selection.approaches.second) requiredApproachCells.add(cell); + for (const cell of selection.approaches.secondAlternate) { + requiredApproachCells.add(cell); + } + for (const path of selection.approaches.all) { + for (const cell of path) { + barrier[cell] = 0; + protectedApproachCells.add(cell); + } + } + gates.push(Object.freeze({ + gateIndex: gates.length, + firstRegion, + secondRegion, + firstCell: selection.edge[0], + secondCell: selection.edge[1], + firstApproachPath: selection.approaches.first, + firstAlternateApproachPath: selection.approaches.firstAlternate, + secondApproachPath: selection.approaches.second, + secondAlternateApproachPath: selection.approaches.secondAlternate, + })); + } + } + } + } // Approach carving for a later pass may cross the shoulder of an earlier // pass in the same region. Gate endpoints themselves are immutable sealed // barrier cells; restore that invariant before graph/topology proofs. @@ -6109,7 +6485,10 @@ export function generateGreaterRealmCandidate(input: Readonly<{ rootSeed: Uint8Array; candidateOrdinal: number; }>): GreaterRealmPrivateCandidate { - const seedMaterial = deriveCandidateSeedMaterial(input.rootSeed, input.candidateOrdinal); + const seedMaterial = deriveGreaterRealmCandidateSeedMaterial( + input.rootSeed, + input.candidateOrdinal, + ); const candidateSeed = deriveCandidateSeed(seedMaterial); try { const canvas = greaterRealmPrivateCanvasAuthority(); @@ -6293,8 +6672,8 @@ export function generateGreaterRealmCandidate(input: Readonly<{ grid, geologicalBarrierMask, ); - const mountainSystems = barrierComponents.filter(component => component.length >= 64); - const continuousBarrierCells = mountainSystems.reduce( + const barrierSystems = barrierComponents.filter(component => component.length >= 64); + const continuousBarrierCells = barrierSystems.reduce( (total, component) => total + component.length, 0, ); @@ -6322,6 +6701,22 @@ export function generateGreaterRealmCandidate(input: Readonly<{ reconciled.flowReceiver, reconciled.filledElevation, ); + const naturalComposition = measureGreaterRealmNaturalComposition({ + grid, + canvasRadius: PRIVATE_CANVAS_RADIUS, + elevation: reconciled.elevation, + tierId: strategy.tierId, + waterRegime: surface.waterRegime, + biomeId: topography.biomeId, + legacyProtectedCell: legacy.protectedCell, + ridgeId: topography.ridgeId, + landformId: topography.landformId, + slope: topography.slope, + seaLevel: SEA_LEVEL, + dryWaterRegime: WATER_DRY, + oceanWaterRegime: WATER_OCEAN, + seaWaterRegime: WATER_SEA, + }); const proofs = Object.freeze({ activeMaskConnected: allActiveConnected(grid) && activeMaskHasNoEnclosedVoids(canvas, maskResult.mask), @@ -6377,6 +6772,11 @@ export function generateGreaterRealmCandidate(input: Readonly<{ && strategicShape.tendrilProof, regionPassableLand: strategicBarrier.passableRegionProof, regionGraph: strategicBarrier.regionGraphProof, + naturalLandSilhouette: naturalComposition.landSilhouette.proof, + dominantContinentComposition: naturalComposition.dominantContinent.proof, + deepOceanBreathingRoom: naturalComposition.oceanBreathingRoom.proof, + forestPatchComposition: naturalComposition.forestPatches.proof, + mountainSystemComposition: naturalComposition.mountainSystems.proof, }); const hardGates = Object.freeze({ ...Object.fromEntries(Object.entries(proofs).map(([key, value]) => [ @@ -6416,6 +6816,38 @@ export function generateGreaterRealmCandidate(input: Readonly<{ 0, 10_000, ); + const legacyBoundaryNaturalnessBasisPoints = clamp( + 10_000 - axialArtifactBasisPoints, + 0, + 10_000, + ); + const compositionNaturalnessBasisPoints = Math.round(( + clamp( + 10_000 - naturalComposition.landSilhouette.raster256.rotationalIouBasisPoints, + 0, + 10_000, + ) + + clamp( + 10_000 + - naturalComposition.landSilhouette.maximumAlignedCoastRunShareBasisPoints, + 0, + 10_000, + ) + + clamp( + 10_000 - Math.abs( + naturalComposition.landSilhouette.dominantLandSolidityBasisPoints - 7_900, + ) * 5, + 0, + 10_000, + ) + + clamp( + naturalComposition.oceanBreathingRoom.boundaryLandDistanceP50 * 400, + 0, + 10_000, + ) + + naturalComposition.forestPatches.clusteredShareBasisPoints + + naturalComposition.mountainSystems.clusteredShareBasisPoints + ) / 6); const scoreRange = (value: number, minimum: number, maximum: number) => { if (value >= minimum && value <= maximum) return 10_000; const miss = value < minimum ? minimum - value : value - maximum; @@ -6451,7 +6883,7 @@ export function generateGreaterRealmCandidate(input: Readonly<{ }), geology: Object.freeze({ pseudoTectonicDomains: domains.length, - mountainSystems: mountainSystems.length, + mountainSystems: naturalComposition.mountainSystems.broadComponentCount, watersheds: finalHydrology.watersheds, }), topography: Object.freeze({ @@ -6489,7 +6921,9 @@ export function generateGreaterRealmCandidate(input: Readonly<{ topography.biomeMetrics.incompatibleBiomeLandformPairCount, }), quality: Object.freeze({ - naturalnessBasisPoints: clamp(10_000 - axialArtifactBasisPoints, 0, 10_000), + naturalnessBasisPoints: Math.round(( + legacyBoundaryNaturalnessBasisPoints + compositionNaturalnessBasisPoints + ) / 2), axialArtifactBasisPoints, ridgeContinuityBasisPoints, hydrologyCoherenceBasisPoints, @@ -6658,6 +7092,7 @@ export function generateGreaterRealmCandidate(input: Readonly<{ strategicBarrier.highlandBarrierShareBasisPoints, barrierMeanElevationAdvantage: strategicBarrier.barrierMeanElevationAdvantage, barrierMeanUpliftAdvantage: strategicBarrier.barrierMeanUpliftAdvantage, + naturalComposition, geomorphology: geomorphology.metrics, throneAnchorBarrierClearance: throne.barrierClearance, tierThreePassableLandCells, diff --git a/scripts/atlas/greater-realm-candidate-package.ts b/scripts/atlas/greater-realm-candidate-package.ts index 42cd04de..e5690513 100644 --- a/scripts/atlas/greater-realm-candidate-package.ts +++ b/scripts/atlas/greater-realm-candidate-package.ts @@ -1,4 +1,4 @@ -import { createHash, createHmac, timingSafeEqual } from 'node:crypto'; +import { createHash, timingSafeEqual } from 'node:crypto'; import { lstatSync, realpathSync } from 'node:fs'; import { createRequire } from 'node:module'; import { basename, dirname, relative, resolve, sep } from 'node:path'; @@ -8,7 +8,9 @@ import { GREATER_REALM_PRIVATE_MANIFEST_KIND, GREATER_REALM_PRIVATE_PACKAGE_MAGIC, GREATER_REALM_REGION_SPECS, + GREATER_REALM_TERRAIN_SEED_NAMESPACE, clearGreaterRealmCandidateSecret, + deriveGreaterRealmCandidateSeedMaterial, generateGreaterRealmCandidate, type GreaterRealmPrivateCandidate, } from './greater-realm-candidate-generator'; @@ -48,6 +50,14 @@ const PRIVATE_PREVIEW_TEXT_KEY = 'WarpkeepPrivate'; const PRIVATE_PREVIEW_MODES = [ 'silhouette', 'hillshade', 'biome', 'hydrology', 'regions', 'mountain-gates', ] as const; +const PRIVATE_PREVIEW_SEA_LEVEL = 0; +const PRIVATE_PREVIEW_WATER_DRY = 0; +const PRIVATE_PREVIEW_WATER_OCEAN = 1; +const PRIVATE_PREVIEW_WATER_LAKE = 2; +const PRIVATE_PREVIEW_WATER_RIVER = 3; +const PRIVATE_PREVIEW_WATER_STREAM = 4; +const PRIVATE_PREVIEW_WATER_SEA = 5; +const PRIVATE_PREVIEW_FOG_COLOR = Object.freeze([24, 22, 31] as const); const PRIVATE_CANVAS_RADIUS = 270; const PRIVATE_CHUNK_AXIS_SPAN = 15; const PRIVATE_CHUNK_PARTITION_VERSION = 'axial-bin-15-v1' as const; @@ -107,6 +117,7 @@ const PRIVATE_MANIFEST_KEYS = Object.freeze([ 'provenanceDigest', 'regionSpecs', 'seedDigest', + 'seedNamespace', 'sourceCommit', 'stageDigests', 'tierOneSemanticPermutation', @@ -447,11 +458,7 @@ function deriveCandidateSeedMaterial( || !Number.isSafeInteger(candidateOrdinal) || candidateOrdinal < 0 ) fail('GREATER_REALM_PRIVATE_PROVENANCE_INVALID'); - return createHmac('sha256', batchSeed) - .update(GREATER_REALM_GENERATOR_VERSION, 'utf8') - .update('\0candidate\0', 'utf8') - .update(String(candidateOrdinal), 'utf8') - .digest(); + return deriveGreaterRealmCandidateSeedMaterial(batchSeed, candidateOrdinal); } function privateProvenanceDigest(input: Readonly<{ @@ -468,6 +475,8 @@ function privateProvenanceDigest(input: Readonly<{ .update('\0', 'utf8') .update(GREATER_REALM_GENERATOR_VERSION, 'utf8') .update('\0', 'utf8') + .update(GREATER_REALM_TERRAIN_SEED_NAMESPACE, 'utf8') + .update('\0', 'utf8') .update(String(input.candidateOrdinal), 'utf8') .update('\0', 'utf8') .update(input.seedMaterial) @@ -1216,6 +1225,40 @@ function verifiedPrivateShortlistMetrics( }); } +function privatePreviewDistanceToTopographicLand( + candidate: GreaterRealmPrivateCandidate, +): Uint16Array { + const distance = new Uint16Array(candidate.grid.cellCount); + distance.fill(0xffff); + const queue = new Uint32Array(candidate.grid.cellCount); + let head = 0; + let tail = 0; + let completed = false; + try { + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if (candidate.elevation[cell]! <= PRIVATE_PREVIEW_SEA_LEVEL) continue; + distance[cell] = 0; + queue[tail++] = cell; + } + if (tail === 0) fail('GREATER_REALM_PRIVATE_PREVIEW_LAND_MISSING'); + while (head < tail) { + const cell = queue[head++]!; + const nextDistance = distance[cell]! + 1; + for (let direction = 0; direction < 6; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * 6 + direction]!; + if (neighbor < 0 || distance[neighbor]! <= nextDistance) continue; + distance[neighbor] = nextDistance; + queue[tail++] = neighbor; + } + } + completed = true; + return distance; + } finally { + queue.fill(0); + if (!completed) distance.fill(0); + } +} + export async function renderGreaterRealmPrivatePreview( candidate: GreaterRealmPrivateCandidate, mode: GreaterRealmPrivatePreviewMode, @@ -1228,7 +1271,14 @@ export async function renderGreaterRealmPrivatePreview( const pixels = Buffer.alloc(width * height * 4, 0); let watermark: Buffer | undefined; let encoded: Buffer | undefined; + let distanceToTopographicLand: Uint16Array | undefined; try { + for (let pixel = 0; pixel < pixels.length; pixel += 4) { + pixels[pixel] = PRIVATE_PREVIEW_FOG_COLOR[0]; + pixels[pixel + 1] = PRIVATE_PREVIEW_FOG_COLOR[1]; + pixels[pixel + 2] = PRIVATE_PREVIEW_FOG_COLOR[2]; + pixels[pixel + 3] = 255; + } let minimumQ = Number.POSITIVE_INFINITY; let maximumQ = Number.NEGATIVE_INFINITY; let minimumR = Number.POSITIVE_INFINITY; @@ -1254,13 +1304,20 @@ export async function renderGreaterRealmPrivatePreview( [87, 144, 84], [117, 159, 195], [205, 151, 76], [84, 147, 127], [103, 124, 164], [151, 96, 80], [92, 112, 82], [116, 103, 126], [85, 121, 142], [116, 84, 135], ] as const; + if (mode === 'regions') { + distanceToTopographicLand = privatePreviewDistanceToTopographicLand(candidate); + } const gateCells = new Set(candidate.gates.flatMap(gate => [gate.firstCell, gate.secondCell])); for (let index = 0; index < candidate.grid.cellCount; index += 1) { const x = 40 + Math.round(((candidate.grid.q[index]! - minimumQ) + (candidate.grid.r[index]! - minimumR) / 2) * scale); const y = 60 + Math.round((candidate.grid.r[index]! - minimumR) * scale * 0.86); let color: readonly [number, number, number] = [44, 49, 64]; if (mode === 'silhouette') { - color = candidate.waterRegime[index] === 0 ? [142, 164, 105] : [39, 76, 124]; + // Rivers and streams are features inside the continental footprint, not + // coastline cuts. Silhouette review therefore follows sea-level land. + color = candidate.elevation[index]! > PRIVATE_PREVIEW_SEA_LEVEL + ? [142, 164, 105] + : [39, 76, 124]; } else if (mode === 'hillshade') { const shade = clampPreview(90 + Math.floor((candidate.elevation[index]! + 12_000) / 260)); color = [shade, shade, Math.min(255, shade + 8)]; @@ -1269,7 +1326,26 @@ export async function renderGreaterRealmPrivatePreview( } else if (mode === 'hydrology') { color = candidate.waterRegime[index] === 0 ? [109, 118, 91] : [48, 132, 205]; } else if (mode === 'regions') { - color = regionPalette[candidate.regionId[index]!]!; + const regime = candidate.waterRegime[index]!; + if (regime === PRIVATE_PREVIEW_WATER_DRY) { + color = regionPalette[candidate.regionId[index]!]!; + } else if (regime === PRIVATE_PREVIEW_WATER_OCEAN) { + const distance = distanceToTopographicLand?.[index] ?? 0; + const band = Math.min(5, Math.floor(distance / 4)); + color = [54 - band * 5, 91 - band * 8, 132 - band * 10]; + } else if ( + regime === PRIVATE_PREVIEW_WATER_LAKE + || regime === PRIVATE_PREVIEW_WATER_SEA + ) { + color = [66, 126, 171]; + } else if ( + regime === PRIVATE_PREVIEW_WATER_RIVER + || regime === PRIVATE_PREVIEW_WATER_STREAM + ) { + color = [72, 143, 190]; + } else { + fail('GREATER_REALM_PRIVATE_PREVIEW_WATER_INVALID'); + } } else { color = gateCells.has(index) ? [236, 194, 82] @@ -1291,19 +1367,26 @@ export async function renderGreaterRealmPrivatePreview( } } const sharpModule = await import('sharp'); + const watermarkLabel = mode === 'regions' + ? 'TOPOLOGY + OUTER-OCEAN PROXY · PRIVATE REVIEW · NOT RUNTIME FOG' + : `PRIVATE OWNER REVIEW — DO NOT DISTRIBUTE · ${mode.toUpperCase()}`; watermark = Buffer.from( - `PRIVATE OWNER REVIEW — DO NOT DISTRIBUTE · ${mode.toUpperCase()}`, + `${watermarkLabel}`, 'utf8', ); encoded = await sharpModule.default(pixels, { raw: { width, height, channels: 4 } }) .composite([{ input: watermark }]) - .png({ compressionLevel: 9, adaptiveFiltering: false, palette: true }) + // Preserve the explicit, fully opaque alpha channel. Palette encoding + // silently collapses an all-255 alpha channel to RGB, which breaks the + // fail-closed preview contract and makes opacity impossible to attest. + .png({ compressionLevel: 9, adaptiveFiltering: false, palette: false }) .toBuffer(); return markPrivatePreviewPng(encoded); } finally { pixels.fill(0); watermark?.fill(0); encoded?.fill(0); + distanceToTopographicLand?.fill(0); } } @@ -1401,6 +1484,7 @@ export async function writeGreaterRealmPrivateCandidate(input: Readonly<{ candidateHandle: input.candidateHandle, candidateOrdinal: input.candidate.candidateOrdinal, seedDigest: createHash('sha256').update(input.candidate.seedMaterial).digest('hex'), + seedNamespace: GREATER_REALM_TERRAIN_SEED_NAMESPACE, provenanceDigest: privateProvenanceDigest({ batchHandle: input.batchHandle, sourceCommit: input.sourceCommit, @@ -1883,6 +1967,7 @@ export async function verifyGreaterRealmPrivateCandidatePackage(input: Readonly< || manifest.exactActiveCellCount !== input.expectedActiveCellCount || manifest.atlasDigest !== atlasDigest || manifest.seedDigest !== expectedSeedDigest + || manifest.seedNamespace !== GREATER_REALM_TERRAIN_SEED_NAMESPACE || manifest.provenanceDigest !== expectedProvenanceDigest || !exactJsonEqual(manifest.canvas, { kind: 'private-axial-disc', diff --git a/scripts/atlas/greater-realm-cli.ts b/scripts/atlas/greater-realm-cli.ts index 09383fe3..c0ab5ba5 100644 --- a/scripts/atlas/greater-realm-cli.ts +++ b/scripts/atlas/greater-realm-cli.ts @@ -370,7 +370,7 @@ async function generateCandidates(arguments_: ParsedArguments): Promise { workspaceRoot: arguments_.workspaceRoot, }); const batchHandle = createGreaterRealmReviewBatchHandle(); - const requestedCount = arguments_.count ?? 12; + const requestedCount = arguments_.count ?? 1; const maximumAttempts = arguments_.maximumAttempts ?? Math.max(128, requestedCount * 16); if (maximumAttempts < requestedCount) fail('GREATER_REALM_CLI_ARGUMENTS_INVALID'); const commit = sourceCommit(); @@ -861,9 +861,9 @@ function shortlistDiversityDistance( } /** - * Produces an unranked owner-review set. It deliberately has no scalar score, - * recommendation, or selection side effect: Pareto specialists and vector - * separation preserve materially different terrain trade-offs for a human. + * Produces an unranked owner-review set. A single-candidate batch remains a + * pending owner review; multi-candidate batches use Pareto specialists and + * vector separation. Neither path recommends or selects a world. */ export function buildGreaterRealmPrivateCandidateShortlist( review: GreaterRealmSanitizedReview, @@ -872,7 +872,7 @@ export function buildGreaterRealmPrivateCandidateShortlist( if ( review.selectionStatus !== 'pending' || review.selectedCandidateHandle !== null - || review.candidates.length < 3 + || review.candidates.length < GREATER_REALM_MINIMUM_CANDIDATE_COUNT || review.candidates.some(candidate => candidate.eligible !== true) || review.candidates.some(candidate => ( candidate.biomes.incompatibleVisualAdjacencyCount !== 0 @@ -917,7 +917,8 @@ export function buildGreaterRealmPrivateCandidateShortlist( const minimum = Math.min(...values); return Object.freeze({ minimum, span: Math.max(1, Math.max(...values) - minimum) }); }); - while (selected.size < 3) { + const minimumReviewSetSize = Math.min(3, candidates.length); + while (selected.size < minimumReviewSetSize) { const remaining = candidates.filter(candidate => !selected.has(candidate.candidateHandle)); if (remaining.length === 0) fail('GREATER_REALM_PRIVATE_SHORTLIST_INVALID'); const next = remaining.sort((first, second) => { @@ -937,7 +938,9 @@ export function buildGreaterRealmPrivateCandidateShortlist( const candidateHandles = Object.freeze([...selected.keys()].sort()); return Object.freeze({ kind: 'warpkeep.greater-realm.private-owner-shortlist.v1' as const, - method: 'pareto-private-vector-diversity-v2' as const, + method: candidates.length === 1 + ? 'single-candidate-reference-review-v1' as const + : 'pareto-private-vector-diversity-v2' as const, comparisonBasis: 'verified-private-package-aggregate-metrics-v1' as const, batchHandle: review.reviewBatchHandle, sourceReviewDigest: review.reportDigest, diff --git a/scripts/atlas/greater-realm-composition.ts b/scripts/atlas/greater-realm-composition.ts new file mode 100644 index 00000000..62a2b1e8 --- /dev/null +++ b/scripts/atlas/greater-realm-composition.ts @@ -0,0 +1,1532 @@ +import { + GREATER_REALM_AXIAL_DIRECTIONS, + type IndexedAxialGrid, +} from './greater-realm-terrain'; +export { GREATER_REALM_COMPOSITION_PROOF_KEYS } from './greater-realm-contracts'; + +const HEX_NEIGHBOR_COUNT = GREATER_REALM_AXIAL_DIRECTIONS.length; +const BASIS_POINTS = 10_000; +const SIXTY_DEGREE_COSINE = 500_000; +const SIXTY_DEGREE_SINE = 866_025; +const ROTATION_SCALE = 1_000_000; +const HEX_X_SCALE = 866_025; +const HEX_Y_SCALE = 1_500_000; + +export type GreaterRealmLandSilhouetteThresholds = Readonly<{ + maximumRotationalIou64BasisPoints: number; + maximumRotationalIou256BasisPoints: number; + maximumAlignedCoastRunShareBasisPoints: number; + minimumDominantLandSolidityBasisPoints: number; + maximumDominantLandSolidityBasisPoints: number; + minimumCoastDetailGainBasisPoints: number; + maximumCoastDetailGainBasisPoints: number; +}>; + +export type GreaterRealmDominantContinentThresholds = Readonly<{ + minimumDominantLandShareBasisPoints: number; + maximumDominantLandShareBasisPoints: number; + minimumDominantToSecondRatioBasisPoints: number; + minimumTierTwoOnDominantBasisPoints: number; + minimumTierThreeOnDominantBasisPoints: number; +}>; + +export type GreaterRealmOceanBreathingRoomThresholds = Readonly<{ + minimumBoundaryLandDistance: number; + minimumBoundaryLandDistanceP05: number; + minimumBoundaryLandDistanceP50: number; + targetBoundaryLandDistance: number; + minimumBoundaryAtTargetShareBasisPoints: number; + sectorCount: number; + minimumSectorMedianLandDistance: number; +}>; + +export type GreaterRealmPatchCompositionThresholds = Readonly<{ + minimumShareBasisPoints: number; + maximumShareBasisPoints: number; + clusteredComponentMinimumCells: number; + minimumClusteredShareBasisPoints: number; + broadComponentMinimumCells: number; + minimumBroadComponentCount: number; + tinyComponentMaximumCells: number; + maximumTinyShareBasisPoints: number; + maximumLargestComponentShareBasisPoints: number; +}>; + +export type GreaterRealmMountainBeltThresholds = Readonly<{ + minimumBeltCells: number; + minimumMaximumAxialSpan: number; + minimumCentroidOffset: number; + minimumAxialAnisotropyBasisPoints: number; +}>; + +export const GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS = Object.freeze({ + maximumRotationalIou64BasisPoints: 9_000, + maximumRotationalIou256BasisPoints: 8_500, + maximumAlignedCoastRunShareBasisPoints: 1_250, + minimumDominantLandSolidityBasisPoints: 6_500, + maximumDominantLandSolidityBasisPoints: 9_300, + minimumCoastDetailGainBasisPoints: 10_250, + maximumCoastDetailGainBasisPoints: 16_500, +} satisfies GreaterRealmLandSilhouetteThresholds); + +export const GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS = Object.freeze({ + minimumDominantLandShareBasisPoints: 5_500, + maximumDominantLandShareBasisPoints: 9_000, + minimumDominantToSecondRatioBasisPoints: 14_000, + minimumTierTwoOnDominantBasisPoints: 8_000, + minimumTierThreeOnDominantBasisPoints: 8_000, +} satisfies GreaterRealmDominantContinentThresholds); + +export const GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS = Object.freeze({ + minimumBoundaryLandDistance: 8, + minimumBoundaryLandDistanceP05: 16, + minimumBoundaryLandDistanceP50: 20, + targetBoundaryLandDistance: 20, + minimumBoundaryAtTargetShareBasisPoints: 7_000, + sectorCount: 12, + minimumSectorMedianLandDistance: 16, +} satisfies GreaterRealmOceanBreathingRoomThresholds); + +export const GREATER_REALM_FOREST_PATCH_THRESHOLDS = Object.freeze({ + minimumShareBasisPoints: 800, + maximumShareBasisPoints: 4_200, + clusteredComponentMinimumCells: 64, + minimumClusteredShareBasisPoints: 7_500, + broadComponentMinimumCells: 256, + minimumBroadComponentCount: 3, + tinyComponentMaximumCells: 7, + maximumTinyShareBasisPoints: 800, + maximumLargestComponentShareBasisPoints: 4_500, +} satisfies GreaterRealmPatchCompositionThresholds); + +export const GREATER_REALM_MOUNTAIN_PATCH_THRESHOLDS = Object.freeze({ + minimumShareBasisPoints: 300, + maximumShareBasisPoints: 2_800, + clusteredComponentMinimumCells: 64, + minimumClusteredShareBasisPoints: 7_000, + broadComponentMinimumCells: 64, + minimumBroadComponentCount: 2, + tinyComponentMaximumCells: 7, + maximumTinyShareBasisPoints: 800, + maximumLargestComponentShareBasisPoints: 6_000, +} satisfies GreaterRealmPatchCompositionThresholds); + +export const GREATER_REALM_MOUNTAIN_BELT_THRESHOLDS = Object.freeze({ + minimumBeltCells: 128, + minimumMaximumAxialSpan: 24, + minimumCentroidOffset: 12, + minimumAxialAnisotropyBasisPoints: 15_000, +} satisfies GreaterRealmMountainBeltThresholds); + +export const GREATER_REALM_COMPOSITION_THRESHOLDS = Object.freeze({ + landSilhouette: GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS, + dominantContinent: GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS, + oceanBreathingRoom: GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS, + forest: GREATER_REALM_FOREST_PATCH_THRESHOLDS, + mountain: GREATER_REALM_MOUNTAIN_PATCH_THRESHOLDS, + mountainBelt: GREATER_REALM_MOUNTAIN_BELT_THRESHOLDS, +}); + +export type GreaterRealmRasterSilhouetteMetrics = Readonly<{ + resolution: 64 | 256; + landPixels: number; + perimeterEdges: number; + rotationalIouBasisPoints: number; +}>; + +export type GreaterRealmLandSilhouetteMetrics = Readonly<{ + coastHalfEdgeCount: number; + maximumAlignedCoastRunCells: number; + maximumAlignedCoastRunShareBasisPoints: number; + dominantLandConvexHullCapacity: number; + dominantLandSolidityBasisPoints: number; + raster64: GreaterRealmRasterSilhouetteMetrics; + raster256: GreaterRealmRasterSilhouetteMetrics; + coastDetailGainBasisPoints: number; + proof: boolean; +}>; + +export type GreaterRealmDominantContinentMetrics = Readonly<{ + landCellCount: number; + landmassCount: number; + dominantLandmassCells: number; + secondLandmassCells: number; + dominantLandShareBasisPoints: number; + dominantToSecondRatioBasisPoints: number; + tierTwoOnDominantBasisPoints: number; + tierThreeOnDominantBasisPoints: number; + componentSizesDescending: readonly number[]; + proof: boolean; +}>; + +export type GreaterRealmOceanBreathingRoomMetrics = Readonly<{ + boundaryCellCount: number; + saltwaterBoundaryBasisPoints: number; + minimumBoundaryLandDistance: number; + boundaryLandDistanceP05: number; + boundaryLandDistanceP50: number; + boundaryLandDistanceP95: number; + boundaryAtTargetShareBasisPoints: number; + sectorBoundaryCellCounts: readonly number[]; + sectorMedianLandDistances: readonly number[]; + proof: boolean; +}>; + +export type GreaterRealmPatchCompositionMetrics = Readonly<{ + eligibleCellCount: number; + patchCellCount: number; + patchShareBasisPoints: number; + componentCount: number; + broadComponentCount: number; + clusteredShareBasisPoints: number; + tinyShareBasisPoints: number; + largestComponentShareBasisPoints: number; + componentSizeP50: number; + componentSizeP90: number; + componentSizesDescending: readonly number[]; + proof: boolean; +}>; + +export type GreaterRealmMountainSystemMetrics = GreaterRealmPatchCompositionMetrics & Readonly<{ + offCentreBeltCount: number; + maximumBeltCells: number; + maximumBeltAxialSpan: number; + maximumBeltCentroidOffset: number; + maximumBeltAxialAnisotropyBasisPoints: number; + proof: boolean; +}>; + +export type GreaterRealmNaturalCompositionMetrics = Readonly<{ + landSilhouette: GreaterRealmLandSilhouetteMetrics; + dominantContinent: GreaterRealmDominantContinentMetrics; + oceanBreathingRoom: GreaterRealmOceanBreathingRoomMetrics; + forestPatches: GreaterRealmPatchCompositionMetrics; + mountainSystems: GreaterRealmMountainSystemMetrics; +}>; + +type ComponentInventory = Readonly<{ + componentId: Int32Array; + components: readonly number[][]; +}>; + +function clearComponentInventory(inventory: ComponentInventory): void { + inventory.componentId.fill(-1); + for (const component of inventory.components) component.fill(0); +} + +function fail(code: string): never { + throw new Error(code); +} + +function assertGridFieldLengths( + grid: IndexedAxialGrid, + fields: readonly ArrayLike[], +): void { + if (fields.some(field => field.length !== grid.cellCount)) { + fail('GREATER_REALM_COMPOSITION_FIELD_LENGTH_INVALID'); + } +} + +function assertBinaryMask(mask: Uint8Array): void { + for (const value of mask) { + if (value !== 0 && value !== 1) fail('GREATER_REALM_COMPOSITION_MASK_INVALID'); + } +} + +function assertNonnegativeInteger(value: number): void { + if (!Number.isSafeInteger(value) || value < 0) { + fail('GREATER_REALM_COMPOSITION_THRESHOLD_INVALID'); + } +} + +function assertShareBasisPoints(value: number): void { + assertNonnegativeInteger(value); + if (value > BASIS_POINTS) fail('GREATER_REALM_COMPOSITION_THRESHOLD_INVALID'); +} + +function assertOrdered(minimum: number, maximum: number): void { + if (minimum > maximum) fail('GREATER_REALM_COMPOSITION_THRESHOLD_INVALID'); +} + +function validateLandSilhouetteThresholds( + thresholds: GreaterRealmLandSilhouetteThresholds, +): void { + assertShareBasisPoints(thresholds.maximumRotationalIou64BasisPoints); + assertShareBasisPoints(thresholds.maximumRotationalIou256BasisPoints); + assertShareBasisPoints(thresholds.maximumAlignedCoastRunShareBasisPoints); + assertShareBasisPoints(thresholds.minimumDominantLandSolidityBasisPoints); + assertShareBasisPoints(thresholds.maximumDominantLandSolidityBasisPoints); + assertNonnegativeInteger(thresholds.minimumCoastDetailGainBasisPoints); + assertNonnegativeInteger(thresholds.maximumCoastDetailGainBasisPoints); + assertOrdered( + thresholds.minimumDominantLandSolidityBasisPoints, + thresholds.maximumDominantLandSolidityBasisPoints, + ); + assertOrdered( + thresholds.minimumCoastDetailGainBasisPoints, + thresholds.maximumCoastDetailGainBasisPoints, + ); +} + +function validateDominantContinentThresholds( + thresholds: GreaterRealmDominantContinentThresholds, +): void { + assertShareBasisPoints(thresholds.minimumDominantLandShareBasisPoints); + assertShareBasisPoints(thresholds.maximumDominantLandShareBasisPoints); + assertNonnegativeInteger(thresholds.minimumDominantToSecondRatioBasisPoints); + assertShareBasisPoints(thresholds.minimumTierTwoOnDominantBasisPoints); + assertShareBasisPoints(thresholds.minimumTierThreeOnDominantBasisPoints); + assertOrdered( + thresholds.minimumDominantLandShareBasisPoints, + thresholds.maximumDominantLandShareBasisPoints, + ); +} + +function validateOceanThresholds( + thresholds: GreaterRealmOceanBreathingRoomThresholds, +): void { + for (const value of [ + thresholds.minimumBoundaryLandDistance, + thresholds.minimumBoundaryLandDistanceP05, + thresholds.minimumBoundaryLandDistanceP50, + thresholds.targetBoundaryLandDistance, + thresholds.minimumSectorMedianLandDistance, + ]) assertNonnegativeInteger(value); + assertShareBasisPoints(thresholds.minimumBoundaryAtTargetShareBasisPoints); + if (thresholds.sectorCount !== TWELVE_SECTOR_NORMALS.length) { + fail('GREATER_REALM_COMPOSITION_SECTOR_COUNT_INVALID'); + } +} + +function validatePatchThresholds( + thresholds: GreaterRealmPatchCompositionThresholds, +): void { + for (const value of [ + thresholds.minimumShareBasisPoints, + thresholds.maximumShareBasisPoints, + thresholds.minimumClusteredShareBasisPoints, + thresholds.maximumTinyShareBasisPoints, + thresholds.maximumLargestComponentShareBasisPoints, + ]) assertShareBasisPoints(value); + for (const value of [ + thresholds.clusteredComponentMinimumCells, + thresholds.broadComponentMinimumCells, + thresholds.minimumBroadComponentCount, + thresholds.tinyComponentMaximumCells, + ]) assertNonnegativeInteger(value); + assertOrdered(thresholds.minimumShareBasisPoints, thresholds.maximumShareBasisPoints); + if ( + thresholds.clusteredComponentMinimumCells < 1 + || thresholds.broadComponentMinimumCells < 1 + || thresholds.minimumBroadComponentCount < 1 + ) fail('GREATER_REALM_COMPOSITION_THRESHOLD_INVALID'); +} + +function validateMountainBeltThresholds( + thresholds: GreaterRealmMountainBeltThresholds, +): void { + for (const value of Object.values(thresholds)) assertNonnegativeInteger(value); + if ( + thresholds.minimumBeltCells < 1 + || thresholds.minimumMaximumAxialSpan < 1 + || thresholds.minimumAxialAnisotropyBasisPoints < BASIS_POINTS + ) fail('GREATER_REALM_COMPOSITION_THRESHOLD_INVALID'); +} + +function roundedBasisPoints(numerator: number, denominator: number): number { + if ( + !Number.isSafeInteger(numerator) + || !Number.isSafeInteger(denominator) + || numerator < 0 + || denominator <= 0 + ) fail('GREATER_REALM_COMPOSITION_RATIO_INVALID'); + return Math.round((numerator * BASIS_POINTS) / denominator); +} + +function roundDivide(numerator: number, denominator: number): number { + if ( + !Number.isSafeInteger(numerator) + || !Number.isSafeInteger(denominator) + || denominator <= 0 + ) fail('GREATER_REALM_COMPOSITION_DIVISION_INVALID'); + const sign = numerator < 0 ? -1 : 1; + const magnitude = Math.abs(numerator); + const quotient = Math.floor(magnitude / denominator); + const remainder = magnitude % denominator; + return sign * (quotient + (remainder * 2 >= denominator ? 1 : 0)); +} + +function axialDistance(q: number, r: number, otherQ = 0, otherR = 0): number { + const deltaQ = q - otherQ; + const deltaR = r - otherR; + return Math.max(Math.abs(deltaQ), Math.abs(deltaR), Math.abs(-deltaQ - deltaR)); +} + +function percentile(sortedAscending: readonly number[], basisPoints: number): number { + if (sortedAscending.length === 0) return 0; + const index = Math.floor(((sortedAscending.length - 1) * basisPoints) / BASIS_POINTS); + return sortedAscending[index]!; +} + +function components(grid: IndexedAxialGrid, mask: Uint8Array): ComponentInventory { + assertGridFieldLengths(grid, [mask]); + assertBinaryMask(mask); + const componentId = new Int32Array(grid.cellCount); + componentId.fill(-1); + const queue = new Uint32Array(grid.cellCount); + const found: number[][] = []; + let completed = false; + try { + for (let start = 0; start < grid.cellCount; start += 1) { + if (mask[start] !== 1 || componentId[start] >= 0) continue; + const id = found.length; + let head = 0; + let tail = 0; + queue[tail++] = start; + componentId[start] = id; + const cells: number[] = []; + while (head < tail) { + const cell = queue[head++]!; + cells.push(cell); + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || mask[neighbor] !== 1 || componentId[neighbor] >= 0) continue; + componentId[neighbor] = id; + queue[tail++] = neighbor; + } + } + found.push(cells); + } + const inventory = Object.freeze({ componentId, components: Object.freeze(found) }); + completed = true; + return inventory; + } finally { + queue.fill(0); + if (!completed) { + componentId.fill(-1); + for (const component of found) component.fill(0); + } + } +} + +function countMask(mask: Uint8Array): number { + let count = 0; + for (const value of mask) count += value; + return count; +} + +function maskCentroid( + grid: IndexedAxialGrid, + mask: Uint8Array, +): Readonly<{ q: number; r: number }> { + let count = 0; + let qTotal = 0; + let rTotal = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (mask[cell] !== 1) continue; + count += 1; + qTotal += grid.q[cell]!; + rTotal += grid.r[cell]!; + } + if (count === 0) fail('GREATER_REALM_COMPOSITION_MASK_EMPTY'); + return Object.freeze({ + q: roundDivide(qTotal, count), + r: roundDivide(rTotal, count), + }); +} + +export function createGreaterRealmTopographicLandMask( + elevation: ArrayLike, + seaLevel = 0, +): Uint8Array { + if (!Number.isSafeInteger(seaLevel)) fail('GREATER_REALM_COMPOSITION_SEA_LEVEL_INVALID'); + const mask = new Uint8Array(elevation.length); + let completed = false; + try { + for (let cell = 0; cell < elevation.length; cell += 1) { + const value = elevation[cell]!; + if (!Number.isSafeInteger(value)) fail('GREATER_REALM_COMPOSITION_ELEVATION_INVALID'); + if (value > seaLevel) mask[cell] = 1; + } + completed = true; + return mask; + } finally { + if (!completed) mask.fill(0); + } +} + +function projectedRasterCoordinate( + q: number, + r: number, + canvasRadius: number, + resolution: 64 | 256, +): Readonly<{ x: number; y: number }> { + const halfSpan = HEX_X_SCALE * canvasRadius * 2; + const projectedX = HEX_X_SCALE * (q * 2 + r); + const projectedY = HEX_Y_SCALE * r; + const denominator = halfSpan * 2 + 1; + return Object.freeze({ + x: Math.max(0, Math.min( + resolution - 1, + Math.floor(((projectedX + halfSpan) * resolution) / denominator), + )), + y: Math.max(0, Math.min( + resolution - 1, + Math.floor(((projectedY + halfSpan) * resolution) / denominator), + )), + }); +} + +function rasterizeLand( + grid: IndexedAxialGrid, + landMask: Uint8Array, + canvasRadius: number, + resolution: 64 | 256, +): Uint8Array { + if (!Number.isSafeInteger(canvasRadius) || canvasRadius < 1 || canvasRadius > 10_000) { + fail('GREATER_REALM_COMPOSITION_CANVAS_RADIUS_INVALID'); + } + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (axialDistance(grid.q[cell]!, grid.r[cell]!) > canvasRadius) { + fail('GREATER_REALM_COMPOSITION_CANVAS_RADIUS_INVALID'); + } + } + const raster = new Uint8Array(resolution * resolution); + const brushRadius = Math.max(0, Math.ceil(resolution / (canvasRadius * 4)) - 1); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const q = grid.q[cell]!; + const r = grid.r[cell]!; + if (landMask[cell] !== 1) continue; + const center = projectedRasterCoordinate(q, r, canvasRadius, resolution); + for (let offsetY = -brushRadius; offsetY <= brushRadius; offsetY += 1) { + const y = center.y + offsetY; + if (y < 0 || y >= resolution) continue; + for (let offsetX = -brushRadius; offsetX <= brushRadius; offsetX += 1) { + const x = center.x + offsetX; + if (x < 0 || x >= resolution) continue; + raster[y * resolution + x] = 1; + } + } + } + return raster; +} + +function rasterPerimeter(raster: Uint8Array, resolution: number): number { + let perimeter = 0; + for (let y = 0; y < resolution; y += 1) { + for (let x = 0; x < resolution; x += 1) { + if (raster[y * resolution + x] !== 1) continue; + if (x === 0 || raster[y * resolution + x - 1] !== 1) perimeter += 1; + if (x + 1 === resolution || raster[y * resolution + x + 1] !== 1) perimeter += 1; + if (y === 0 || raster[(y - 1) * resolution + x] !== 1) perimeter += 1; + if (y + 1 === resolution || raster[(y + 1) * resolution + x] !== 1) perimeter += 1; + } + } + return perimeter; +} + +function rotatedRasterIou( + raster: Uint8Array, + resolution: 64 | 256, + sine: number, +): number { + let landPixels = 0; + let xTotal = 0; + let yTotal = 0; + for (let y = 0; y < resolution; y += 1) { + for (let x = 0; x < resolution; x += 1) { + if (raster[y * resolution + x] !== 1) continue; + landPixels += 1; + xTotal += x; + yTotal += y; + } + } + if (landPixels === 0) return 0; + const denominator = landPixels * ROTATION_SCALE; + const rotated = new Uint8Array(raster.length); + try { + for (let y = 0; y < resolution; y += 1) { + for (let x = 0; x < resolution; x += 1) { + if (raster[y * resolution + x] !== 1) continue; + const deltaX = x * landPixels - xTotal; + const deltaY = y * landPixels - yTotal; + const rotatedXNumerator = SIXTY_DEGREE_COSINE * deltaX + - sine * deltaY + + xTotal * ROTATION_SCALE; + const rotatedYNumerator = sine * deltaX + + SIXTY_DEGREE_COSINE * deltaY + + yTotal * ROTATION_SCALE; + const rotatedX = roundDivide(rotatedXNumerator, denominator); + const rotatedY = roundDivide(rotatedYNumerator, denominator); + if ( + rotatedX >= 0 + && rotatedX < resolution + && rotatedY >= 0 + && rotatedY < resolution + ) rotated[rotatedY * resolution + rotatedX] = 1; + } + } + let intersection = 0; + let union = 0; + for (let pixel = 0; pixel < raster.length; pixel += 1) { + const first = raster[pixel] === 1; + const second = rotated[pixel] === 1; + if (first && second) intersection += 1; + if (first || second) union += 1; + } + return union === 0 ? 0 : roundedBasisPoints(intersection, union); + } finally { + rotated.fill(0); + } +} + +function rasterMetrics( + grid: IndexedAxialGrid, + landMask: Uint8Array, + canvasRadius: number, + resolution: 64 | 256, +): GreaterRealmRasterSilhouetteMetrics { + const raster = rasterizeLand(grid, landMask, canvasRadius, resolution); + try { + return Object.freeze({ + resolution, + landPixels: countMask(raster), + perimeterEdges: rasterPerimeter(raster, resolution), + rotationalIouBasisPoints: Math.max( + rotatedRasterIou(raster, resolution, SIXTY_DEGREE_SINE), + rotatedRasterIou(raster, resolution, -SIXTY_DEGREE_SINE), + ), + }); + } finally { + raster.fill(0); + } +} + +function alignedCoastRun( + grid: IndexedAxialGrid, + dominantMask: Uint8Array, +): Readonly<{ + coastHalfEdgeCount: number; + maximumRunCells: number; +}> { + let coastHalfEdgeCount = 0; + let maximumRunCells = 0; + const queue = new Uint32Array(grid.cellCount); + try { + for (let normal = 0; normal < HEX_NEIGHBOR_COUNT; normal += 1) { + const edgeMask = new Uint8Array(grid.cellCount); + const seen = new Uint8Array(grid.cellCount); + try { + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (dominantMask[cell] !== 1) continue; + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + normal]!; + if (neighbor >= 0 && dominantMask[neighbor] === 1) continue; + edgeMask[cell] = 1; + coastHalfEdgeCount += 1; + } + const tangentDirections = [(normal + 2) % 6, (normal + 5) % 6] as const; + for (let start = 0; start < grid.cellCount; start += 1) { + if (edgeMask[start] !== 1 || seen[start] === 1) continue; + let head = 0; + let tail = 0; + queue[tail++] = start; + seen[start] = 1; + while (head < tail) { + const cell = queue[head++]!; + for (const direction of tangentDirections) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || edgeMask[neighbor] !== 1 + || seen[neighbor] === 1 + ) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + } + } + maximumRunCells = Math.max(maximumRunCells, tail); + } + } finally { + edgeMask.fill(0); + seen.fill(0); + } + } + return Object.freeze({ coastHalfEdgeCount, maximumRunCells }); + } finally { + queue.fill(0); + } +} + +function greatestCommonDivisor(first: number, second: number): number { + let left = Math.abs(first); + let right = Math.abs(second); + while (right !== 0) { + const remainder = left % right; + left = right; + right = remainder; + } + return left; +} + +function dominantConvexHullCapacity( + grid: IndexedAxialGrid, + cells: readonly number[], +): number { + type MutableAxialPoint = { q: number; r: number }; + const points: MutableAxialPoint[] = cells.map(cell => ({ + q: grid.q[cell]!, + r: grid.r[cell]!, + })).sort((first, second) => first.q - second.q || first.r - second.r); + try { + if (points.length <= 2) return points.length; + const cross = ( + first: Readonly, + second: Readonly, + third: Readonly, + ) => (second.q - first.q) * (third.r - first.r) + - (second.r - first.r) * (third.q - first.q); + const halfHull = ( + ordered: readonly Readonly[], + ): Array> => { + const hull: Array> = []; + for (const point of ordered) { + while ( + hull.length >= 2 + && cross(hull[hull.length - 2]!, hull[hull.length - 1]!, point) <= 0 + ) hull.pop(); + hull.push(point); + } + return hull; + }; + const lower = halfHull(points); + const upper = halfHull([...points].reverse()); + const hull = [...lower.slice(0, -1), ...upper.slice(0, -1)]; + if (hull.length < 3) return points.length; + let signedDoubleArea = 0; + let boundaryLatticePoints = 0; + for (let index = 0; index < hull.length; index += 1) { + const first = hull[index]!; + const second = hull[(index + 1) % hull.length]!; + signedDoubleArea += first.q * second.r - first.r * second.q; + boundaryLatticePoints += greatestCommonDivisor( + second.q - first.q, + second.r - first.r, + ); + } + // Pick's theorem on the axial integer lattice. This returns the number of + // cell centers inside or on the convex hull, which equals the exact cell + // count for a filled axial hexagon and avoids a floating-point ellipse fit. + const numerator = Math.abs(signedDoubleArea) + boundaryLatticePoints; + if (numerator % 2 !== 0) fail('GREATER_REALM_COMPOSITION_HULL_INVALID'); + return numerator / 2 + 1; + } finally { + for (const point of points) { + point.q = 0; + point.r = 0; + } + } +} + +export function isGreaterRealmNaturalLandSilhouette( + metrics: Omit, + thresholds: GreaterRealmLandSilhouetteThresholds = GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS, +): boolean { + validateLandSilhouetteThresholds(thresholds); + return metrics.coastHalfEdgeCount > 0 + && metrics.raster64.landPixels > 0 + && metrics.raster256.landPixels > 0 + && metrics.raster64.perimeterEdges > 0 + && metrics.raster256.perimeterEdges > 0 + && metrics.raster64.rotationalIouBasisPoints + <= thresholds.maximumRotationalIou64BasisPoints + && metrics.raster256.rotationalIouBasisPoints + <= thresholds.maximumRotationalIou256BasisPoints + && metrics.maximumAlignedCoastRunShareBasisPoints + <= thresholds.maximumAlignedCoastRunShareBasisPoints + && metrics.dominantLandSolidityBasisPoints + >= thresholds.minimumDominantLandSolidityBasisPoints + && metrics.dominantLandSolidityBasisPoints + <= thresholds.maximumDominantLandSolidityBasisPoints + && metrics.coastDetailGainBasisPoints >= thresholds.minimumCoastDetailGainBasisPoints + && metrics.coastDetailGainBasisPoints <= thresholds.maximumCoastDetailGainBasisPoints; +} + +export function measureGreaterRealmLandSilhouette(input: Readonly<{ + grid: IndexedAxialGrid; + landMask: Uint8Array; + canvasRadius: number; + thresholds?: GreaterRealmLandSilhouetteThresholds; +}>): GreaterRealmLandSilhouetteMetrics { + assertGridFieldLengths(input.grid, [input.landMask]); + assertBinaryMask(input.landMask); + const inventory = components(input.grid, input.landMask); + const dominantMask = new Uint8Array(input.grid.cellCount); + try { + if (inventory.components.length === 0) fail('GREATER_REALM_COMPOSITION_LAND_EMPTY'); + let dominantId = 0; + for (let id = 1; id < inventory.components.length; id += 1) { + if ( + inventory.components[id]!.length > inventory.components[dominantId]!.length + || ( + inventory.components[id]!.length === inventory.components[dominantId]!.length + && inventory.components[id]![0]! < inventory.components[dominantId]![0]! + ) + ) dominantId = id; + } + for (const cell of inventory.components[dominantId]!) dominantMask[cell] = 1; + const coast = alignedCoastRun(input.grid, dominantMask); + const dominantLandConvexHullCapacity = dominantConvexHullCapacity( + input.grid, + inventory.components[dominantId]!, + ); + const raster64 = rasterMetrics(input.grid, dominantMask, input.canvasRadius, 64); + const raster256 = rasterMetrics(input.grid, dominantMask, input.canvasRadius, 256); + const coastDetailGainBasisPoints = raster64.perimeterEdges === 0 + ? 0 + : roundedBasisPoints(raster256.perimeterEdges, raster64.perimeterEdges * 4); + const withoutProof = Object.freeze({ + coastHalfEdgeCount: coast.coastHalfEdgeCount, + maximumAlignedCoastRunCells: coast.maximumRunCells, + maximumAlignedCoastRunShareBasisPoints: coast.coastHalfEdgeCount === 0 + ? BASIS_POINTS + : roundedBasisPoints(coast.maximumRunCells, coast.coastHalfEdgeCount), + dominantLandConvexHullCapacity, + dominantLandSolidityBasisPoints: roundedBasisPoints( + inventory.components[dominantId]!.length, + dominantLandConvexHullCapacity, + ), + raster64, + raster256, + coastDetailGainBasisPoints, + }); + return Object.freeze({ + ...withoutProof, + proof: isGreaterRealmNaturalLandSilhouette( + withoutProof, + input.thresholds ?? GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS, + ), + }); + } finally { + dominantMask.fill(0); + clearComponentInventory(inventory); + } +} + +export function isGreaterRealmDominantContinentComposition( + metrics: Omit, + thresholds: GreaterRealmDominantContinentThresholds = GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS, +): boolean { + validateDominantContinentThresholds(thresholds); + return metrics.landCellCount > 0 + && metrics.landmassCount >= 2 + && metrics.secondLandmassCells > 0 + && metrics.dominantLandShareBasisPoints >= thresholds.minimumDominantLandShareBasisPoints + && metrics.dominantLandShareBasisPoints <= thresholds.maximumDominantLandShareBasisPoints + && metrics.dominantToSecondRatioBasisPoints + >= thresholds.minimumDominantToSecondRatioBasisPoints + && metrics.tierTwoOnDominantBasisPoints >= thresholds.minimumTierTwoOnDominantBasisPoints + && metrics.tierThreeOnDominantBasisPoints >= thresholds.minimumTierThreeOnDominantBasisPoints; +} + +export function measureGreaterRealmDominantContinent(input: Readonly<{ + grid: IndexedAxialGrid; + landMask: Uint8Array; + tierId: Uint8Array; + tierTwoId?: number; + tierThreeId?: number; + thresholds?: GreaterRealmDominantContinentThresholds; +}>): GreaterRealmDominantContinentMetrics { + assertGridFieldLengths(input.grid, [input.landMask, input.tierId]); + assertBinaryMask(input.landMask); + const tierTwoId = input.tierTwoId ?? 2; + const tierThreeId = input.tierThreeId ?? 3; + if (!Number.isSafeInteger(tierTwoId) || !Number.isSafeInteger(tierThreeId)) { + fail('GREATER_REALM_COMPOSITION_TIER_INVALID'); + } + const inventory = components(input.grid, input.landMask); + try { + if (inventory.components.length === 0) fail('GREATER_REALM_COMPOSITION_LAND_EMPTY'); + const orderedIds = inventory.components.map((_, id) => id).sort((first, second) => ( + inventory.components[second]!.length - inventory.components[first]!.length + || inventory.components[first]![0]! - inventory.components[second]![0]! + )); + const dominantId = orderedIds[0]!; + const componentSizesDescending = Object.freeze(orderedIds.map( + id => inventory.components[id]!.length, + )); + const landCellCount = componentSizesDescending.reduce( + (total, size) => total + size, + 0, + ); + const dominantLandmassCells = componentSizesDescending[0]!; + const secondLandmassCells = componentSizesDescending[1] ?? 0; + let tierTwoCells = 0; + let tierThreeCells = 0; + let tierTwoDominantCells = 0; + let tierThreeDominantCells = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (input.landMask[cell] !== 1) continue; + if (input.tierId[cell] === tierTwoId) { + tierTwoCells += 1; + if (inventory.componentId[cell] === dominantId) tierTwoDominantCells += 1; + } + if (input.tierId[cell] === tierThreeId) { + tierThreeCells += 1; + if (inventory.componentId[cell] === dominantId) tierThreeDominantCells += 1; + } + } + const withoutVectors = Object.freeze({ + landCellCount, + landmassCount: inventory.components.length, + dominantLandmassCells, + secondLandmassCells, + dominantLandShareBasisPoints: roundedBasisPoints( + dominantLandmassCells, + landCellCount, + ), + dominantToSecondRatioBasisPoints: secondLandmassCells === 0 + ? Number.MAX_SAFE_INTEGER + : roundedBasisPoints(dominantLandmassCells, secondLandmassCells), + tierTwoOnDominantBasisPoints: tierTwoCells === 0 + ? 0 + : roundedBasisPoints(tierTwoDominantCells, tierTwoCells), + tierThreeOnDominantBasisPoints: tierThreeCells === 0 + ? 0 + : roundedBasisPoints(tierThreeDominantCells, tierThreeCells), + }); + return Object.freeze({ + ...withoutVectors, + componentSizesDescending, + proof: isGreaterRealmDominantContinentComposition( + withoutVectors, + input.thresholds ?? GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS, + ), + }); + } finally { + clearComponentInventory(inventory); + } +} + +function distanceFromLand(grid: IndexedAxialGrid, landMask: Uint8Array): Uint32Array { + const unreachable = 0xffff_ffff; + const distance = new Uint32Array(grid.cellCount); + distance.fill(unreachable); + const queue = new Uint32Array(grid.cellCount); + let head = 0; + let tail = 0; + let completed = false; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (landMask[cell] !== 1) continue; + distance[cell] = 0; + queue[tail++] = cell; + } + try { + if (tail === 0) fail('GREATER_REALM_COMPOSITION_LAND_EMPTY'); + while (head < tail) { + const cell = queue[head++]!; + const nextDistance = distance[cell]! + 1; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || distance[neighbor]! <= nextDistance) continue; + distance[neighbor] = nextDistance; + queue[tail++] = neighbor; + } + } + for (const value of distance) { + if (value === unreachable) fail('GREATER_REALM_COMPOSITION_GRID_DISCONNECTED'); + } + completed = true; + return distance; + } finally { + queue.fill(0); + if (!completed) distance.fill(0); + } +} + +const TWELVE_SECTOR_NORMALS = Object.freeze([ + Object.freeze([1_000, 0] as const), + Object.freeze([866, 500] as const), + Object.freeze([500, 866] as const), + Object.freeze([0, 1_000] as const), + Object.freeze([-500, 866] as const), + Object.freeze([-866, 500] as const), + Object.freeze([-1_000, 0] as const), + Object.freeze([-866, -500] as const), + Object.freeze([-500, -866] as const), + Object.freeze([0, -1_000] as const), + Object.freeze([500, -866] as const), + Object.freeze([866, -500] as const), +]); + +function boundarySector( + q: number, + r: number, + centroid: Readonly<{ q: number; r: number }>, + sectorCount: number, +): number { + if (sectorCount !== TWELVE_SECTOR_NORMALS.length) { + fail('GREATER_REALM_COMPOSITION_SECTOR_COUNT_INVALID'); + } + const deltaQ = q - centroid.q; + const deltaR = r - centroid.r; + const x = 1_732 * (deltaQ * 2 + deltaR); + const y = 3_000 * deltaR; + let selected = 0; + let selectedDot = Number.NEGATIVE_INFINITY; + for (let sector = 0; sector < TWELVE_SECTOR_NORMALS.length; sector += 1) { + const normal = TWELVE_SECTOR_NORMALS[sector]!; + const dot = x * normal[0] + y * normal[1]; + if (dot > selectedDot) { + selected = sector; + selectedDot = dot; + } + } + return selected; +} + +export function isGreaterRealmDeepOceanBreathingRoom( + metrics: Omit, + thresholds: GreaterRealmOceanBreathingRoomThresholds = + GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS, +): boolean { + validateOceanThresholds(thresholds); + return metrics.boundaryCellCount > 0 + && metrics.saltwaterBoundaryBasisPoints === BASIS_POINTS + && metrics.minimumBoundaryLandDistance >= thresholds.minimumBoundaryLandDistance + && metrics.boundaryLandDistanceP05 >= thresholds.minimumBoundaryLandDistanceP05 + && metrics.boundaryLandDistanceP50 >= thresholds.minimumBoundaryLandDistanceP50 + && metrics.boundaryAtTargetShareBasisPoints + >= thresholds.minimumBoundaryAtTargetShareBasisPoints + && metrics.sectorBoundaryCellCounts.length === thresholds.sectorCount + && metrics.sectorMedianLandDistances.length === thresholds.sectorCount + && metrics.sectorBoundaryCellCounts.every(count => count > 0) + && metrics.sectorMedianLandDistances.every( + distance => distance >= thresholds.minimumSectorMedianLandDistance, + ); +} + +export function measureGreaterRealmOceanBreathingRoom(input: Readonly<{ + grid: IndexedAxialGrid; + landMask: Uint8Array; + saltwaterMask: Uint8Array; + thresholds?: GreaterRealmOceanBreathingRoomThresholds; +}>): GreaterRealmOceanBreathingRoomMetrics { + assertGridFieldLengths(input.grid, [input.landMask, input.saltwaterMask]); + assertBinaryMask(input.landMask); + assertBinaryMask(input.saltwaterMask); + const thresholds = input.thresholds ?? GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS; + validateOceanThresholds(thresholds); + const centroid = maskCentroid(input.grid, input.landMask); + const distance = distanceFromLand(input.grid, input.landMask); + const boundaryDistances: number[] = []; + const sectorDistances = Array.from({ length: thresholds.sectorCount }, () => [] as number[]); + try { + let saltwaterBoundaryCells = 0; + let boundaryAtTargetCells = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + let boundary = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + if (input.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction] === -1) { + boundary = true; + break; + } + } + if (!boundary) continue; + const cellDistance = distance[cell]!; + boundaryDistances.push(cellDistance); + if (input.saltwaterMask[cell] === 1) saltwaterBoundaryCells += 1; + if (cellDistance >= thresholds.targetBoundaryLandDistance) boundaryAtTargetCells += 1; + sectorDistances[boundarySector( + input.grid.q[cell]!, + input.grid.r[cell]!, + centroid, + thresholds.sectorCount, + )]!.push(cellDistance); + } + if (boundaryDistances.length === 0) fail('GREATER_REALM_COMPOSITION_BOUNDARY_EMPTY'); + boundaryDistances.sort((first, second) => first - second); + const sortedSectorDistances = sectorDistances.map(values => ( + values.sort((first, second) => first - second) + )); + const withoutProof = Object.freeze({ + boundaryCellCount: boundaryDistances.length, + saltwaterBoundaryBasisPoints: roundedBasisPoints( + saltwaterBoundaryCells, + boundaryDistances.length, + ), + minimumBoundaryLandDistance: boundaryDistances[0]!, + boundaryLandDistanceP05: percentile(boundaryDistances, 500), + boundaryLandDistanceP50: percentile(boundaryDistances, 5_000), + boundaryLandDistanceP95: percentile(boundaryDistances, 9_500), + boundaryAtTargetShareBasisPoints: roundedBasisPoints( + boundaryAtTargetCells, + boundaryDistances.length, + ), + sectorBoundaryCellCounts: Object.freeze( + sortedSectorDistances.map(values => values.length), + ), + sectorMedianLandDistances: Object.freeze(sortedSectorDistances.map( + values => percentile(values, 5_000), + )), + }); + return Object.freeze({ + ...withoutProof, + proof: isGreaterRealmDeepOceanBreathingRoom(withoutProof, thresholds), + }); + } finally { + boundaryDistances.fill(0); + for (const values of sectorDistances) values.fill(0); + distance.fill(0); + } +} + +export function isGreaterRealmPatchComposition( + metrics: Omit, + thresholds: GreaterRealmPatchCompositionThresholds, +): boolean { + validatePatchThresholds(thresholds); + return metrics.eligibleCellCount > 0 + && metrics.patchCellCount > 0 + && metrics.patchShareBasisPoints >= thresholds.minimumShareBasisPoints + && metrics.patchShareBasisPoints <= thresholds.maximumShareBasisPoints + && metrics.clusteredShareBasisPoints >= thresholds.minimumClusteredShareBasisPoints + && metrics.broadComponentCount >= thresholds.minimumBroadComponentCount + && metrics.tinyShareBasisPoints <= thresholds.maximumTinyShareBasisPoints + && metrics.largestComponentShareBasisPoints + <= thresholds.maximumLargestComponentShareBasisPoints; +} + +export function measureGreaterRealmPatchComposition(input: Readonly<{ + grid: IndexedAxialGrid; + eligibleMask: Uint8Array; + patchMask: Uint8Array; + thresholds: GreaterRealmPatchCompositionThresholds; +}>): GreaterRealmPatchCompositionMetrics { + assertGridFieldLengths(input.grid, [input.eligibleMask, input.patchMask]); + assertBinaryMask(input.eligibleMask); + assertBinaryMask(input.patchMask); + validatePatchThresholds(input.thresholds); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (input.patchMask[cell] === 1 && input.eligibleMask[cell] !== 1) { + fail('GREATER_REALM_COMPOSITION_PATCH_OUTSIDE_ELIGIBLE'); + } + } + const eligibleCellCount = countMask(input.eligibleMask); + const patchCellCount = countMask(input.patchMask); + if (eligibleCellCount === 0) fail('GREATER_REALM_COMPOSITION_ELIGIBLE_EMPTY'); + const inventory = components(input.grid, input.patchMask); + try { + const ascending = inventory.components.map(component => component.length) + .sort((first, second) => first - second); + const componentSizesDescending = Object.freeze([...ascending].reverse()); + const clusteredCells = ascending + .filter(size => size >= input.thresholds.clusteredComponentMinimumCells) + .reduce((total, size) => total + size, 0); + const tinyCells = ascending + .filter(size => size <= input.thresholds.tinyComponentMaximumCells) + .reduce((total, size) => total + size, 0); + const broadComponentCount = ascending.filter( + size => size >= input.thresholds.broadComponentMinimumCells, + ).length; + const withoutVectors = Object.freeze({ + eligibleCellCount, + patchCellCount, + patchShareBasisPoints: patchCellCount === 0 + ? 0 + : roundedBasisPoints(patchCellCount, eligibleCellCount), + componentCount: ascending.length, + broadComponentCount, + clusteredShareBasisPoints: patchCellCount === 0 + ? 0 + : roundedBasisPoints(clusteredCells, patchCellCount), + tinyShareBasisPoints: patchCellCount === 0 + ? 0 + : roundedBasisPoints(tinyCells, patchCellCount), + largestComponentShareBasisPoints: patchCellCount === 0 + ? 0 + : roundedBasisPoints(componentSizesDescending[0] ?? 0, patchCellCount), + componentSizeP50: percentile(ascending, 5_000), + componentSizeP90: percentile(ascending, 9_000), + }); + return Object.freeze({ + ...withoutVectors, + componentSizesDescending, + proof: isGreaterRealmPatchComposition(withoutVectors, input.thresholds), + }); + } finally { + clearComponentInventory(inventory); + } +} + +export function createGreaterRealmForestMask(input: Readonly<{ + waterRegime: Uint8Array; + biomeId: Uint8Array; + landformId: Uint8Array; + legacyProtectedCell: Uint8Array; + dryWaterRegime?: number; +}>): Uint8Array { + if ( + input.waterRegime.length !== input.biomeId.length + || input.waterRegime.length !== input.landformId.length + || input.waterRegime.length !== input.legacyProtectedCell.length + ) fail('GREATER_REALM_COMPOSITION_FIELD_LENGTH_INVALID'); + const dryWaterRegime = input.dryWaterRegime ?? 0; + const mask = new Uint8Array(input.waterRegime.length); + for (let cell = 0; cell < mask.length; cell += 1) { + // The frozen Lowlands surface is compatibility authority, not a sample of + // the new generator's patch composition. It is preserved exactly and is + // therefore excluded from both numerator and denominator below. + if ( + input.waterRegime[cell] !== dryWaterRegime + || input.legacyProtectedCell[cell] === 1 + ) continue; + const biome = input.biomeId[cell]!; + if ( + biome === 2 + || biome === 3 + // Biome 5 is also used by glacial landform 15. Snow and ice are not + // forest merely because the cool-forest visual family shares an ID. + || (biome === 5 && input.landformId[cell] !== 15) + ) mask[cell] = 1; + } + return mask; +} + +export function measureGreaterRealmForestPatchComposition(input: Readonly<{ + grid: IndexedAxialGrid; + waterRegime: Uint8Array; + biomeId: Uint8Array; + landformId: Uint8Array; + legacyProtectedCell: Uint8Array; + dryWaterRegime?: number; + thresholds?: GreaterRealmPatchCompositionThresholds; +}>): GreaterRealmPatchCompositionMetrics { + assertGridFieldLengths(input.grid, [ + input.waterRegime, + input.biomeId, + input.landformId, + input.legacyProtectedCell, + ]); + const dryWaterRegime = input.dryWaterRegime ?? 0; + const eligibleMask = Uint8Array.from( + input.waterRegime, + (value, cell) => ( + value === dryWaterRegime && input.legacyProtectedCell[cell] !== 1 ? 1 : 0 + ), + ); + let forestMask: Uint8Array | undefined; + try { + forestMask = createGreaterRealmForestMask({ + waterRegime: input.waterRegime, + biomeId: input.biomeId, + landformId: input.landformId, + legacyProtectedCell: input.legacyProtectedCell, + dryWaterRegime, + }); + return measureGreaterRealmPatchComposition({ + grid: input.grid, + eligibleMask, + patchMask: forestMask, + thresholds: input.thresholds ?? GREATER_REALM_FOREST_PATCH_THRESHOLDS, + }); + } finally { + eligibleMask.fill(0); + forestMask?.fill(0); + } +} + +export function createGreaterRealmMountainMask(input: Readonly<{ + grid: IndexedAxialGrid; + waterRegime: Uint8Array; + ridgeId: Int32Array; + biomeId: Uint8Array; + landformId: Uint8Array; + elevation: Int32Array; + slope: Uint16Array; + dryWaterRegime?: number; +}>): Uint8Array { + assertGridFieldLengths(input.grid, [ + input.waterRegime, + input.ridgeId, + input.biomeId, + input.landformId, + input.elevation, + input.slope, + ]); + const dryWaterRegime = input.dryWaterRegime ?? 0; + const core = new Uint8Array(input.grid.cellCount); + let mask: Uint8Array | undefined; + let completed = false; + try { + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (input.waterRegime[cell] !== dryWaterRegime) continue; + if ( + input.ridgeId[cell]! > 0 + || (input.biomeId[cell] === 19 && input.landformId[cell] === 6) + || input.landformId[cell] === 7 + ) core[cell] = 1; + } + mask = new Uint8Array(core); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + mask[cell] === 1 + || input.waterRegime[cell] !== dryWaterRegime + || input.elevation[cell]! < 5_500 + || input.slope[cell]! < 600 + ) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = input.grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && core[neighbor] === 1) { + mask[cell] = 1; + break; + } + } + } + completed = true; + return mask; + } finally { + core.fill(0); + if (!completed) mask?.fill(0); + } +} + +function componentBeltMetrics( + grid: IndexedAxialGrid, + cells: readonly number[], + landCentroid: Readonly<{ q: number; r: number }>, +): Readonly<{ + cells: number; + maximumAxialSpan: number; + centroidOffset: number; + axialAnisotropyBasisPoints: number; +}> { + let minimumQ = Number.POSITIVE_INFINITY; + let maximumQ = Number.NEGATIVE_INFINITY; + let minimumR = Number.POSITIVE_INFINITY; + let maximumR = Number.NEGATIVE_INFINITY; + let minimumS = Number.POSITIVE_INFINITY; + let maximumS = Number.NEGATIVE_INFINITY; + let qTotal = 0; + let rTotal = 0; + for (const cell of cells) { + const q = grid.q[cell]!; + const r = grid.r[cell]!; + const s = -q - r; + minimumQ = Math.min(minimumQ, q); + maximumQ = Math.max(maximumQ, q); + minimumR = Math.min(minimumR, r); + maximumR = Math.max(maximumR, r); + minimumS = Math.min(minimumS, s); + maximumS = Math.max(maximumS, s); + qTotal += q; + rTotal += r; + } + const spans = [maximumQ - minimumQ, maximumR - minimumR, maximumS - minimumS] + .sort((first, second) => first - second); + // A one-cell-wide axial chain has a zero transverse span. Treat that as a + // bounded width of one cell instead of skipping to the next (longitudinal) + // span, otherwise the clearest natural belt is incorrectly scored 1:1. + const boundedMinimumSpan = Math.max(1, spans[0]!); + const maximumAxialSpan = spans[2]!; + const centroidQ = roundDivide(qTotal, cells.length); + const centroidR = roundDivide(rTotal, cells.length); + return Object.freeze({ + cells: cells.length, + maximumAxialSpan, + centroidOffset: axialDistance(centroidQ, centroidR, landCentroid.q, landCentroid.r), + axialAnisotropyBasisPoints: roundedBasisPoints(maximumAxialSpan, boundedMinimumSpan), + }); +} + +export function isGreaterRealmMountainSystemComposition( + metrics: Omit, + patchThresholds: GreaterRealmPatchCompositionThresholds = + GREATER_REALM_MOUNTAIN_PATCH_THRESHOLDS, +): boolean { + return isGreaterRealmPatchComposition(metrics, patchThresholds) + && metrics.offCentreBeltCount > 0; +} + +export function measureGreaterRealmMountainSystemComposition(input: Readonly<{ + grid: IndexedAxialGrid; + landMask: Uint8Array; + mountainMask: Uint8Array; + patchThresholds?: GreaterRealmPatchCompositionThresholds; + beltThresholds?: GreaterRealmMountainBeltThresholds; +}>): GreaterRealmMountainSystemMetrics { + assertGridFieldLengths(input.grid, [input.landMask, input.mountainMask]); + assertBinaryMask(input.landMask); + assertBinaryMask(input.mountainMask); + const patchThresholds = input.patchThresholds ?? GREATER_REALM_MOUNTAIN_PATCH_THRESHOLDS; + const beltThresholds = input.beltThresholds ?? GREATER_REALM_MOUNTAIN_BELT_THRESHOLDS; + validatePatchThresholds(patchThresholds); + validateMountainBeltThresholds(beltThresholds); + const patch = measureGreaterRealmPatchComposition({ + grid: input.grid, + eligibleMask: input.landMask, + patchMask: input.mountainMask, + thresholds: patchThresholds, + }); + const landCentroid = maskCentroid(input.grid, input.landMask); + const inventory = components(input.grid, input.mountainMask); + try { + const belts = inventory.components.map(component => ( + componentBeltMetrics(input.grid, component, landCentroid) + )); + const acceptedBelts = belts.filter(belt => ( + belt.cells >= beltThresholds.minimumBeltCells + && belt.maximumAxialSpan >= beltThresholds.minimumMaximumAxialSpan + && belt.centroidOffset >= beltThresholds.minimumCentroidOffset + && belt.axialAnisotropyBasisPoints + >= beltThresholds.minimumAxialAnisotropyBasisPoints + )); + let maximumBeltCells = 0; + let maximumBeltAxialSpan = 0; + let maximumBeltCentroidOffset = 0; + let maximumBeltAxialAnisotropyBasisPoints = 0; + for (const belt of acceptedBelts) { + maximumBeltCells = Math.max(maximumBeltCells, belt.cells); + maximumBeltAxialSpan = Math.max(maximumBeltAxialSpan, belt.maximumAxialSpan); + maximumBeltCentroidOffset = Math.max( + maximumBeltCentroidOffset, + belt.centroidOffset, + ); + maximumBeltAxialAnisotropyBasisPoints = Math.max( + maximumBeltAxialAnisotropyBasisPoints, + belt.axialAnisotropyBasisPoints, + ); + } + const withoutProof = Object.freeze({ + ...patch, + offCentreBeltCount: acceptedBelts.length, + maximumBeltCells, + maximumBeltAxialSpan, + maximumBeltCentroidOffset, + maximumBeltAxialAnisotropyBasisPoints, + }); + return Object.freeze({ + ...withoutProof, + proof: isGreaterRealmMountainSystemComposition(withoutProof, patchThresholds), + }); + } finally { + clearComponentInventory(inventory); + } +} + +/** + * Measures the complete owner-review composition contract from final terrain + * authority. The temporary masks are intentionally kept out of both the + * private package and the sanitized aggregate evidence. + */ +export function measureGreaterRealmNaturalComposition(input: Readonly<{ + grid: IndexedAxialGrid; + canvasRadius: number; + elevation: Int32Array; + tierId: Uint8Array; + waterRegime: Uint8Array; + biomeId: Uint8Array; + legacyProtectedCell: Uint8Array; + ridgeId: Int32Array; + landformId: Uint8Array; + slope: Uint16Array; + seaLevel?: number; + dryWaterRegime?: number; + oceanWaterRegime?: number; + seaWaterRegime?: number; +}>): GreaterRealmNaturalCompositionMetrics { + assertGridFieldLengths(input.grid, [ + input.elevation, + input.tierId, + input.waterRegime, + input.biomeId, + input.legacyProtectedCell, + input.ridgeId, + input.landformId, + input.slope, + ]); + const dryWaterRegime = input.dryWaterRegime ?? 0; + const oceanWaterRegime = input.oceanWaterRegime ?? 1; + const seaWaterRegime = input.seaWaterRegime ?? 5; + let landMask: Uint8Array | undefined; + let saltwaterMask: Uint8Array | undefined; + let forestMask: Uint8Array | undefined; + let dryMask: Uint8Array | undefined; + let mountainMask: Uint8Array | undefined; + try { + const topographicLandMask = createGreaterRealmTopographicLandMask( + input.elevation, + input.seaLevel ?? 0, + ); + landMask = topographicLandMask; + saltwaterMask = Uint8Array.from( + input.waterRegime, + value => value === oceanWaterRegime || value === seaWaterRegime ? 1 : 0, + ); + forestMask = createGreaterRealmForestMask({ + waterRegime: input.waterRegime, + biomeId: input.biomeId, + landformId: input.landformId, + legacyProtectedCell: input.legacyProtectedCell, + dryWaterRegime, + }); + dryMask = Uint8Array.from( + input.waterRegime, + (value, cell) => ( + value === dryWaterRegime + && input.legacyProtectedCell[cell] !== 1 + && topographicLandMask[cell] === 1 + ? 1 + : 0 + ), + ); + mountainMask = createGreaterRealmMountainMask({ + grid: input.grid, + waterRegime: input.waterRegime, + ridgeId: input.ridgeId, + biomeId: input.biomeId, + landformId: input.landformId, + elevation: input.elevation, + slope: input.slope, + dryWaterRegime, + }); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (dryMask[cell] === 1) continue; + forestMask[cell] = 0; + mountainMask[cell] = 0; + } + return Object.freeze({ + landSilhouette: measureGreaterRealmLandSilhouette({ + grid: input.grid, + landMask, + canvasRadius: input.canvasRadius, + }), + dominantContinent: measureGreaterRealmDominantContinent({ + grid: input.grid, + landMask, + tierId: input.tierId, + }), + oceanBreathingRoom: measureGreaterRealmOceanBreathingRoom({ + grid: input.grid, + landMask, + saltwaterMask, + }), + forestPatches: measureGreaterRealmPatchComposition({ + grid: input.grid, + eligibleMask: dryMask, + patchMask: forestMask, + thresholds: GREATER_REALM_FOREST_PATCH_THRESHOLDS, + }), + mountainSystems: measureGreaterRealmMountainSystemComposition({ + grid: input.grid, + landMask: dryMask, + mountainMask, + }), + }); + } finally { + landMask?.fill(0); + saltwaterMask?.fill(0); + forestMask?.fill(0); + dryMask?.fill(0); + mountainMask?.fill(0); + } +} diff --git a/scripts/atlas/greater-realm-contracts.ts b/scripts/atlas/greater-realm-contracts.ts index 6aabf63f..a9f3edb4 100644 --- a/scripts/atlas/greater-realm-contracts.ts +++ b/scripts/atlas/greater-realm-contracts.ts @@ -4,7 +4,7 @@ export const GREATER_REALM_SANITIZED_REVIEW_SCHEMA = 'warpkeep.greater-realm.candidate-review.v1' as const; export const GREATER_REALM_SANITIZED_PRIVACY_BOUNDARY = 'aggregate-only-no-private-generation-material-v1' as const; -export const GREATER_REALM_MINIMUM_CANDIDATE_COUNT = 8; +export const GREATER_REALM_MINIMUM_CANDIDATE_COUNT = 1; export const GREATER_REALM_MAXIMUM_CANDIDATE_COUNT = 16; export const GREATER_REALM_MINIMUM_ACTIVE_CELL_COUNT = 100_000; export const GREATER_REALM_MAXIMUM_ACTIVE_CELL_COUNT = 150_000; @@ -18,6 +18,14 @@ export const GREATER_REALM_SHA256_PATTERN = /^[0-9a-f]{64}$/u; export const GREATER_REALM_GENERATOR_VERSION_PATTERN = /^[a-z0-9](?:[a-z0-9.-]{0,62}[a-z0-9])?$/u; +export const GREATER_REALM_COMPOSITION_PROOF_KEYS = Object.freeze([ + 'naturalLandSilhouette', + 'dominantContinentComposition', + 'deepOceanBreathingRoom', + 'forestPatchComposition', + 'mountainSystemComposition', +] as const); + export const GREATER_REALM_PROOF_KEYS = Object.freeze([ 'activeMaskConnected', 'advancedGeomorphology', @@ -40,6 +48,7 @@ export const GREATER_REALM_PROOF_KEYS = Object.freeze([ 'regionPassableLand', 'regionLandCoherence', 'regionGraph', + ...GREATER_REALM_COMPOSITION_PROOF_KEYS, ] as const); export type GreaterRealmProofKey = typeof GREATER_REALM_PROOF_KEYS[number]; diff --git a/scripts/atlas/greater-realm-topography.ts b/scripts/atlas/greater-realm-topography.ts index 26dcf000..a8620e6b 100644 --- a/scripts/atlas/greater-realm-topography.ts +++ b/scripts/atlas/greater-realm-topography.ts @@ -9,6 +9,7 @@ const WATER_LAKE = 2; const WATER_RIVER = 3; const WATER_STREAM = 4; const WATER_SEA = 5; +const MINIMUM_GENERATED_FOREST_PATCH_CELLS = 28; export type GreaterRealmTopographyMetrics = Readonly<{ elevationMinimum: number; @@ -209,6 +210,80 @@ function isCompatibleBiomeLandformPair( return DRY_LANDFORMS_BY_BIOME[biome]?.has(landform) === true; } +function consolidateGeneratedForestPatches(input: Readonly<{ + grid: IndexedAxialGrid; + waterRegime: Uint8Array; + legacyProtectedCell: Uint8Array; + biomeId: Uint8Array; + landformId: Uint8Array; + distanceToFreshwater: Uint16Array; + slope: Uint16Array; + moisture: Int32Array; + exposure: Int32Array; +}>): void { + const { grid } = input; + const forestMask = new Uint8Array(grid.cellCount); + const seen = new Uint8Array(grid.cellCount); + const queue = new Uint32Array(grid.cellCount); + try { + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + input.waterRegime[cell] !== WATER_DRY + || input.legacyProtectedCell[cell] === 1 + ) continue; + const biome = input.biomeId[cell]!; + if ( + biome === 2 + || biome === 3 + || (biome === 5 && input.landformId[cell] !== 15) + ) forestMask[cell] = 1; + } + + for (let start = 0; start < grid.cellCount; start += 1) { + if (forestMask[start] !== 1 || seen[start] === 1) continue; + let head = 0; + let tail = 0; + queue[tail++] = start; + seen[start] = 1; + while (head < tail) { + const cell = queue[head++]!; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || forestMask[neighbor] !== 1 + || seen[neighbor] === 1 + ) continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + } + } + if (tail >= MINIMUM_GENERATED_FOREST_PATCH_CELLS) continue; + for (let offset = 0; offset < tail; offset += 1) { + const cell = queue[offset]!; + if ( + input.distanceToFreshwater[cell]! <= 2 + && input.slope[cell]! < 550 + && input.moisture[cell]! > 800 + ) { + input.biomeId[cell] = 4; + input.landformId[cell] = input.moisture[cell]! > 1_900 ? 5 : 3; + } else if (input.exposure[cell]! >= 55) { + input.biomeId[cell] = 8; + input.landformId[cell] = input.slope[cell]! < 550 ? 3 : 4; + } else { + input.biomeId[cell] = 1; + input.landformId[cell] = input.slope[cell]! < 550 ? 3 : 4; + } + } + } + } finally { + forestMask.fill(0); + seen.fill(0); + queue.fill(0); + } +} + /** * Derive private, fixed-point terrain authority after final water routing. * These fields are offline candidate evidence; they do not activate runtime @@ -584,6 +659,20 @@ export function deriveGreaterRealmTopography(input: Readonly<{ landformId.set(nextLandform); } + // Broad forest masses remain, while generated salt-and-pepper woods become + // meadow/heath transitions. The frozen Lowlands surface is never touched. + consolidateGeneratedForestPatches({ + grid, + waterRegime, + legacyProtectedCell, + biomeId, + landformId, + distanceToFreshwater, + slope, + moisture, + exposure, + }); + let incompatibleBiomeLandformPairCount = 0; for (let cell = 0; cell < grid.cellCount; cell += 1) { if (!isCompatibleBiomeLandformPair( diff --git a/scripts/atlas/verify-public-boundary.mjs b/scripts/atlas/verify-public-boundary.mjs index 2b1d2327..db034c2c 100644 --- a/scripts/atlas/verify-public-boundary.mjs +++ b/scripts/atlas/verify-public-boundary.mjs @@ -126,10 +126,12 @@ const SANITIZED_REVIEW_EVIDENCE_PATH = const SANITIZED_REVIEW_EVIDENCE_PREFIX = 'docs/evidence/greater-realm/'; const SANITIZED_REVIEW_EVIDENCE_README = 'docs/evidence/greater-realm/README.md'; -const SANITIZED_REVIEW_EVIDENCE_README_BYTES = 1_371; +const SANITIZED_REVIEW_EVIDENCE_README_BYTES = 1_387; const SANITIZED_REVIEW_EVIDENCE_README_SHA256 = - '77261c47d7511547ca5265b7b358e5676d939a69aa9a15766d7906a0282d0450'; + 'b18d53a323c25749e7abc3695c6680332920d2fdfd5f1fe6151aef08d395e0b4'; const SANITIZED_REVIEW_MAXIMUM_BYTES = 4 * 1024 * 1024; +const SANITIZED_REVIEW_MINIMUM_CANDIDATE_COUNT = 1; +const SANITIZED_REVIEW_MAXIMUM_CANDIDATE_COUNT = 16; const SANITIZED_REVIEW_SCHEMA = 'warpkeep.greater-realm.candidate-review.v1'; const SANITIZED_REVIEW_PRIVACY_BOUNDARY = 'aggregate-only-no-private-generation-material-v1'; @@ -155,6 +157,11 @@ const SANITIZED_REVIEW_PROOF_KEYS = Object.freeze([ 'regionPassableLand', 'regionLandCoherence', 'regionGraph', + 'naturalLandSilhouette', + 'dominantContinentComposition', + 'deepOceanBreathingRoom', + 'forestPatchComposition', + 'mountainSystemComposition', ]); const SANITIZED_REVIEW_CANDIDATE_KEYS = Object.freeze([ 'candidateHandle', @@ -1056,8 +1063,8 @@ function validateSanitizedReviewEvidence(text) { || typeof row.reportDigest !== 'string' || !SANITIZED_REVIEW_SHA256.test(row.reportDigest) || !Array.isArray(row.candidates) - || row.candidates.length < 8 - || row.candidates.length > 16 + || row.candidates.length < SANITIZED_REVIEW_MINIMUM_CANDIDATE_COUNT + || row.candidates.length > SANITIZED_REVIEW_MAXIMUM_CANDIDATE_COUNT || row.candidateCount !== row.candidates.length ) invalidSanitizedReview(); const candidates = row.candidates.map(validateSanitizedCandidate); @@ -1065,7 +1072,8 @@ function validateSanitizedReviewEvidence(text) { if ( new Set(handles).size !== handles.length || handles.some((handle, index) => index > 0 && handles[index - 1] >= handle) - || candidates.filter(candidate => candidate.eligible).length < 8 + || candidates.filter(candidate => candidate.eligible).length + < SANITIZED_REVIEW_MINIMUM_CANDIDATE_COUNT || (row.selectionStatus === 'pending' && row.selectedCandidateHandle !== null) || (row.selectionStatus === 'selected' && ( row.selectedCandidateHandle === null diff --git a/tests/greaterRealmAdvancedInvariants.test.ts b/tests/greaterRealmAdvancedInvariants.test.ts index d745b83c..bc3ef544 100644 --- a/tests/greaterRealmAdvancedInvariants.test.ts +++ b/tests/greaterRealmAdvancedInvariants.test.ts @@ -14,8 +14,8 @@ import { transformLegacyLowlandsToGlobal, } from '../scripts/atlas/greater-realm-legacy-lowlands'; -const PINNED_ROOT_LABEL = 'greater-realm-ordinary-parent-a'; -const PINNED_ORDINAL = 9; +const PINNED_ROOT_LABEL = 'greater-realm-yield-regression-b'; +const PINNED_ORDINAL = 0; const PRIVATE_CANVAS_RADIUS = 270; const HEX_NEIGHBOR_COUNT = 6; const REGION_COUNT = 10; diff --git a/tests/greaterRealmCandidateGenerator.test.ts b/tests/greaterRealmCandidateGenerator.test.ts index d3e01938..502fc7b4 100644 --- a/tests/greaterRealmCandidateGenerator.test.ts +++ b/tests/greaterRealmCandidateGenerator.test.ts @@ -21,10 +21,10 @@ import { type IntegerTerrainArray, } from '../scripts/atlas/greater-realm-terrain'; -const PINNED_ROOT_LABEL = 'greater-realm-ordinary-parent-a'; +const PINNED_ROOT_LABEL = 'greater-realm-yield-regression-b'; const SAME_FIRST_WORD_BASELINE_ROOT_INDEX = 23_248; const SAME_FIRST_WORD_ROOT_INDEX = 41_769; -const PINNED_ORDINAL = 9; +const PINNED_ORDINAL = 0; const SAME_FIRST_WORD_ORDINAL = 0; const EXPECTED_ACTIVE_CELL_MINIMUM = 100_000; const EXPECTED_ACTIVE_CELL_MAXIMUM = 150_000; diff --git a/tests/greaterRealmCandidatePackage.test.ts b/tests/greaterRealmCandidatePackage.test.ts index 3cc57266..401fd808 100644 --- a/tests/greaterRealmCandidatePackage.test.ts +++ b/tests/greaterRealmCandidatePackage.test.ts @@ -17,6 +17,9 @@ import { isAbsolute, join } from 'node:path'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { + GREATER_REALM_GENERATOR_VERSION, + GREATER_REALM_TERRAIN_SEED_NAMESPACE, + deriveGreaterRealmCandidateSeedMaterial, generateGreaterRealmCandidate, type GreaterRealmPrivateCandidate, } from '../scripts/atlas/greater-realm-candidate-generator'; @@ -42,8 +45,8 @@ import { const SOURCE_COMMIT = 'a'.repeat(40); const BATCH_HANDLE = 'GR-B-AAAAAAAAAAAAAAAA'; const CANDIDATE_HANDLE = 'GR-A-AAAAAAAAAAAAAAAA'; -const CANDIDATE_ROOT_LABEL = 'greater-realm-ordinary-parent-a'; -const CANDIDATE_ORDINAL = 9; +const CANDIDATE_ROOT_LABEL = 'greater-realm-yield-regression-b'; +const CANDIDATE_ORDINAL = 0; const PERFORMANCE = Object.freeze({ generationMilliseconds: 1_200, processPeakMemoryMiB: 512, @@ -314,6 +317,148 @@ describe('Greater Realm owner-only candidate package', () => { } }, 60_000); + it('binds package version and stable terrain seed namespace as separate authorities', () => { + const fixture = requireFixture(); + const rootSeed = candidateRoot(); + const candidateSeed = Buffer.from(fixture.candidate.seedMaterial); + const manifestBytes = fixture.workspace.readFile( + candidateRelativePath('manifest.private.json'), + ); + let derivedSeed: Buffer | undefined; + try { + expect(GREATER_REALM_GENERATOR_VERSION) + .toBe('greater-realm-v2-natural-continent-pr-a.5'); + expect(GREATER_REALM_TERRAIN_SEED_NAMESPACE) + .toBe('greater-realm-v2-natural-continent-pr-a.3'); + expect(GREATER_REALM_GENERATOR_VERSION).not.toBe( + GREATER_REALM_TERRAIN_SEED_NAMESPACE, + ); + derivedSeed = deriveGreaterRealmCandidateSeedMaterial(rootSeed, CANDIDATE_ORDINAL); + expect(candidateSeed.equals(derivedSeed)).toBe(true); + const manifest = JSON.parse(manifestBytes.toString('utf8')) as { + generatorVersion: string; + seedNamespace: string; + }; + expect(manifest.generatorVersion).toBe(GREATER_REALM_GENERATOR_VERSION); + expect(manifest.seedNamespace).toBe(GREATER_REALM_TERRAIN_SEED_NAMESPACE); + } finally { + rootSeed.fill(0); + candidateSeed.fill(0); + derivedSeed?.fill(0); + manifestBytes.fill(0); + } + }); + + it('renders an opaque outer-fog topology proxy and keeps rivers inside the land silhouette', async () => { + const fixture = requireFixture(); + const topologyPreview = await renderGreaterRealmPrivatePreview( + fixture.candidate, + 'regions', + ); + const silhouettePreview = await renderGreaterRealmPrivatePreview( + fixture.candidate, + 'silhouette', + ); + let topologyPixels: Buffer | undefined; + let silhouettePixels: Buffer | undefined; + try { + const sharpModule = await import('sharp'); + const topology = await sharpModule.default(topologyPreview) + .raw() + .toBuffer({ resolveWithObject: true }); + const silhouette = await sharpModule.default(silhouettePreview) + .raw() + .toBuffer({ resolveWithObject: true }); + topologyPixels = topology.data; + silhouettePixels = silhouette.data; + expect(topology.info).toMatchObject({ width: 1_280, height: 1_024, channels: 4 }); + expect(silhouette.info).toMatchObject({ width: 1_280, height: 1_024, channels: 4 }); + expect([...topologyPixels.subarray(0, 4)]).toEqual([24, 22, 31, 255]); + + const regionColors = new Set([ + '87:144:84', '117:159:195', '205:151:76', '84:147:127', '103:124:164', + '151:96:80', '92:112:82', '116:103:126', '85:121:142', '116:84:135', + ]); + const oceanTones = new Set(); + let fogPixels = 0; + let regionPixels = 0; + let inlandWaterPixels = 0; + let nonOpaquePixels = 0; + for (let y = 0; y < 980; y += 1) { + for (let x = 0; x < 1_280; x += 1) { + const offset = (y * 1_280 + x) * 4; + const red = topologyPixels[offset]!; + const green = topologyPixels[offset + 1]!; + const blue = topologyPixels[offset + 2]!; + if (topologyPixels[offset + 3] !== 255) nonOpaquePixels += 1; + const key = `${red}:${green}:${blue}`; + if (key === '24:22:31') fogPixels += 1; + if (regionColors.has(key)) regionPixels += 1; + if (key === '66:126:171' || key === '72:143:190') inlandWaterPixels += 1; + for (let band = 0; band <= 5; band += 1) { + if (key === `${54 - band * 5}:${91 - band * 8}:${132 - band * 10}`) { + oceanTones.add(key); + } + } + } + } + expect(nonOpaquePixels).toBe(0); + expect(fogPixels).toBeGreaterThan(100_000); + expect(regionPixels).toBeGreaterThan(10_000); + expect(inlandWaterPixels).toBeGreaterThan(100); + expect(oceanTones.size).toBeGreaterThanOrEqual(4); + + let minimumQ = Number.POSITIVE_INFINITY; + let maximumQ = Number.NEGATIVE_INFINITY; + let minimumR = Number.POSITIVE_INFINITY; + let maximumR = Number.NEGATIVE_INFINITY; + for (let cell = 0; cell < fixture.candidate.grid.cellCount; cell += 1) { + minimumQ = Math.min(minimumQ, fixture.candidate.grid.q[cell]!); + maximumQ = Math.max(maximumQ, fixture.candidate.grid.q[cell]!); + minimumR = Math.min(minimumR, fixture.candidate.grid.r[cell]!); + maximumR = Math.max(maximumR, fixture.candidate.grid.r[cell]!); + } + const qSpan = maximumQ - minimumQ + 1; + const rSpan = maximumR - minimumR + 1; + const scale = Math.max(1, Math.floor(Math.min( + (1_280 - 80) / (qSpan + rSpan / 2), + (1_024 - 120) / rSpan, + ))); + let renderedRiverAsLand = false; + for ( + let cell = fixture.candidate.grid.cellCount - 1; + cell >= 0 && !renderedRiverAsLand; + cell -= 1 + ) { + if ( + fixture.candidate.elevation[cell]! <= 0 + || ( + fixture.candidate.waterRegime[cell] !== 3 + && fixture.candidate.waterRegime[cell] !== 4 + ) + ) continue; + const x = 40 + Math.round(( + (fixture.candidate.grid.q[cell]! - minimumQ) + + (fixture.candidate.grid.r[cell]! - minimumR) / 2 + ) * scale); + const y = 60 + Math.round( + (fixture.candidate.grid.r[cell]! - minimumR) * scale * 0.86, + ); + const offset = (y * 1_280 + x) * 4; + renderedRiverAsLand = silhouettePixels[offset] === 142 + && silhouettePixels[offset + 1] === 164 + && silhouettePixels[offset + 2] === 105 + && silhouettePixels[offset + 3] === 255; + } + expect(renderedRiverAsLand).toBe(true); + } finally { + topologyPixels?.fill(0); + silhouettePixels?.fill(0); + topologyPreview.fill(0); + silhouettePreview.fill(0); + } + }, 45_000); + it('binds deterministic 15-by-15 axial bins, topography patches, and toolchain pins', () => { const fixture = requireFixture(); const bytes = fixture.workspace.readFile(candidateRelativePath('manifest.private.json')); @@ -618,6 +763,21 @@ describe('Greater Realm owner-only candidate package', () => { }); }, 45_000); + it('rejects terrain seed namespace drift even when the expected digest is updated', async () => { + const relativePath = candidateRelativePath('manifest.private.json'); + const original = requireFixture().workspace.readFile(relativePath); + const parsed = JSON.parse(original.toString('utf8')) as Record; + original.fill(0); + parsed.seedNamespace = 'greater-realm-v2-natural-continent-pr-unreviewed'; + const corrupted = Buffer.from(`${JSON.stringify(parsed, null, 2)}\n`, 'utf8'); + const corruptedDigest = createHash('sha256').update(corrupted).digest('hex'); + + await replacePrivateFile(relativePath, corrupted, async () => { + await expect(verifyFixture({ expectedManifestDigest: corruptedDigest })) + .rejects.toThrow('GREATER_REALM_PRIVATE_MANIFEST_INVALID'); + }); + }, 45_000); + it('rejects unknown manifest fields even when the expected digest is updated', async () => { const relativePath = candidateRelativePath('manifest.private.json'); const original = requireFixture().workspace.readFile(relativePath); diff --git a/tests/greaterRealmCliSecurity.test.ts b/tests/greaterRealmCliSecurity.test.ts index 84f95bc7..4f049a88 100644 --- a/tests/greaterRealmCliSecurity.test.ts +++ b/tests/greaterRealmCliSecurity.test.ts @@ -78,7 +78,7 @@ afterEach(() => { }); describe('Greater Realm atlas CLI security boundary', () => { - it('creates an unranked Pareto-diversity shortlist without choosing a winner', () => { + it('creates pending unranked owner-review sets without choosing a winner', () => { const candidate = ( suffix: string, values: readonly [number, number, number, number, number], @@ -178,6 +178,18 @@ describe('Greater Realm atlas CLI security boundary', () => { expect(JSON.stringify(shortlist)).not.toMatch(/(?:winner|recommend|score|rank":\s*[0-9])/iu); expect(JSON.stringify(shortlist)).not.toMatch(/(?:coordinate|seed|transform|chunkKey)/iu); + const single = buildGreaterRealmPrivateCandidateShortlist({ + ...review, + candidates: review.candidates.slice(0, 1), + }, privateMetrics.slice(0, 1)); + expect(single.shortlistCount).toBe(1); + expect(single.candidateHandles).toEqual([candidates[0]!.candidateHandle]); + expect(single.method).toBe('single-candidate-reference-review-v1'); + expect(single.selectionStatus).toBe('pending'); + expect(single.selectedCandidateHandle).toBeNull(); + expect(single.ranked).toBe(false); + expect(single.automaticSelection).toBe(false); + expect(() => buildGreaterRealmPrivateCandidateShortlist({ ...review, candidates: review.candidates.map((entry, index) => index === 0 @@ -251,7 +263,7 @@ describe('Greater Realm atlas CLI security boundary', () => { '--workspace', forbiddenWorkspace, '--count', - '8', + '1', '--maximum-attempts', '8', ]); diff --git a/tests/greaterRealmComposition.test.ts b/tests/greaterRealmComposition.test.ts new file mode 100644 index 00000000..933ae6af --- /dev/null +++ b/tests/greaterRealmComposition.test.ts @@ -0,0 +1,611 @@ +import { describe, expect, it } from 'vitest'; + +import { + GREATER_REALM_COMPOSITION_PROOF_KEYS, + GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS, + GREATER_REALM_FOREST_PATCH_THRESHOLDS, + GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS, + GREATER_REALM_MOUNTAIN_PATCH_THRESHOLDS, + GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS, + createGreaterRealmForestMask, + createGreaterRealmMountainMask, + createGreaterRealmTopographicLandMask, + isGreaterRealmDeepOceanBreathingRoom, + isGreaterRealmDominantContinentComposition, + isGreaterRealmNaturalLandSilhouette, + isGreaterRealmPatchComposition, + measureGreaterRealmDominantContinent, + measureGreaterRealmLandSilhouette, + measureGreaterRealmMountainSystemComposition, + measureGreaterRealmNaturalComposition, + measureGreaterRealmOceanBreathingRoom, + measureGreaterRealmPatchComposition, +} from '../scripts/atlas/greater-realm-composition'; +import { + indexGreaterRealmAxialGrid, + type AxialCoordinate, + type IndexedAxialGrid, +} from '../scripts/atlas/greater-realm-terrain'; + +function axialDistance(q: number, r: number, otherQ = 0, otherR = 0): number { + const deltaQ = q - otherQ; + const deltaR = r - otherR; + return Math.max(Math.abs(deltaQ), Math.abs(deltaR), Math.abs(-deltaQ - deltaR)); +} + +function axialDisc(radius: number): readonly AxialCoordinate[] { + const coordinates: AxialCoordinate[] = []; + for (let q = -radius; q <= radius; q += 1) { + const minimumR = Math.max(-radius, -q - radius); + const maximumR = Math.min(radius, -q + radius); + for (let r = minimumR; r <= maximumR; r += 1) coordinates.push({ q, r }); + } + return coordinates; +} + +function maskFrom( + grid: IndexedAxialGrid, + predicate: (q: number, r: number) => boolean, +): Uint8Array { + return Uint8Array.from( + { length: grid.cellCount }, + (_, cell) => predicate(grid.q[cell]!, grid.r[cell]!) ? 1 : 0, + ); +} + +function rotateAxial(coordinate: AxialCoordinate): AxialCoordinate { + return Object.freeze({ q: -coordinate.r, r: coordinate.q + coordinate.r }); +} + +function transformMask( + grid: IndexedAxialGrid, + mask: Uint8Array, + transform: (coordinate: AxialCoordinate) => AxialCoordinate, +): Uint8Array { + const transformed = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (mask[cell] !== 1) continue; + const target = grid.indexOf(transform({ q: grid.q[cell]!, r: grid.r[cell]! })); + if (target >= 0) transformed[target] = 1; + } + return transformed; +} + +function irregularContinent(grid: IndexedAxialGrid): Uint8Array { + const cuts = Object.freeze([ + Object.freeze([-27, 5] as const), + Object.freeze([-23, 16] as const), + Object.freeze([-10, 25] as const), + Object.freeze([5, 15] as const), + Object.freeze([21, -4] as const), + Object.freeze([8, -20] as const), + ]); + return maskFrom(grid, (q, r) => { + let land = axialDistance(q, r, -8, 2) <= 22 + || axialDistance(q, r, 15, -8) <= 14 + || axialDistance(q, r, -4, 20) <= 12; + if (axialDistance(q, r, 22, 4) <= 8) land = false; + for (const [cutQ, cutR] of cuts) { + if (axialDistance(q, r, cutQ, cutR) <= 2) land = false; + } + if (q >= 19 && q <= 34 && r >= -15 && r <= -12) land = true; + return land; + }); +} + +function productionScaleIrregularContinent(grid: IndexedAxialGrid): Uint8Array { + const cuts = Object.freeze([ + Object.freeze([-108, 20] as const), + Object.freeze([-92, 64] as const), + Object.freeze([-40, 100] as const), + Object.freeze([20, 60] as const), + Object.freeze([84, -16] as const), + Object.freeze([32, -80] as const), + ]); + return maskFrom(grid, (q, r) => { + let land = axialDistance(q, r, -32, 8) <= 88 + || axialDistance(q, r, 60, -32) <= 56 + || axialDistance(q, r, -16, 80) <= 48; + if (axialDistance(q, r, 88, 16) <= 32) land = false; + for (const [cutQ, cutR] of cuts) { + if (axialDistance(q, r, cutQ, cutR) <= 8) land = false; + } + if (q >= 76 && q <= 136 && r >= -60 && r <= -48) land = true; + return land; + }); +} + +describe('Greater Realm pure composition metrics', () => { + it('reserves the exact aggregate proof names for later generator integration', () => { + expect(GREATER_REALM_COMPOSITION_PROOF_KEYS).toEqual([ + 'naturalLandSilhouette', + 'dominantContinentComposition', + 'deepOceanBreathingRoom', + 'forestPatchComposition', + 'mountainSystemComposition', + ]); + }); + + it('derives the continental footprint from elevation rather than surface-water overlays', () => { + expect(createGreaterRealmTopographicLandMask(Int32Array.from([ + -1, 0, 1, 20, -20, + ]))).toEqual(Uint8Array.from([0, 0, 1, 1, 0])); + expect(createGreaterRealmTopographicLandMask(Int32Array.from([ + 4, 5, 6, + ]), 5)).toEqual(Uint8Array.from([0, 0, 1])); + }); + + it('rejects geometric and noisy coasts while accepting an irregular multiscale silhouette', () => { + const grid = indexGreaterRealmAxialGrid(axialDisc(50)); + const regularHex = maskFrom(grid, (q, r) => axialDistance(q, r) <= 25); + const smoothEllipse = maskFrom(grid, (q, r) => { + const x = q * 2 + r; + const y = r * 3; + return x * x * 60 * 60 + y * y * 30 * 30 <= 30 * 30 * 60 * 60; + }); + const noisy = maskFrom(grid, (q, r) => { + const distance = axialDistance(q, r); + const hash = ((q * 17 + r * 31) % 3 + 3) % 3; + return distance <= 25 && !(distance >= 22 && hash === 0); + }); + const natural = irregularContinent(grid); + + const hexMetrics = measureGreaterRealmLandSilhouette({ + grid, + landMask: regularHex, + canvasRadius: 50, + }); + const ellipseMetrics = measureGreaterRealmLandSilhouette({ + grid, + landMask: smoothEllipse, + canvasRadius: 50, + }); + const noisyMetrics = measureGreaterRealmLandSilhouette({ + grid, + landMask: noisy, + canvasRadius: 50, + }); + const naturalMetrics = measureGreaterRealmLandSilhouette({ + grid, + landMask: natural, + canvasRadius: 50, + }); + + expect(hexMetrics.proof).toBe(false); + expect(hexMetrics.dominantLandSolidityBasisPoints).toBe(10_000); + expect(ellipseMetrics.proof).toBe(false); + expect(ellipseMetrics.dominantLandSolidityBasisPoints).toBe(10_000); + expect(noisyMetrics.proof).toBe(false); + expect(noisyMetrics.coastDetailGainBasisPoints) + .toBeGreaterThan(GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS + .maximumCoastDetailGainBasisPoints); + expect(naturalMetrics.proof).toBe(true); + expect(naturalMetrics.dominantLandSolidityBasisPoints).toBeGreaterThanOrEqual( + GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS.minimumDominantLandSolidityBasisPoints, + ); + expect(naturalMetrics.dominantLandSolidityBasisPoints).toBeLessThanOrEqual( + GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS.maximumDominantLandSolidityBasisPoints, + ); + }); + + it('keeps the silhouette verdict stable under axial rotation and bounded translation', () => { + const grid = indexGreaterRealmAxialGrid(axialDisc(50)); + const natural = irregularContinent(grid); + const rotated = transformMask(grid, natural, rotateAxial); + const translated = transformMask(grid, natural, coordinate => Object.freeze({ + q: coordinate.q + 2, + r: coordinate.r - 1, + })); + const verdicts = [natural, rotated, translated].map(landMask => ( + measureGreaterRealmLandSilhouette({ + grid, + landMask, + canvasRadius: 50, + }).proof + )); + expect(verdicts).toEqual([true, true, true]); + }); + + it('calibrates the silhouette proof on a 100k-plus production-scale grid', () => { + const grid = indexGreaterRealmAxialGrid(axialDisc(210)); + expect(grid.cellCount).toBeGreaterThanOrEqual(100_000); + expect(grid.cellCount).toBeLessThanOrEqual(150_000); + const regularHex = maskFrom(grid, (q, r) => axialDistance(q, r) <= 100); + const natural = productionScaleIrregularContinent(grid); + const regularMetrics = measureGreaterRealmLandSilhouette({ + grid, + landMask: regularHex, + canvasRadius: 270, + }); + const naturalMetrics = measureGreaterRealmLandSilhouette({ + grid, + landMask: natural, + canvasRadius: 270, + }); + expect(regularMetrics.proof).toBe(false); + expect(naturalMetrics.proof).toBe(true); + }); + + it('requires one materially dominant continent carrying Tier II and Tier III land', () => { + const grid = indexGreaterRealmAxialGrid(axialDisc(45)); + const dominant = (q: number, r: number) => axialDistance(q, r, -18, 2) <= 12; + const secondary = (q: number, r: number) => axialDistance(q, r, 18, -4) <= 7; + const island = (q: number, r: number) => axialDistance(q, r, 2, 30) <= 3; + const landMask = maskFrom(grid, (q, r) => dominant(q, r) || secondary(q, r) || island(q, r)); + const tierId = new Uint8Array(grid.cellCount); + tierId.fill(1); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (!dominant(grid.q[cell]!, grid.r[cell]!)) continue; + tierId[cell] = grid.q[cell]! % 2 === 0 ? 2 : 3; + } + const valid = measureGreaterRealmDominantContinent({ grid, landMask, tierId }); + expect(valid.proof).toBe(true); + expect(valid.componentSizesDescending).toEqual([469, 169, 37]); + + const peerLandmasses = maskFrom(grid, (q, r) => ( + axialDistance(q, r, -16, 2) <= 10 + || axialDistance(q, r, 16, -2) <= 9 + || island(q, r) + )); + const peerTierId = new Uint8Array(grid.cellCount); + peerTierId.fill(2); + expect(measureGreaterRealmDominantContinent({ + grid, + landMask: peerLandmasses, + tierId: peerTierId, + }).proof).toBe(false); + + const offshoreTiers = new Uint8Array(tierId); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (secondary(grid.q[cell]!, grid.r[cell]!)) offshoreTiers[cell] = 3; + else if (dominant(grid.q[cell]!, grid.r[cell]!)) offshoreTiers[cell] = 1; + } + expect(measureGreaterRealmDominantContinent({ + grid, + landMask, + tierId: offshoreTiers, + }).proof).toBe(false); + }); + + it('proves global and directional deep-ocean breathing room', () => { + const broadGrid = indexGreaterRealmAxialGrid(axialDisc(40)); + const broadLand = maskFrom(broadGrid, (q, r) => axialDistance(q, r) <= 15); + const broadSaltwater = Uint8Array.from(broadLand, value => value === 1 ? 0 : 1); + const broad = measureGreaterRealmOceanBreathingRoom({ + grid: broadGrid, + landMask: broadLand, + saltwaterMask: broadSaltwater, + }); + expect(broad.proof).toBe(true); + expect(broad.minimumBoundaryLandDistance).toBe(25); + expect(new Set(broad.sectorMedianLandDistances)).toEqual(new Set([25])); + + const crampedCoordinates = axialDisc(40).filter(({ q, r }) => { + const eastWedge = q > 0 && Math.abs(r * 2 + q) * 2 <= q; + return !eastWedge || axialDistance(q, r) <= 30; + }); + const crampedGrid = indexGreaterRealmAxialGrid(crampedCoordinates); + const crampedLand = maskFrom(crampedGrid, (q, r) => axialDistance(q, r) <= 15); + const crampedSaltwater = Uint8Array.from(crampedLand, value => value === 1 ? 0 : 1); + const cramped = measureGreaterRealmOceanBreathingRoom({ + grid: crampedGrid, + landMask: crampedLand, + saltwaterMask: crampedSaltwater, + }); + expect(cramped.boundaryLandDistanceP50).toBe(25); + expect(Math.min(...cramped.sectorMedianLandDistances)).toBe(15); + expect(cramped.proof).toBe(false); + }); + + it('distinguishes clustered forests from equal-area speckles and blanket cover', () => { + const grid = indexGreaterRealmAxialGrid(axialDisc(50)); + const eligibleMask = new Uint8Array(grid.cellCount); + eligibleMask.fill(1); + const clustered = maskFrom(grid, (q, r) => ( + axialDistance(q, r, -25, 5) <= 9 + || axialDistance(q, r, 20, -20) <= 9 + || axialDistance(q, r, 10, 20) <= 9 + )); + const clusteredMetrics = measureGreaterRealmPatchComposition({ + grid, + eligibleMask, + patchMask: clustered, + thresholds: GREATER_REALM_FOREST_PATCH_THRESHOLDS, + }); + expect(clusteredMetrics.proof).toBe(true); + expect(clusteredMetrics.componentSizesDescending).toEqual([271, 271, 271]); + + const scattered = new Uint8Array(grid.cellCount); + let remaining = clusteredMetrics.patchCellCount; + for (let cell = 0; cell < grid.cellCount && remaining > 0; cell += 1) { + const residue = ((grid.q[cell]! - grid.r[cell]!) % 3 + 3) % 3; + if (residue !== 0) continue; + scattered[cell] = 1; + remaining -= 1; + } + const scatteredMetrics = measureGreaterRealmPatchComposition({ + grid, + eligibleMask, + patchMask: scattered, + thresholds: GREATER_REALM_FOREST_PATCH_THRESHOLDS, + }); + expect(scatteredMetrics.patchCellCount).toBe(clusteredMetrics.patchCellCount); + expect(scatteredMetrics.proof).toBe(false); + expect(scatteredMetrics.tinyShareBasisPoints).toBe(10_000); + + expect(measureGreaterRealmPatchComposition({ + grid, + eligibleMask, + patchMask: eligibleMask, + thresholds: GREATER_REALM_FOREST_PATCH_THRESHOLDS, + }).proof).toBe(false); + }); + + it('treats only reviewed forest-family biomes as forest', () => { + const grid = indexGreaterRealmAxialGrid(axialDisc(2)); + const waterRegime = new Uint8Array(grid.cellCount); + const biomeId = new Uint8Array(grid.cellCount); + const landformId = new Uint8Array(grid.cellCount); + const protectedCell = new Uint8Array(grid.cellCount); + const forestBiomes = [2, 5, 3, 4, 4]; + for (let cell = 0; cell < forestBiomes.length; cell += 1) { + biomeId[cell] = forestBiomes[cell]!; + } + protectedCell[3] = 1; + waterRegime[2] = 1; + landformId[1] = 15; + const forest = createGreaterRealmForestMask({ + waterRegime, + biomeId, + landformId, + legacyProtectedCell: protectedCell, + }); + expect(forest.slice(0, 5)).toEqual(Uint8Array.from([1, 0, 0, 0, 0])); + }); + + it('requires clustered, off-centre mountain belts rather than a centered ring', () => { + const grid = indexGreaterRealmAxialGrid(axialDisc(50)); + const landMask = new Uint8Array(grid.cellCount); + landMask.fill(1); + const belts = maskFrom(grid, (q, r) => ( + (q >= -36 && q <= -10 && r >= -12 && r <= -6) + || (q >= 9 && q <= 35 && r >= -25 && r <= -19) + || (q >= 3 && q <= 29 && r >= 16 && r <= 22) + )); + const beltMetrics = measureGreaterRealmMountainSystemComposition({ + grid, + landMask, + mountainMask: belts, + }); + expect(beltMetrics.proof).toBe(true); + expect(beltMetrics.offCentreBeltCount).toBe(3); + + const centeredRing = maskFrom(grid, (q, r) => { + const distance = axialDistance(q, r); + return distance >= 15 && distance <= 17; + }); + const ringMetrics = measureGreaterRealmMountainSystemComposition({ + grid, + landMask, + mountainMask: centeredRing, + }); + expect(ringMetrics.proof).toBe(false); + expect(ringMetrics.offCentreBeltCount).toBe(0); + }); + + it('builds mountain mass from ridge and visual authority plus bounded shoulders', () => { + const grid = indexGreaterRealmAxialGrid(axialDisc(2)); + const waterRegime = new Uint8Array(grid.cellCount); + const ridgeId = new Int32Array(grid.cellCount); + const biomeId = new Uint8Array(grid.cellCount); + const landformId = new Uint8Array(grid.cellCount); + const elevation = new Int32Array(grid.cellCount); + const slope = new Uint16Array(grid.cellCount); + const core = grid.indexOf({ q: 0, r: 0 }); + const shoulder = grid.indexOf({ q: 1, r: 0 }); + const lowShoulder = grid.indexOf({ q: 0, r: 1 }); + const isolatedHighland = grid.indexOf({ q: -2, r: 0 }); + ridgeId[core] = 1; + biomeId[isolatedHighland] = 19; + landformId[isolatedHighland] = 5; + for (const cell of [shoulder, lowShoulder, isolatedHighland]) { + elevation[cell] = 6_000; + slope[cell] = 700; + } + slope[lowShoulder] = 599; + const mountain = createGreaterRealmMountainMask({ + grid, + waterRegime, + ridgeId, + biomeId, + landformId, + elevation, + slope, + }); + expect(mountain[core]).toBe(1); + expect(mountain[shoulder]).toBe(1); + expect(mountain[lowShoulder]).toBe(0); + expect(mountain[isolatedHighland]).toBe(0); + }); + + it('uses inclusive frozen thresholds and fails one unit beyond each boundary', () => { + const silhouette = { + coastHalfEdgeCount: 100, + maximumAlignedCoastRunCells: 1, + maximumAlignedCoastRunShareBasisPoints: + GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS.maximumAlignedCoastRunShareBasisPoints, + dominantLandConvexHullCapacity: 100, + dominantLandSolidityBasisPoints: + GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS.maximumDominantLandSolidityBasisPoints, + raster64: { + resolution: 64 as const, + landPixels: 10, + perimeterEdges: 10, + rotationalIouBasisPoints: + GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS.maximumRotationalIou64BasisPoints, + }, + raster256: { + resolution: 256 as const, + landPixels: 40, + perimeterEdges: 41, + rotationalIouBasisPoints: + GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS.maximumRotationalIou256BasisPoints, + }, + coastDetailGainBasisPoints: + GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS.minimumCoastDetailGainBasisPoints, + }; + expect(isGreaterRealmNaturalLandSilhouette(silhouette)).toBe(true); + expect(isGreaterRealmNaturalLandSilhouette({ + ...silhouette, + maximumAlignedCoastRunShareBasisPoints: + GREATER_REALM_LAND_SILHOUETTE_THRESHOLDS.maximumAlignedCoastRunShareBasisPoints + 1, + })).toBe(false); + + const dominant = { + landCellCount: 100, + landmassCount: 2, + dominantLandmassCells: 55, + secondLandmassCells: 39, + dominantLandShareBasisPoints: + GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS.minimumDominantLandShareBasisPoints, + dominantToSecondRatioBasisPoints: + GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS.minimumDominantToSecondRatioBasisPoints, + tierTwoOnDominantBasisPoints: + GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS.minimumTierTwoOnDominantBasisPoints, + tierThreeOnDominantBasisPoints: + GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS.minimumTierThreeOnDominantBasisPoints, + }; + expect(isGreaterRealmDominantContinentComposition(dominant)).toBe(true); + expect(isGreaterRealmDominantContinentComposition({ + ...dominant, + tierThreeOnDominantBasisPoints: + GREATER_REALM_DOMINANT_CONTINENT_THRESHOLDS.minimumTierThreeOnDominantBasisPoints - 1, + })).toBe(false); + + const ocean = { + boundaryCellCount: 12, + saltwaterBoundaryBasisPoints: 10_000, + minimumBoundaryLandDistance: + GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS.minimumBoundaryLandDistance, + boundaryLandDistanceP05: + GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS.minimumBoundaryLandDistanceP05, + boundaryLandDistanceP50: + GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS.minimumBoundaryLandDistanceP50, + boundaryLandDistanceP95: 24, + boundaryAtTargetShareBasisPoints: + GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS + .minimumBoundaryAtTargetShareBasisPoints, + sectorBoundaryCellCounts: Object.freeze(Array(12).fill(1)), + sectorMedianLandDistances: Object.freeze(Array(12).fill( + GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS.minimumSectorMedianLandDistance, + )), + }; + expect(isGreaterRealmDeepOceanBreathingRoom(ocean)).toBe(true); + expect(isGreaterRealmDeepOceanBreathingRoom({ + ...ocean, + sectorMedianLandDistances: Object.freeze([ + ...ocean.sectorMedianLandDistances.slice(0, 11), + GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS.minimumSectorMedianLandDistance - 1, + ]), + })).toBe(false); + + const forestPatch = { + eligibleCellCount: 1_000, + patchCellCount: 100, + patchShareBasisPoints: GREATER_REALM_FOREST_PATCH_THRESHOLDS.minimumShareBasisPoints, + componentCount: 3, + broadComponentCount: GREATER_REALM_FOREST_PATCH_THRESHOLDS.minimumBroadComponentCount, + clusteredShareBasisPoints: + GREATER_REALM_FOREST_PATCH_THRESHOLDS.minimumClusteredShareBasisPoints, + tinyShareBasisPoints: GREATER_REALM_FOREST_PATCH_THRESHOLDS.maximumTinyShareBasisPoints, + largestComponentShareBasisPoints: + GREATER_REALM_FOREST_PATCH_THRESHOLDS.maximumLargestComponentShareBasisPoints, + componentSizeP50: 64, + componentSizeP90: 256, + }; + expect(isGreaterRealmPatchComposition( + forestPatch, + GREATER_REALM_FOREST_PATCH_THRESHOLDS, + )).toBe(true); + expect(isGreaterRealmPatchComposition({ + ...forestPatch, + largestComponentShareBasisPoints: + GREATER_REALM_FOREST_PATCH_THRESHOLDS.maximumLargestComponentShareBasisPoints + 1, + }, GREATER_REALM_FOREST_PATCH_THRESHOLDS)).toBe(false); + + expect(GREATER_REALM_MOUNTAIN_PATCH_THRESHOLDS.minimumBroadComponentCount).toBe(2); + }); + + it('fails closed on disconnected ocean distance and malformed threshold overrides', () => { + const disconnected = indexGreaterRealmAxialGrid(Object.freeze([ + Object.freeze({ q: 0, r: 0 }), + Object.freeze({ q: 3, r: 0 }), + ])); + expect(() => measureGreaterRealmOceanBreathingRoom({ + grid: disconnected, + landMask: Uint8Array.from([1, 0]), + saltwaterMask: Uint8Array.from([0, 1]), + })).toThrow('GREATER_REALM_COMPOSITION_GRID_DISCONNECTED'); + + const grid = indexGreaterRealmAxialGrid(axialDisc(4)); + const landMask = maskFrom(grid, (q, r) => axialDistance(q, r) <= 1); + const saltwaterMask = Uint8Array.from(landMask, value => value === 1 ? 0 : 1); + expect(() => measureGreaterRealmOceanBreathingRoom({ + grid, + landMask, + saltwaterMask, + thresholds: { + ...GREATER_REALM_OCEAN_BREATHING_ROOM_THRESHOLDS, + sectorCount: 11, + }, + })).toThrow('GREATER_REALM_COMPOSITION_SECTOR_COUNT_INVALID'); + expect(() => isGreaterRealmPatchComposition({ + eligibleCellCount: 10, + patchCellCount: 1, + patchShareBasisPoints: 1_000, + componentCount: 1, + broadComponentCount: 1, + clusteredShareBasisPoints: 10_000, + tinyShareBasisPoints: 0, + largestComponentShareBasisPoints: 10_000, + componentSizeP50: 1, + componentSizeP90: 1, + }, { + ...GREATER_REALM_FOREST_PATCH_THRESHOLDS, + maximumShareBasisPoints: 10_001, + })).toThrow('GREATER_REALM_COMPOSITION_THRESHOLD_INVALID'); + }); + + it('excludes dry below-sea visual authority instead of aborting composition', () => { + const grid = indexGreaterRealmAxialGrid(axialDisc(3)); + const elevation = new Int32Array(grid.cellCount); + elevation.fill(1); + const belowSea = grid.indexOf({ q: 0, r: 0 }); + elevation[belowSea] = -1; + const tierId = new Uint8Array(grid.cellCount); + tierId.fill(2); + const waterRegime = new Uint8Array(grid.cellCount); + const biomeId = new Uint8Array(grid.cellCount); + biomeId.fill(1); + biomeId[belowSea] = 19; + const landformId = new Uint8Array(grid.cellCount); + landformId.fill(3); + landformId[belowSea] = 6; + const ridgeId = new Int32Array(grid.cellCount); + ridgeId[belowSea] = 1; + const metrics = measureGreaterRealmNaturalComposition({ + grid, + canvasRadius: 3, + elevation, + tierId, + waterRegime, + biomeId, + legacyProtectedCell: new Uint8Array(grid.cellCount), + ridgeId, + landformId, + slope: new Uint16Array(grid.cellCount), + }); + expect(metrics.mountainSystems.patchCellCount).toBe(0); + expect(metrics.mountainSystems.proof).toBe(false); + }); +}); diff --git a/tests/greaterRealmOrdinaryYield.test.ts b/tests/greaterRealmOrdinaryYield.test.ts index ea70bbdf..adb46d4a 100644 --- a/tests/greaterRealmOrdinaryYield.test.ts +++ b/tests/greaterRealmOrdinaryYield.test.ts @@ -14,7 +14,6 @@ function ordinaryRoot(label: string): Uint8Array { describe('Greater Realm ordinary deterministic candidate yield', () => { it('retains a deterministic eligible candidate after generator hardening', () => { const cases = [ - ['greater-realm-ordinary-parent-a', 9], ['greater-realm-yield-regression-b', 0], ] as const; diff --git a/tests/greaterRealmPrivateBoundary.test.ts b/tests/greaterRealmPrivateBoundary.test.ts index 308c3cc8..ae9141c8 100644 --- a/tests/greaterRealmPrivateBoundary.test.ts +++ b/tests/greaterRealmPrivateBoundary.test.ts @@ -57,7 +57,7 @@ function scannerRepository() { } function sanitizedReviewEvidence(): string { - const candidates = Array.from({ length: 8 }, (_, index) => Object.freeze({ + const candidates = Array.from({ length: 1 }, (_, index) => Object.freeze({ candidateHandle: `GR-A-${String.fromCharCode(65 + index)}AAAAAAAAAAAAAAA`, eligible: true, activeCellCount: 120_000, diff --git a/tests/greaterRealmSanitizedReview.test.ts b/tests/greaterRealmSanitizedReview.test.ts index 8a5fd107..fd216953 100644 --- a/tests/greaterRealmSanitizedReview.test.ts +++ b/tests/greaterRealmSanitizedReview.test.ts @@ -276,11 +276,18 @@ describe('Greater Realm sanitized candidate review', () => { ); }); - it('requires at least eight eligible candidates and an eligible exact selection', () => { + it('accepts one eligible candidate and requires an eligible exact selection', () => { const input = source(); + const single = createGreaterRealmSanitizedReview({ + ...input, + candidates: input.candidates.slice(0, 1), + }); + expect(single.candidateCount).toBe(1); + expect(single.selectionStatus).toBe('pending'); + expect(() => createGreaterRealmSanitizedReview({ ...input, - candidates: input.candidates.slice(0, 7), + candidates: [], })).toThrow('GREATER_REALM_SANITIZED_REVIEW_INVALID'); const selectedHandle = input.candidates[0]!.candidateHandle; diff --git a/tests/greaterRealmTopography.test.ts b/tests/greaterRealmTopography.test.ts index 31895430..dbe9533c 100644 --- a/tests/greaterRealmTopography.test.ts +++ b/tests/greaterRealmTopography.test.ts @@ -131,8 +131,11 @@ describe('Greater Realm derived topography', () => { it('smooths visual classification as a compatible pair without moving protected process cells', () => { const coordinates: Array<{ q: number; r: number }> = []; - for (let q = -2; q <= 2; q += 1) { - for (let r = Math.max(-2, -q - 2); r <= Math.min(2, -q + 2); r += 1) { + // Use a production-representative connected patch large enough to survive + // the generated-forest deconfetti pass; this test remains focused on the + // earlier compatible-pair smoothing behavior. + for (let q = -4; q <= 4; q += 1) { + for (let r = Math.max(-4, -q - 4); r <= Math.min(4, -q + 4); r += 1) { coordinates.push({ q, r }); } } From 08fcaae36f3f0842d0efa1f65a01b422508afc58 Mon Sep 17 00:00:00 2001 From: Ael Date: Tue, 4 Aug 2026 21:24:48 +0200 Subject: [PATCH 5/6] fix(atlas): improve Greater Realm candidate yield --- .../design/greater-realm-natural-continent.md | 6 +- .../greater-realm-candidate-generator.ts | 21 ++++- scripts/atlas/greater-realm-geomorphology.ts | 26 ++----- tests/greaterRealmAdvancedInvariants.test.ts | 4 +- tests/greaterRealmCandidateGenerator.test.ts | 4 +- tests/greaterRealmCandidatePackage.test.ts | 12 +-- tests/greaterRealmCliSecurity.test.ts | 76 ++++++++++--------- tests/greaterRealmGeomorphology.test.ts | 23 +++++- tests/greaterRealmOrdinaryYield.test.ts | 2 +- tests/greaterRealmSanitizedReview.test.ts | 19 +++-- 10 files changed, 115 insertions(+), 78 deletions(-) diff --git a/docs/design/greater-realm-natural-continent.md b/docs/design/greater-realm-natural-continent.md index 1281cc81..132ea63e 100644 --- a/docs/design/greater-realm-natural-continent.md +++ b/docs/design/greater-realm-natural-continent.md @@ -4,7 +4,7 @@ Status: candidate-generation design only Atlas: `GENESIS_001_GREATER_REALM` -Generator algorithm: `greater-realm-v2-natural-continent-pr-a.5` +Generator algorithm: `greater-realm-v2-natural-continent-pr-a.6` Terrain-seed namespace: `greater-realm-v2-natural-continent-pr-a.3` Production state: unchanged @@ -250,8 +250,8 @@ hidden sites, maps, screenshots, previews, paths, or reconstructive data. ## Review and future pull requests -Per the owner’s direction, PR A produces one eligible world for review, not an -eight-world comparison batch. Private owner review includes silhouette, +Per the owner’s direction, PR A produces exactly one eligible world for review. +Private owner review includes silhouette, hillshade, biome, hydrology, region-topology/outer-ocean, and mountain/gate views. The region view uses a fixed opaque fog exterior and review-only outer-ocean bands; its watermark explicitly identifies it as a composition diff --git a/scripts/atlas/greater-realm-candidate-generator.ts b/scripts/atlas/greater-realm-candidate-generator.ts index 840c034e..523f4079 100644 --- a/scripts/atlas/greater-realm-candidate-generator.ts +++ b/scripts/atlas/greater-realm-candidate-generator.ts @@ -44,7 +44,7 @@ import { } from './greater-realm-composition'; export const GREATER_REALM_GENERATOR_VERSION = - 'greater-realm-v2-natural-continent-pr-a.5' as const; + 'greater-realm-v2-natural-continent-pr-a.6' as const; // Package/algorithm revisions must not silently reroll root-seed ordinals. // Bump this namespace only for an explicitly approved deterministic world reroll. export const GREATER_REALM_TERRAIN_SEED_NAMESPACE = @@ -4559,7 +4559,6 @@ function reconcileBarrierMeasuredRegionCoherence( || GREATER_REALM_REGION_SPECS[region]!.tier !== GREATER_REALM_REGION_SPECS[sourceRegion]!.tier || availableSwapWater[region]! < transferableComponent.length - || contacts[region] === 0 ) continue; const projectedTotal = topology.passableCounts[region]! + transferableComponent.length; @@ -4571,6 +4570,11 @@ function reconcileBarrierMeasuredRegionCoherence( const projectedShare = projectedTotal === 0 ? 0 : Math.floor((projectedLargest * 10_000) / projectedTotal); + // A water-separated island has no same-tier land contact to inherit, + // but its political ownership can still move to a coherent peer while + // an equal non-passable ownership area moves back. Never cure one + // fragmented realm by pushing the recipient below its own proof floor. + if (projectedShare < largestTarget(region)) continue; if ( projectedShare > bestProjectedShare || ( @@ -5566,6 +5570,19 @@ function barriersAndGates( } } } + // The corridor-isolation pass above can seal an additional shoulder cell + // beside a gate mouth after the ordinary coherence repair has completed. + // Reconnect same-region fragments once more against that final sealed mask. + // The repair keeps every gate endpoint and every cross-tier-adjacent barrier + // cell locked, so it cannot manufacture an unreviewed Crown crossing. + reconnectBarrierSplitRegionComponents( + grid, + regionId, + tierId, + waterRegime, + barrier, + gates, + ); let futureOpenCrossTierEdges = 0; for (let cell = 0; cell < grid.cellCount; cell += 1) { for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { diff --git a/scripts/atlas/greater-realm-geomorphology.ts b/scripts/atlas/greater-realm-geomorphology.ts index 968dfe2d..9ded170d 100644 --- a/scripts/atlas/greater-realm-geomorphology.ts +++ b/scripts/atlas/greater-realm-geomorphology.ts @@ -10,7 +10,7 @@ import { } from './greater-realm-terrain'; export const GREATER_REALM_GEOMORPHOLOGY_VERSION = - 'greater-realm-geomorphology-v1' as const; + 'greater-realm-geomorphology-v2' as const; export const GREATER_REALM_COASTAL_CLASS = Object.freeze({ none: 0, @@ -578,24 +578,12 @@ function volcanicProcess(input: Readonly<{ || input.volcanicPotential[cell]! < 7_000 || input.tectonicUplift[cell]! < 2_500 ) continue; - let localMaximum = true; - for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { - const neighbor = input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; - if ( - neighbor >= 0 - && ( - input.volcanicPotential[neighbor]! > input.volcanicPotential[cell]! - || ( - input.volcanicPotential[neighbor] === input.volcanicPotential[cell] - && neighbor < cell - ) - ) - ) { - localMaximum = false; - break; - } - } - if (!localMaximum) continue; + // Volcanic potential is intentionally constant across each tectonic + // domain. Treating that plateau as a local-maximum field reduced an + // otherwise broad compatible belt to one arbitrary cell-index minimum, + // which could itself sit outside eligible land and yield no anchors. + // Rank every compatible cell here; the bounded greedy spacing pass below + // remains the authority for selecting distinct volcanic centres. candidates.push(Object.freeze({ cell, score: input.volcanicPotential[cell]! * 4 diff --git a/tests/greaterRealmAdvancedInvariants.test.ts b/tests/greaterRealmAdvancedInvariants.test.ts index bc3ef544..d745b83c 100644 --- a/tests/greaterRealmAdvancedInvariants.test.ts +++ b/tests/greaterRealmAdvancedInvariants.test.ts @@ -14,8 +14,8 @@ import { transformLegacyLowlandsToGlobal, } from '../scripts/atlas/greater-realm-legacy-lowlands'; -const PINNED_ROOT_LABEL = 'greater-realm-yield-regression-b'; -const PINNED_ORDINAL = 0; +const PINNED_ROOT_LABEL = 'greater-realm-ordinary-parent-a'; +const PINNED_ORDINAL = 9; const PRIVATE_CANVAS_RADIUS = 270; const HEX_NEIGHBOR_COUNT = 6; const REGION_COUNT = 10; diff --git a/tests/greaterRealmCandidateGenerator.test.ts b/tests/greaterRealmCandidateGenerator.test.ts index 502fc7b4..d3e01938 100644 --- a/tests/greaterRealmCandidateGenerator.test.ts +++ b/tests/greaterRealmCandidateGenerator.test.ts @@ -21,10 +21,10 @@ import { type IntegerTerrainArray, } from '../scripts/atlas/greater-realm-terrain'; -const PINNED_ROOT_LABEL = 'greater-realm-yield-regression-b'; +const PINNED_ROOT_LABEL = 'greater-realm-ordinary-parent-a'; const SAME_FIRST_WORD_BASELINE_ROOT_INDEX = 23_248; const SAME_FIRST_WORD_ROOT_INDEX = 41_769; -const PINNED_ORDINAL = 0; +const PINNED_ORDINAL = 9; const SAME_FIRST_WORD_ORDINAL = 0; const EXPECTED_ACTIVE_CELL_MINIMUM = 100_000; const EXPECTED_ACTIVE_CELL_MAXIMUM = 150_000; diff --git a/tests/greaterRealmCandidatePackage.test.ts b/tests/greaterRealmCandidatePackage.test.ts index 401fd808..1c5fae19 100644 --- a/tests/greaterRealmCandidatePackage.test.ts +++ b/tests/greaterRealmCandidatePackage.test.ts @@ -45,8 +45,8 @@ import { const SOURCE_COMMIT = 'a'.repeat(40); const BATCH_HANDLE = 'GR-B-AAAAAAAAAAAAAAAA'; const CANDIDATE_HANDLE = 'GR-A-AAAAAAAAAAAAAAAA'; -const CANDIDATE_ROOT_LABEL = 'greater-realm-yield-regression-b'; -const CANDIDATE_ORDINAL = 0; +const CANDIDATE_ROOT_LABEL = 'greater-realm-ordinary-parent-a'; +const CANDIDATE_ORDINAL = 9; const PERFORMANCE = Object.freeze({ generationMilliseconds: 1_200, processPeakMemoryMiB: 512, @@ -327,7 +327,7 @@ describe('Greater Realm owner-only candidate package', () => { let derivedSeed: Buffer | undefined; try { expect(GREATER_REALM_GENERATOR_VERSION) - .toBe('greater-realm-v2-natural-continent-pr-a.5'); + .toBe('greater-realm-v2-natural-continent-pr-a.6'); expect(GREATER_REALM_TERRAIN_SEED_NAMESPACE) .toBe('greater-realm-v2-natural-continent-pr-a.3'); expect(GREATER_REALM_GENERATOR_VERSION).not.toBe( @@ -596,7 +596,7 @@ describe('Greater Realm owner-only candidate package', () => { expect(file.path).toMatch(/^node_modules\//u); expect(file.sha256).toMatch(/^[0-9a-f]{64}$/u); } - expect(parsed.geomorphologyVersion).toBe('greater-realm-geomorphology-v1'); + expect(parsed.geomorphologyVersion).toBe('greater-realm-geomorphology-v2'); const throneCell = fixture.candidate.throneAnchor.findIndex(value => value === 1); expect(throneCell).toBeGreaterThanOrEqual(0); expect(parsed.throneAnchor).toEqual({ @@ -633,8 +633,8 @@ describe('Greater Realm owner-only candidate package', () => { const patch = parsed.topographyPatchManifests[index]!; expect(patch.chunkKey).toBe(chunk.chunkKey); expect(chunk.partitionVersion).toBe('axial-bin-15-v1'); - expect(chunk.geomorphologyVersion).toBe('greater-realm-geomorphology-v1'); - expect(patch.geomorphologyVersion).toBe('greater-realm-geomorphology-v1'); + expect(chunk.geomorphologyVersion).toBe('greater-realm-geomorphology-v2'); + expect(patch.geomorphologyVersion).toBe('greater-realm-geomorphology-v2'); expect(patch.topographyPatchId).toBe(chunk.topographyPatchId); expect(patch.manifestDigest).toBe(chunk.topographyPatchDigest); expect(patch.sampleCount).toBe(chunk.cellCount); diff --git a/tests/greaterRealmCliSecurity.test.ts b/tests/greaterRealmCliSecurity.test.ts index 4f049a88..9c2a59f4 100644 --- a/tests/greaterRealmCliSecurity.test.ts +++ b/tests/greaterRealmCliSecurity.test.ts @@ -78,7 +78,7 @@ afterEach(() => { }); describe('Greater Realm atlas CLI security boundary', () => { - it('creates pending unranked owner-review sets without choosing a winner', () => { + it('creates a pending unranked one-world owner review without choosing a winner', () => { const candidate = ( suffix: string, values: readonly [number, number, number, number, number], @@ -100,13 +100,6 @@ describe('Greater Realm atlas CLI security boundary', () => { }); const candidates = [ candidate('B', [9_900, 5_000, 5_000, 5, 2]), - candidate('C', [8_000, 5_000, 5_000, 5, 2]), - candidate('D', [8_000, 5_000, 5_000, 5, 2]), - candidate('E', [8_000, 5_000, 5_000, 5, 2]), - candidate('F', [8_000, 5_000, 5_000, 12, 9]), - candidate('G', [8_100, 5_100, 5_100, 6, 3]), - candidate('H', [7_000, 4_000, 4_000, 4, 1]), - candidate('I', [7_100, 4_100, 4_100, 4, 1]), ]; const privateMetrics = candidates.map((entry, index) => Object.freeze({ candidateHandle: entry.candidateHandle, @@ -114,7 +107,7 @@ describe('Greater Realm atlas CLI security boundary', () => { rotationalSimilarityBasisPoints: 4_000, maximumAlignedBoundaryRun: 24, saltwaterBoundaryBasisPoints: 9_900, - minimumLargestPassableRegionShareBasisPoints: index === 1 ? 9_900 : 8_000, + minimumLargestPassableRegionShareBasisPoints: index === 0 ? 9_900 : 8_000, maximumMinorPassableFragmentShareBasisPoints: 500, maximumPassableBoundaryDensityBasisPoints: 2_000, maximumPassableTendrilShareBasisPoints: 300, @@ -123,12 +116,12 @@ describe('Greater Realm atlas CLI security boundary', () => { measuredMinimumBarrierWidth: 4, measuredMaximumBarrierWidth: 8, chunkCount: 700, - chunkPopulationSpread: index === 2 ? 1 : 100, + chunkPopulationSpread: index === 0 ? 1 : 100, chunkUpperTailSpread: 20, highlandBarrierShareBasisPoints: 8_000, barrierMeanElevationAdvantage: 2_000, barrierMeanUpliftAdvantage: 700, - ridgeUpliftAlignmentBasisPoints: index === 3 ? 9_900 : 7_000, + ridgeUpliftAlignmentBasisPoints: index === 0 ? 9_900 : 7_000, riverValleyAlignmentBasisPoints: 8_000, landformClimateCompatibilityFloorBasisPoints: 9_000, coastalProximityCompatibilityBasisPoints: 9_000, @@ -143,23 +136,13 @@ describe('Greater Realm atlas CLI security boundary', () => { } as unknown as GreaterRealmSanitizedReview; const shortlist = buildGreaterRealmPrivateCandidateShortlist(review, privateMetrics); - const reversed = buildGreaterRealmPrivateCandidateShortlist({ - ...review, - candidates: [...review.candidates].reverse(), - }, [...privateMetrics].reverse()); - - expect(shortlist).toEqual(reversed); - expect(shortlist.shortlistCount).toBeGreaterThanOrEqual(3); - expect(shortlist.shortlistCount).toBeLessThanOrEqual(5); - expect(shortlist.candidateHandles).toHaveLength(shortlist.shortlistCount); - expect(shortlist.candidateHandles).toEqual( - candidates.slice(0, 5).map(entry => entry.candidateHandle).sort(), - ); + expect(shortlist.shortlistCount).toBe(1); + expect(shortlist.candidateHandles).toEqual([candidates[0]!.candidateHandle]); expect(shortlist.selectionStatus).toBe('pending'); expect(shortlist.selectedCandidateHandle).toBeNull(); expect(shortlist.ranked).toBe(false); expect(shortlist.automaticSelection).toBe(false); - expect(shortlist.method).toBe('pareto-private-vector-diversity-v2'); + expect(shortlist.method).toBe('single-candidate-reference-review-v1'); expect(shortlist.comparisonBasis).toBe('verified-private-package-aggregate-metrics-v1'); expect(shortlist.objectiveDirections).toEqual(expect.arrayContaining([ 'minimize:OUTER_BOUNDARY_ROTATIONAL_ARTIFACT', @@ -178,17 +161,42 @@ describe('Greater Realm atlas CLI security boundary', () => { expect(JSON.stringify(shortlist)).not.toMatch(/(?:winner|recommend|score|rank":\s*[0-9])/iu); expect(JSON.stringify(shortlist)).not.toMatch(/(?:coordinate|seed|transform|chunkKey)/iu); - const single = buildGreaterRealmPrivateCandidateShortlist({ + const multiCandidates = Object.freeze([ + ...candidates, + candidate('C', [8_000, 5_000, 5_000, 5, 2]), + candidate('D', [8_000, 5_000, 5_000, 5, 2]), + candidate('E', [8_000, 5_000, 5_000, 5, 2]), + candidate('F', [8_000, 5_000, 5_000, 12, 9]), + candidate('G', [8_100, 5_100, 5_100, 6, 3]), + candidate('H', [7_000, 4_000, 4_000, 4, 1]), + candidate('I', [7_100, 4_100, 4_100, 4, 1]), + ]); + const multiPrivateMetrics = Object.freeze(multiCandidates.map((entry, index) => Object.freeze({ + ...privateMetrics[0]!, + candidateHandle: entry.candidateHandle, + minimumLargestPassableRegionShareBasisPoints: index === 1 ? 9_900 : 8_000, + chunkPopulationSpread: index === 2 ? 1 : 100, + ridgeUpliftAlignmentBasisPoints: index === 3 ? 9_900 : 7_000, + }) satisfies GreaterRealmVerifiedPrivateShortlistMetrics)); + const multiReview = { ...review, - candidates: review.candidates.slice(0, 1), - }, privateMetrics.slice(0, 1)); - expect(single.shortlistCount).toBe(1); - expect(single.candidateHandles).toEqual([candidates[0]!.candidateHandle]); - expect(single.method).toBe('single-candidate-reference-review-v1'); - expect(single.selectionStatus).toBe('pending'); - expect(single.selectedCandidateHandle).toBeNull(); - expect(single.ranked).toBe(false); - expect(single.automaticSelection).toBe(false); + candidates: multiCandidates, + } as unknown as GreaterRealmSanitizedReview; + const multi = buildGreaterRealmPrivateCandidateShortlist( + multiReview, + multiPrivateMetrics, + ); + const reversed = buildGreaterRealmPrivateCandidateShortlist({ + ...multiReview, + candidates: [...multiReview.candidates].reverse(), + }, [...multiPrivateMetrics].reverse()); + expect(multi).toEqual(reversed); + expect(multi.shortlistCount).toBeGreaterThanOrEqual(3); + expect(multi.shortlistCount).toBeLessThanOrEqual(5); + expect(multi.candidateHandles).toEqual( + multiCandidates.slice(0, 5).map(entry => entry.candidateHandle).sort(), + ); + expect(multi.method).toBe('pareto-private-vector-diversity-v2'); expect(() => buildGreaterRealmPrivateCandidateShortlist({ ...review, diff --git a/tests/greaterRealmGeomorphology.test.ts b/tests/greaterRealmGeomorphology.test.ts index 71cceb9a..d11cfd7c 100644 --- a/tests/greaterRealmGeomorphology.test.ts +++ b/tests/greaterRealmGeomorphology.test.ts @@ -77,7 +77,7 @@ describe('Greater Realm geomorphic shaping', () => { const first = shapeGreaterRealmGeomorphology(input); const second = shapeGreaterRealmGeomorphology(input); - expect(GREATER_REALM_GEOMORPHOLOGY_VERSION).toBe('greater-realm-geomorphology-v1'); + expect(GREATER_REALM_GEOMORPHOLOGY_VERSION).toBe('greater-realm-geomorphology-v2'); expect(first).toEqual(second); expect(fixture.elevation).toEqual(originalElevation); expect(first.metrics.changedCellCount).toBeGreaterThan(0); @@ -142,6 +142,27 @@ describe('Greater Realm geomorphic shaping', () => { expect(result.metrics.arid.systemCount).toBe(0); }); + it('selects separated volcanic anchors from a domain-wide potential plateau', () => { + const fixture = syntheticFixture(); + fixture.volcanicPotential.fill(8_500); + fixture.tectonicUplift.fill(6_000); + const result = shapeGreaterRealmGeomorphology({ + ...fixture, + candidateSeed: new Uint32Array([0x0bad_f00d, 0x1020_3040, 0x5060_7080, 0x90a0_b0c0]), + }); + const anchors = [...result.volcanicAnchorMask] + .map((value, cell) => value === 1 ? cell : -1) + .filter(cell => cell >= 0); + + expect(result.metrics.volcanicAnchorCount).toBe(2); + expect(anchors).toHaveLength(2); + expect(result.metrics.volcanicTectonicCompatibilityBasisPoints).toBe(10_000); + expect(greaterRealmHexDistance( + { q: fixture.grid.q[anchors[0]!]!, r: fixture.grid.r[anchors[0]!]! }, + { q: fixture.grid.q[anchors[1]!]!, r: fixture.grid.r[anchors[1]!]! }, + )).toBeGreaterThanOrEqual(14); + }); + it('derives its production climate from independent named integer fields', () => { const { climate: _fixtureClimate, ...fixture } = syntheticFixture(); const input = { diff --git a/tests/greaterRealmOrdinaryYield.test.ts b/tests/greaterRealmOrdinaryYield.test.ts index adb46d4a..b6ce0451 100644 --- a/tests/greaterRealmOrdinaryYield.test.ts +++ b/tests/greaterRealmOrdinaryYield.test.ts @@ -14,7 +14,7 @@ function ordinaryRoot(label: string): Uint8Array { describe('Greater Realm ordinary deterministic candidate yield', () => { it('retains a deterministic eligible candidate after generator hardening', () => { const cases = [ - ['greater-realm-yield-regression-b', 0], + ['greater-realm-ordinary-parent-a', 9], ] as const; for (const [label, candidateOrdinal] of cases) { diff --git a/tests/greaterRealmSanitizedReview.test.ts b/tests/greaterRealmSanitizedReview.test.ts index fd216953..8cd5ed49 100644 --- a/tests/greaterRealmSanitizedReview.test.ts +++ b/tests/greaterRealmSanitizedReview.test.ts @@ -89,7 +89,7 @@ function source(): GreaterRealmSanitizedReviewSource { reviewBatchHandle: createGreaterRealmReviewBatchHandle(), selectionStatus: 'pending', selectedCandidateHandle: null, - candidates: Object.freeze(Array.from({ length: 8 }, () => candidate())), + candidates: Object.freeze([candidate()]), }); } @@ -102,8 +102,8 @@ describe('Greater Realm sanitized candidate review', () => { const review = createGreaterRealmSanitizedReview(source()); expect(review.reviewBatchHandle).toMatch(GREATER_REALM_REVIEW_BATCH_HANDLE_PATTERN); - expect(review.candidates).toHaveLength(8); - expect(new Set(review.candidates.map(entry => entry.candidateHandle)).size).toBe(8); + expect(review.candidates).toHaveLength(1); + expect(new Set(review.candidates.map(entry => entry.candidateHandle)).size).toBe(1); expect(review.candidates.every(entry => ( GREATER_REALM_CANDIDATE_HANDLE_PATTERN.test(entry.candidateHandle) && entry.insideApprovedRange @@ -121,7 +121,13 @@ describe('Greater Realm sanitized candidate review', () => { }); it('sorts candidates canonically and binds every public field into the digest', () => { - const input = source(); + const input = { + ...source(), + candidates: Object.freeze([ + candidate('GR-A-AAAAAAAAAAAAAAAA'), + candidate('GR-A-AAAAAAAAAAAAAAAB'), + ]), + }; const reversed = { ...input, candidates: [...input.candidates].reverse() }; const review = createGreaterRealmSanitizedReview(reversed); const { reportDigest: _digest, ...body } = review; @@ -278,10 +284,7 @@ describe('Greater Realm sanitized candidate review', () => { it('accepts one eligible candidate and requires an eligible exact selection', () => { const input = source(); - const single = createGreaterRealmSanitizedReview({ - ...input, - candidates: input.candidates.slice(0, 1), - }); + const single = createGreaterRealmSanitizedReview(input); expect(single.candidateCount).toBe(1); expect(single.selectionStatus).toBe('pending'); From 7b5197da3d2a710fcd196d3a9f4fff1541317962 Mon Sep 17 00:00:00 2001 From: Ael Date: Wed, 5 Aug 2026 15:25:22 +0200 Subject: [PATCH 6/6] feat(atlas): add terraced Greater Realm atmosphere --- .../design/greater-realm-natural-continent.md | 59 +- .../greater-realm-private-generation.md | 21 +- scripts/atlas/greater-realm-atmosphere.ts | 106 + .../greater-realm-candidate-generator.ts | 1693 +++++++++++++++- .../atlas/greater-realm-candidate-package.ts | 415 +++- scripts/atlas/greater-realm-geomorphology.ts | 1784 +++++++++++------ scripts/atlas/greater-realm-terraces.ts | 504 +++++ scripts/atlas/greater-realm-terrain.ts | 407 ++-- tests/greaterRealmAtmosphere.test.ts | 130 ++ tests/greaterRealmCandidateGenerator.test.ts | 86 +- tests/greaterRealmCandidatePackage.test.ts | 125 +- tests/greaterRealmGeomorphology.test.ts | 258 ++- tests/greaterRealmTerraces.test.ts | 206 ++ tests/greaterRealmTerrain.test.ts | 29 + ...eaterRealmTierTwoCapacityAuthority.test.ts | 78 + 15 files changed, 4853 insertions(+), 1048 deletions(-) create mode 100644 scripts/atlas/greater-realm-atmosphere.ts create mode 100644 scripts/atlas/greater-realm-terraces.ts create mode 100644 tests/greaterRealmAtmosphere.test.ts create mode 100644 tests/greaterRealmTerraces.test.ts create mode 100644 tests/greaterRealmTierTwoCapacityAuthority.test.ts diff --git a/docs/design/greater-realm-natural-continent.md b/docs/design/greater-realm-natural-continent.md index 132ea63e..8641c398 100644 --- a/docs/design/greater-realm-natural-continent.md +++ b/docs/design/greater-realm-natural-continent.md @@ -4,7 +4,7 @@ Status: candidate-generation design only Atlas: `GENESIS_001_GREATER_REALM` -Generator algorithm: `greater-realm-v2-natural-continent-pr-a.6` +Generator algorithm: `greater-realm-v2-natural-continent-pr-a.8` Terrain-seed namespace: `greater-realm-v2-natural-continent-pr-a.3` Production state: unchanged @@ -37,11 +37,11 @@ before any atlas record can be activated. An eligible candidate has 100,000–150,000 active atlas cells and exactly ten strategic regions: -| Tier | Count | Working regions | Aggregate target | -| --- | ---: | --- | ---: | -| I | 6 | The Hegemony Lowlands, Frostmere Reach, Sunscar Expanse, Mirefen Delta, Stonewake Isles, Emberwood March | 68%–74% | -| II | 3 | Crownwood March, Ironveil March, Glasswater March | 22%–27% | -| III | 1 | Throneheart | 3%–6% | +| Tier | Count | Working regions | Aggregate target | +| ---- | ----: | -------------------------------------------------------------------------------------------------------- | ---------------: | +| I | 6 | The Hegemony Lowlands, Frostmere Reach, Sunscar Expanse, Mirefen Delta, Stonewake Isles, Emberwood March | 68%–74% | +| II | 3 | Crownwood March, Ironveil March, Glasswater March | 22%–27% | +| III | 1 | Throneheart | 3%–6% | Tier III must be the smallest region by total cells and passable land. Each Tier II region has two Tier I neighbours and one Tier III neighbour. The @@ -79,11 +79,15 @@ Each candidate runs independently through these stage families: 4. Derive a connected active atlas mask from geography plus a variable deep-ocean/fog buffer. Inactive canvas cells are discarded, not seeded. 5. Place the locked Lowlands reserve, then run a real geomorphology stage before - the final fluvial pass. That stage uses preliminary drainage and climate to + the final fluvial pass. A low-frequency carrier first shapes broad terraces + with short smooth ramps; independently named meso/detail bands restore + bounded weathering so the result keeps natural relief instead of reading as + contour stairs. The stage then uses preliminary drainage and climate to apply bounded glacial erosion and moraines, arid wadis and aeolian shaping, volcanic/caldera relief tied to tectonic evidence, and coherent beach, - cliff, delta, and fjord shaping. It preserves the Lowlands reserve exactly - and proves a material budget for its signed process deltas. + cliff, delta, and fjord shaping. Coastline sign and the Lowlands reserve stay + exact. The package records the terrace delta separately and proves the + material budget of the erosion/deposition processes independently. 6. Run stable Priority-Flood depression handling on the six-connected graph, route flats toward legal outlets, prove a drainage DAG, accumulate discharge, and apply bounded stream-power-like incision and sediment @@ -127,7 +131,12 @@ The design adapts practical ideas from research on [procedural tectonic structure](https://onlinelibrary.wiley.com/doi/10.1111/cgf.13614), [coupled uplift and fluvial erosion](https://onlinelibrary.wiley.com/doi/10.1111/cgf.12820), [Priority-Flood drainage](https://doi.org/10.1016/j.cageo.2013.04.024), and -[tile-based erosion evaluation](https://arxiv.org/abs/2210.14496). It does not +[tile-based erosion evaluation](https://arxiv.org/abs/2210.14496), plus the +terrain-material and height-atmosphere demonstrations in +[SimonDev's game-development demos](https://simondev.io/demos/gamedev/#customizing-materials) +and the exponential height-fog treatment documented in the +[Crytek SIGGRAPH 2006 course notes](https://advances.realtimerendering.com/s2006/Course_26_SIGGRAPH_2006.pdf). +It does not copy third-party code, data, maps, art, labels, or balance tables. ## Candidate hard gates @@ -161,10 +170,10 @@ A candidate is ineligible if any of these proofs fail: Lowlands cells are not counted as mountains; - region count, tier ratios, region balance, fixed adjacency, 18 gates, or 600 total castle capacity differs from the contract; -- a Tier-II parent lacks a dry outer-frontier anchor joined to its dry inner - anchor. The generator first preserves an already-sound natural partition; - only a parent that would miss the Tier-I frontier activates the deterministic - dual-anchor spine repair; +- a Tier-II parent lacks one connected strategic mainland spine between dry + outer- and inner-frontier anchors. Dry ground plus explicitly fordable river + and stream cells may carry the interior route; ocean, lake, and sea cells may + not. Gate endpoints and their independently proved approaches remain dry; - a closed tier barrier has an ungated land bypass; - opening all declared gate endpoints exposes any cross-tier edge other than the exact 18 recorded physical corridors; @@ -197,9 +206,11 @@ A candidate is ineligible if any of these proofs fail: - derived topography is degenerate, a region misses its tier-specific biome diversity floor, a Tier-I biome exceeds 55% of that region's land, or an incompatible hot-arid/frozen visual adjacency survives classification; -- geomorphology changes the Lowlands reserve, violates its exact material - budget or climate/tectonic/coastal compatibility, or omits the required - glacial, arid, volcanic, and coastal process evidence; +- geomorphology changes the Lowlands reserve, lacks both broad plateau and + short-ramp terrace evidence, loses its bounded weathered detail, violates its + exact erosion/deposition material budget or climate/tectonic/coastal + compatibility, or omits the required glacial, arid, volcanic, and coastal + process evidence; - the locked Lowlands patch or any protected catalog differs; - castle candidates lack local passability, spacing, or clearance; - integer range, stage-digest, deterministic replay, package-integrity, or private @@ -207,8 +218,10 @@ A candidate is ineligible if any of these proofs fail: For this offline topology proof, river and minor-stream corridors are fordable route surfaces; oceans and lakes are not. This does not activate movement, -bridges, ferries, or any cross-region mechanic. Castle sites and sealed gate -endpoints themselves must still be dry. +bridges, ferries, or any cross-region mechanic. Every sealed gate endpoint and +every recorded primary/alternate gate-approach cell must remain strictly dry; +fordable water can connect regional interiors but cannot masquerade as a gate +apron. Quality remains a vector, not an automatic decision. After the private package has been regenerated and byte-for-byte verified, the owner-review record joins @@ -250,7 +263,8 @@ hidden sites, maps, screenshots, previews, paths, or reconstructive data. ## Review and future pull requests -Per the owner’s direction, PR A produces exactly one eligible world for review. +Per the owner’s direction, PR A produces exactly one eligible canonical world +for review—not eight separate worlds or parallel variants. Private owner review includes silhouette, hillshade, biome, hydrology, region-topology/outer-ocean, and mountain/gate views. The region view uses a fixed opaque fog exterior and review-only @@ -258,6 +272,11 @@ outer-ocean bands; its watermark explicitly identifies it as a composition proxy, not shipped runtime fog or server fog-of-war authority. The silhouette follows topographic land at sea level, so rivers and streams remain features within the landmass instead of punching false coastline gaps. +The hillshade view applies a presentation-only exponential height atmosphere: +low valleys and long view rays accumulate denser haze, while high peaks remain +clearer. Extinction darkens terrain radiance and in-scattering adds a blurred +sky-color proxy as separate terms. Neither term becomes terrain, visibility, +fog-of-war, persistence, or gameplay authority. Owner-supplied map references are composition guidance, never source assets or pixel targets. Review asks whether the candidate has a strong irregular macro diff --git a/docs/security/greater-realm-private-generation.md b/docs/security/greater-realm-private-generation.md index a661205b..fe2a9705 100644 --- a/docs/security/greater-realm-private-generation.md +++ b/docs/security/greater-realm-private-generation.md @@ -6,14 +6,14 @@ exists. This document applies to the candidate-generation pull request only. ## Assets and trust boundaries -| Data | Classification | Allowed location | -| --- | --- | --- | -| Root/candidate seed bytes | Private authority | Owner-only workspace outside the repository | -| Exact canvas, cells, geology/geomorphology processes, paired topography/biomes, regions, gates, slots, sites, fields, transforms | Private authority | Owner-only workspace outside the repository | -| Chunk/topography-patch manifests, package/layout/stage digests, toolchain records, and inventories | Private operational data | Owner-only workspace outside the repository | -| Candidate maps and contact sheets | Private owner-review data | Owner-only workspace outside the repository | -| Aggregate allowlisted candidate metrics | Public sanitized evidence | `docs/evidence/greater-realm/` after strict validation | -| Generator source and synthetic fixtures | Public source | `scripts/atlas/` and `tests/` | +| Data | Classification | Allowed location | +| -------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------------------ | +| Root/candidate seed bytes | Private authority | Owner-only workspace outside the repository | +| Exact canvas, cells, geology/geomorphology processes, paired topography/biomes, regions, gates, slots, sites, fields, transforms | Private authority | Owner-only workspace outside the repository | +| Chunk/topography-patch manifests, package/layout/stage digests, toolchain records, and inventories | Private operational data | Owner-only workspace outside the repository | +| Candidate maps and contact sheets | Private owner-review data | Owner-only workspace outside the repository | +| Aggregate allowlisted candidate metrics | Public sanitized evidence | `docs/evidence/greater-realm/` after strict validation | +| Generator source and synthetic fixtures | Public source | `scripts/atlas/` and `tests/` | The browser, Vite `public/` tree, production `dist/`, source maps, Git history, pull-request comments, CI artifacts, logs, and public SpacetimeDB tables are not @@ -48,8 +48,9 @@ The private workspace must: field payload, then bind its referenced topography patch to the same cells, generation/topography/partition versions, exact process-and-derived field inventory, payload length, and payload digest; -- include final elevation, each glacial/arid/volcanic/coastal elevation delta, - and the corresponding process masks/classes in that canonical inventory so +- include final elevation, the low-frequency terrace delta, each + glacial/arid/volcanic/coastal elevation delta, and the corresponding process + masks/classes in that canonical inventory so the private physical-process metrics can be independently reproduced; retain raw geomorphology climate fields separately from final derived climate, and retain process-output elevation so process input is exactly output minus the diff --git a/scripts/atlas/greater-realm-atmosphere.ts b/scripts/atlas/greater-realm-atmosphere.ts new file mode 100644 index 00000000..d1d75b59 --- /dev/null +++ b/scripts/atlas/greater-realm-atmosphere.ts @@ -0,0 +1,106 @@ +export const GREATER_REALM_ATMOSPHERE_VERSION = + 'greater-realm-height-atmosphere-v1' as const; + +export type GreaterRealmHeightFogInput = Readonly<{ + /** Camera height above the configured fog datum, in terrain-height units. */ + originHeight: number; + /** Surface height minus camera height, in terrain-height units. */ + rayHeightDelta: number; + /** Length of the camera-to-surface ray in terrain-height units. */ + rayLength: number; + /** Density at the fog datum, in inverse terrain-height units. */ + density: number; + /** Exponential density falloff with height, in inverse terrain-height units. */ + heightFalloff: number; +}>; + +export type GreaterRealmAtmosphereCompositeInput = Readonly<{ + scene: readonly [number, number, number]; + haze: readonly [number, number, number]; + extinction: number; + inScattering: number; +}>; + +function fail(code: string): never { + throw new Error(code); +} + +function saturate(value: number): number { + return Math.max(0, Math.min(1, value)); +} + +/** + * Analytically integrate an exponentially thinning fog layer along a camera + * ray. Keeping this as a presentation-only helper prevents atmosphere from + * becoming terrain, visibility, or gameplay authority. + */ +export function integrateGreaterRealmHeightFog( + input: GreaterRealmHeightFogInput, +): number { + if ( + !Number.isFinite(input.originHeight) || + !Number.isFinite(input.rayHeightDelta) || + !Number.isFinite(input.rayLength) || + !Number.isFinite(input.density) || + !Number.isFinite(input.heightFalloff) || + input.rayLength < 0 || + input.density < 0 || + input.heightFalloff < 0 || + Math.abs(input.rayHeightDelta) > + input.rayLength + Math.max(1e-9, input.rayLength * 1e-12) + ) + fail('GREATER_REALM_HEIGHT_FOG_INPUT_INVALID'); + if (input.rayLength === 0 || input.density === 0) return 0; + + const originDensity = Math.exp( + Math.max(-40, Math.min(40, -input.originHeight * input.heightFalloff)), + ); + const verticalExponent = Math.max( + -40, + Math.min(40, input.heightFalloff * input.rayHeightDelta), + ); + const heightIntegral = + Math.abs(verticalExponent) < 1e-6 + ? 1 + : -Math.expm1(-verticalExponent) / verticalExponent; + const opticalDepth = + input.rayLength * originDensity * heightIntegral * input.density; + const factor = 1 - Math.exp(-Math.max(0, Math.min(80, opticalDepth))); + + // The squared response keeps nearby terrain crisp while allowing distant + // valleys to accumulate a visibly substantial atmosphere layer. + return saturate(factor * factor); +} + +/** + * Composite atmosphere as two independent physical-looking terms: + * extinction removes scene radiance, while in-scattering adds sky radiance. + */ +export function compositeGreaterRealmAtmosphere( + input: GreaterRealmAtmosphereCompositeInput, +): readonly [number, number, number] { + if ( + input.scene.length !== 3 || + input.haze.length !== 3 || + !Number.isFinite(input.extinction) || + !Number.isFinite(input.inScattering) || + input.scene.some((value) => !Number.isFinite(value)) || + input.haze.some((value) => !Number.isFinite(value)) + ) + fail('GREATER_REALM_ATMOSPHERE_COMPOSITE_INVALID'); + const extinction = saturate(input.extinction); + const inScattering = saturate(input.inScattering); + return Object.freeze( + input.scene.map((channel, index) => + Math.round( + Math.max( + 0, + Math.min( + 255, + channel * (1 - extinction) + input.haze[index]! * inScattering, + ), + ), + ), + ) as [number, number, number], + ); +} diff --git a/scripts/atlas/greater-realm-candidate-generator.ts b/scripts/atlas/greater-realm-candidate-generator.ts index 523f4079..95767589 100644 --- a/scripts/atlas/greater-realm-candidate-generator.ts +++ b/scripts/atlas/greater-realm-candidate-generator.ts @@ -44,7 +44,7 @@ import { } from './greater-realm-composition'; export const GREATER_REALM_GENERATOR_VERSION = - 'greater-realm-v2-natural-continent-pr-a.6' as const; + 'greater-realm-v2-natural-continent-pr-a.8' as const; // Package/algorithm revisions must not silently reroll root-seed ordinals. // Bump this namespace only for an explicitly approved deterministic world reroll. export const GREATER_REALM_TERRAIN_SEED_NAMESPACE = @@ -161,6 +161,7 @@ export type GreaterRealmPrivateCandidate = Readonly<{ geomorphologyTemperature: Int32Array; geomorphologyMoisture: Int32Array; geomorphologyTotalDelta: Int32Array; + geomorphologyTerraceDelta: Int32Array; geomorphologyGlacialDelta: Int32Array; geomorphologyAridDelta: Int32Array; geomorphologyVolcanicDelta: Int32Array; @@ -2203,6 +2204,1368 @@ function assignTiersAndRegions( return Object.freeze({ tierId, regionId, tierCounts, regionCounts: Object.freeze(regionCounts) }); } +/** + * Reconcile the provisional middle ring against the authoritative water + * surface before semantic region naming. The provisional partition is built + * before lakes and inland seas exist, so balancing its political envelopes by + * raw cell count can make one Tier-II realm own two large, mutually + * unreachable shores. This allocator gives each middle realm exactly one + * primary passable component, keeps its inner/outer strategic spine connected, + * and uses only non-passable ownership to satisfy the exact cell quotas. + * + * When a water-separated Tier-II component cannot fit a safe political slot, + * its passable cells return to Tier I and an equal number of unprotected Tier-I + * ocean/lake cells enter Tier II. This changes neither terrain nor hydrology, + * keeps total tier counts exact, and fails closed when a deterministic capacity + * allocation cannot be constructed. + */ +function allocateTierTwoPassableCapacity( + grid: IndexedAxialGrid, + candidateSeed: GreaterRealmTerrainSeed, + strategy: Readonly<{ + tierId: Uint8Array; + regionId: Uint8Array; + tierCounts: readonly [number, number, number]; + regionCounts: readonly number[]; + }>, + waterRegime: Uint8Array, + legacyProtectedCell: Uint8Array, + legacyReserveCell: Uint8Array, +): Readonly<{ + tierId: Uint8Array; + regionId: Uint8Array; + tierCounts: readonly [number, number, number]; + regionCounts: readonly number[]; + tierTwoPassableOwner: Int8Array; + tierTwoSpineOwner: Int8Array; +}> { + type OwnedAllocatorArray = Uint8Array | Uint16Array | Uint32Array | Int8Array | Int32Array; + const ownedAllocatorArrays = new Set(); + const own = (array: ArrayType): ArrayType => { + ownedAllocatorArrays.add(array); + return array; + }; + const release = (array: OwnedAllocatorArray): void => { + array.fill(0); + ownedAllocatorArrays.delete(array); + }; + let retainedAuthorityArrays: ReadonlySet = new Set(); + const reject = (_suffix: string): never => rejectGreaterRealmCandidate( + 'GREATER_REALM_TIER_TWO_CAPACITY_INVARIANT', + ); + try { + const tierId = own(new Uint8Array(strategy.tierId)); + const regionId = own(new Uint8Array(strategy.regionId)); + const originalTierId = own(new Uint8Array(strategy.tierId)); + const originalRegionId = own(new Uint8Array(strategy.regionId)); + const tierTwoTotal = strategy.tierCounts[1]; + const targetCounts = Array.from({ length: TIER_II_REGION_COUNT }, (_, parent) => ( + Math.floor(tierTwoTotal / TIER_II_REGION_COUNT) + + (parent < tierTwoTotal % TIER_II_REGION_COUNT ? 1 : 0) + )); + const passableTierTwoMask = own(new Uint8Array(grid.cellCount)); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + tierId[cell] === 2 + && strategicallyPassableSurface(waterRegime[cell]!) + ) passableTierTwoMask[cell] = 1; + } + const passableComponents = [...connectedComponents(grid, passableTierTwoMask)] + .sort((first, second) => second.length - first.length || first[0]! - second[0]!); + if (passableComponents.length === 0) reject('PASSABLE_COMPONENTS_EMPTY'); + + type CapacityComponent = Readonly<{ + id: number; + cells: readonly number[]; + innerBoundary: readonly number[]; + outerBoundary: readonly number[]; + originalParentCounts: readonly number[]; + }>; + const components: CapacityComponent[] = passableComponents.map((cells, id) => { + const innerBoundary: number[] = []; + const outerBoundary: number[] = []; + const originalParentCounts = Array(TIER_II_REGION_COUNT).fill(0); + for (const cell of cells) { + const originalParent = originalRegionId[cell]! - TIER_I_REGION_COUNT; + if (originalParent >= 0 && originalParent < TIER_II_REGION_COUNT) { + originalParentCounts[originalParent] += 1; + } + if (waterRegime[cell] !== WATER_DRY) continue; + let touchesInner = false; + let touchesOuter = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || waterRegime[neighbor] !== WATER_DRY) continue; + if (tierId[neighbor] === 3) touchesInner = true; + else if (tierId[neighbor] === 1) touchesOuter = true; + } + if (touchesInner) innerBoundary.push(cell); + if (touchesOuter) outerBoundary.push(cell); + } + return Object.freeze({ + id, + cells, + innerBoundary: Object.freeze(innerBoundary), + outerBoundary: Object.freeze(outerBoundary), + originalParentCounts: Object.freeze(originalParentCounts), + }); + }); + let tierThreePassableCells = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + tierId[cell] === 3 + && strategicallyPassableSurface(waterRegime[cell]!) + ) tierThreePassableCells += 1; + } + // The public realm proof requires every outer and middle realm to own more + // traversable land than compact Tier III. Reserve that capacity before any + // political ownership is committed; a later water-cell swap cannot create + // real land and must never be allowed to conceal an undersized realm. + const minimumPrimaryCells = tierThreePassableCells + 512; + const eligibleComponents = components.filter(component => ( + component.cells.length >= minimumPrimaryCells + && component.innerBoundary.length > 0 + && component.outerBoundary.length > 0 + )); + if (eligibleComponents.length === 0) reject('STRATEGIC_COMPONENTS_EMPTY'); + + // Gate viability is political capacity, not a post-hoc rendering choice. + // During allocation, stage two distinct dry apron options for every middle + // realm. Each option has two boundary edges and vertex-disjoint local dry + // corridors into a durable interior. These are transactional capacity hints: + // the later semantic repair may rename or replace them, and barriersAndGates + // must independently re-prove the final eighteen dry gate routes. + const componentByCell = own(new Int32Array(grid.cellCount)); + componentByCell.fill(-1); + for (const component of components) { + for (const cell of component.cells) componentByCell[cell] = component.id; + } + const outerTierBoundary = own(new Uint8Array(grid.cellCount)); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor <= cell) continue; + const firstTier = tierId[cell]!; + const secondTier = tierId[neighbor]!; + if ( + (firstTier === 1 && secondTier === 2) + || (firstTier === 2 && secondTier === 1) + ) { + outerTierBoundary[cell] = 1; + outerTierBoundary[neighbor] = 1; + } + } + } + const distanceToOuterTierBoundary = own(new Uint16Array(grid.cellCount)); + distanceToOuterTierBoundary.fill(0xffff); + const outerTierQueue = own(new Uint32Array(grid.cellCount)); + let outerTierHead = 0; + let outerTierTail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (outerTierBoundary[cell] !== 1) continue; + distanceToOuterTierBoundary[cell] = 0; + outerTierQueue[outerTierTail++] = cell; + } + while (outerTierHead < outerTierTail) { + const cell = outerTierQueue[outerTierHead++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || tierId[neighbor] !== tierId[cell] + || distanceToOuterTierBoundary[neighbor] !== 0xffff + ) continue; + distanceToOuterTierBoundary[neighbor] = distanceToOuterTierBoundary[cell]! + 1; + outerTierQueue[outerTierTail++] = neighbor; + } + } + const strictDryWaterRegime = own(new Uint8Array(grid.cellCount)); + const apronRegionId = own(new Uint8Array(grid.cellCount)); + const outerGateApronBarrier = own(new Uint8Array(grid.cellCount)); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + strictDryWaterRegime[cell] = waterRegime[cell] === WATER_DRY + ? WATER_DRY + : WATER_OCEAN; + apronRegionId[cell] = tierId[cell] === 1 + ? originalRegionId[cell]! + : tierId[cell] === 2 ? TIER_I_REGION_COUNT : TIER_III_REGION_INDEX; + if ( + distanceToOuterTierBoundary[cell]! < 2 + && legacyProtectedCell[cell] === 0 + && legacyReserveCell[cell] === 0 + ) outerGateApronBarrier[cell] = 1; + } + const apronRobustTopology = robustRegionTopology( + grid, + apronRegionId, + strictDryWaterRegime, + outerGateApronBarrier, + ); + own(apronRobustTopology.articulation); + own(apronRobustTopology.componentId); + type GateApronCorridor = readonly number[]; + type GateApronEdge = Readonly<{ + child: number; + componentId: number; + tierOneEndpoint: number; + tierTwoEndpoint: number; + tierOneCorridors: readonly [GateApronCorridor, GateApronCorridor]; + tierTwoCorridors: readonly [GateApronCorridor, GateApronCorridor]; + score: number; + }>; + const gateApronEdgesByKey = new Map(); + const apronChannel = greaterRealmTerrainChannelId('tier-two-dry-gate-aprons'); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor <= cell || tierId[cell] === tierId[neighbor]) continue; + const tierOneEndpoint = tierId[cell] === 1 ? cell : neighbor; + const tierTwoEndpoint = tierOneEndpoint === cell ? neighbor : cell; + if ( + tierId[tierOneEndpoint] !== 1 + || tierId[tierTwoEndpoint] !== 2 + || waterRegime[tierOneEndpoint] !== WATER_DRY + || waterRegime[tierTwoEndpoint] !== WATER_DRY + || legacyProtectedCell[tierOneEndpoint] === 1 + || legacyProtectedCell[tierTwoEndpoint] === 1 + || legacyReserveCell[tierOneEndpoint] === 1 + || legacyReserveCell[tierTwoEndpoint] === 1 + ) continue; + const child = originalRegionId[tierOneEndpoint]!; + const componentId = componentByCell[tierTwoEndpoint]!; + if (child >= TIER_I_REGION_COUNT || componentId < 0) continue; + const tierOnePaths = barrierApproachPaths( + grid, + tierOneEndpoint, + child, + apronRegionId, + strictDryWaterRegime, + outerGateApronBarrier, + apronRobustTopology.componentId, + apronRobustTopology.componentSizes, + ); + const tierTwoPaths = barrierApproachPaths( + grid, + tierTwoEndpoint, + TIER_I_REGION_COUNT, + apronRegionId, + strictDryWaterRegime, + outerGateApronBarrier, + apronRobustTopology.componentId, + apronRobustTopology.componentSizes, + ); + if (!tierOnePaths || !tierTwoPaths) continue; + const compatible = compatibleGateApproaches( + grid, + tierId, + strictDryWaterRegime, + outerGateApronBarrier, + apronRobustTopology.componentId, + apronRobustTopology.componentSizes, + tierOnePaths, + tierTwoPaths, + ); + if (!compatible) continue; + const edge = Object.freeze({ + child, + componentId, + tierOneEndpoint, + tierTwoEndpoint, + tierOneCorridors: Object.freeze([ + compatible.first, + compatible.firstAlternate, + ] as const), + tierTwoCorridors: Object.freeze([ + compatible.second, + compatible.secondAlternate, + ] as const), + score: greaterRealmCounterRandomU32( + candidateSeed, + apronChannel, + grid.q[tierTwoEndpoint]!, + grid.r[tierTwoEndpoint]!, + ), + }); + const key = `${componentId}:${child}`; + const edges = gateApronEdgesByKey.get(key) ?? []; + edges.push(edge); + gateApronEdgesByKey.set(key, edges); + } + } + type GateApronBundle = Readonly<{ + child: number; + componentId: number; + edges: readonly [GateApronEdge, GateApronEdge]; + tierOneCells: readonly number[]; + tierTwoCells: readonly number[]; + score: number; + }>; + const gateApronBundlesByKey = new Map(); + for (const [key, rawEdges] of gateApronEdgesByKey) { + const edges = [...rawEdges] + .sort((first, second) => first.score - second.score + || first.tierTwoEndpoint - second.tierTwoEndpoint) + .slice(0, 128); + const bundles: GateApronBundle[] = []; + for (let first = 0; first < edges.length; first += 1) { + for (let second = first + 1; second < edges.length; second += 1) { + const left = edges[first]!; + const right = edges[second]!; + if ( + left.tierOneEndpoint === right.tierOneEndpoint + || left.tierTwoEndpoint === right.tierTwoEndpoint + ) continue; + const separation = axialDistance( + grid.q[left.tierTwoEndpoint]!, + grid.r[left.tierTwoEndpoint]!, + grid.q[right.tierTwoEndpoint]!, + grid.r[right.tierTwoEndpoint]!, + ); + if (separation < 4 || separation > 48) continue; + const tierOneCells = [ + left.tierOneEndpoint, + ...left.tierOneCorridors.flat(), + right.tierOneEndpoint, + ...right.tierOneCorridors.flat(), + ]; + const tierTwoCells = [ + left.tierTwoEndpoint, + ...left.tierTwoCorridors.flat(), + right.tierTwoEndpoint, + ...right.tierTwoCorridors.flat(), + ]; + if ( + left.tierOneCorridors.flat().includes(right.tierOneEndpoint) + || right.tierOneCorridors.flat().includes(left.tierOneEndpoint) + || left.tierTwoCorridors.flat().includes(right.tierTwoEndpoint) + || right.tierTwoCorridors.flat().includes(left.tierTwoEndpoint) + ) continue; + bundles.push(Object.freeze({ + child: left.child, + componentId: left.componentId, + edges: Object.freeze([left, right] as const), + tierOneCells: Object.freeze([...new Set(tierOneCells)]), + tierTwoCells: Object.freeze([...new Set(tierTwoCells)]), + score: left.score + right.score, + })); + if (bundles.length >= 32) break; + } + if (bundles.length >= 32) break; + } + gateApronBundlesByKey.set(key, Object.freeze(bundles)); + } + + type CapacityAssignment = Readonly<{ + componentByParent: readonly number[]; + retainedPassableCells: number; + originalOwnershipAffinity: number; + }>; + let selectedAssignment: CapacityAssignment | undefined; + for (const first of eligibleComponents) { + for (const second of eligibleComponents) { + for (const third of eligibleComponents) { + const componentByParent = [first.id, second.id, third.id] as const; + const parentsByComponent = new Map(); + for (let parent = 0; parent < TIER_II_REGION_COUNT; parent += 1) { + const component = componentByParent[parent]!; + const parents = parentsByComponent.get(component) ?? []; + parents.push(parent); + parentsByComponent.set(component, parents); + } + let retainedPassableCells = 0; + let originalOwnershipAffinity = 0; + let valid = true; + for (const [componentId, parents] of parentsByComponent) { + const component = components[componentId]!; + const capacity = parents.reduce((sum, parent) => sum + targetCounts[parent]!, 0); + if ( + component.cells.length < parents.length * minimumPrimaryCells + || component.innerBoundary.length < parents.length + || component.outerBoundary.length < parents.length + ) { + valid = false; + break; + } + retainedPassableCells += Math.min(component.cells.length, capacity); + for (const parent of parents) { + originalOwnershipAffinity += component.originalParentCounts[parent]!; + } + } + if (!valid) continue; + const candidate = Object.freeze({ + componentByParent: Object.freeze([...componentByParent]), + retainedPassableCells, + originalOwnershipAffinity, + }); + const firstDifference = selectedAssignment + ? candidate.componentByParent.findIndex((component, index) => ( + component !== selectedAssignment!.componentByParent[index] + )) + : 0; + const lexicographicallyEarlier = !selectedAssignment + || ( + firstDifference >= 0 + && candidate.componentByParent[firstDifference]! + < selectedAssignment.componentByParent[firstDifference]! + ); + if ( + !selectedAssignment + || candidate.retainedPassableCells > selectedAssignment.retainedPassableCells + || ( + candidate.retainedPassableCells === selectedAssignment.retainedPassableCells + && candidate.originalOwnershipAffinity > selectedAssignment.originalOwnershipAffinity + ) + || ( + candidate.retainedPassableCells === selectedAssignment.retainedPassableCells + && candidate.originalOwnershipAffinity === selectedAssignment.originalOwnershipAffinity + && lexicographicallyEarlier + ) + ) selectedAssignment = candidate; + } + } + } + if (!selectedAssignment) reject('ASSIGNMENT_MISSING'); + const capacityAssignment = selectedAssignment as CapacityAssignment; + + type GateApronPlan = Readonly<{ + parentByChild: readonly number[]; + bundleByChild: readonly GateApronBundle[]; + tierTwoOwnershipForestByParent: readonly (readonly number[])[]; + }>; + const parentByChild = own(new Int8Array(TIER_I_REGION_COUNT)); + parentByChild.fill(-1); + for (const [child, parentRegion] of GREATER_REALM_PROVISIONAL_GATE_GRAPH) { + if ( + child < TIER_I_REGION_COUNT + && parentRegion >= TIER_I_REGION_COUNT + && parentRegion < TIER_III_REGION_INDEX + ) parentByChild[child] = parentRegion - TIER_I_REGION_COUNT; + } + if ([...parentByChild].some(parent => parent < 0)) reject('GATE_PARENT_SLOT_MISSING'); + const parentOrders = (parents: readonly number[]): readonly (readonly number[])[] => { + if (parents.length <= 1) return Object.freeze([Object.freeze([...parents])]); + if (parents.length === 2) { + return Object.freeze([ + Object.freeze([parents[0]!, parents[1]!]), + Object.freeze([parents[1]!, parents[0]!]), + ]); + } + return Object.freeze([ + Object.freeze([parents[0]!, parents[1]!, parents[2]!]), + Object.freeze([parents[0]!, parents[2]!, parents[1]!]), + Object.freeze([parents[1]!, parents[0]!, parents[2]!]), + Object.freeze([parents[1]!, parents[2]!, parents[0]!]), + Object.freeze([parents[2]!, parents[0]!, parents[1]!]), + Object.freeze([parents[2]!, parents[1]!, parents[0]!]), + ]); + }; + const bundleOptionsByChild = Array.from( + { length: TIER_I_REGION_COUNT }, + (_, child) => { + const componentId = capacityAssignment.componentByParent[parentByChild[child]!]!; + const options = [...gateApronBundlesByKey] + .filter(([key]) => Number.parseInt(key.split(':')[0]!, 10) === componentId) + .flatMap(([, bundles]) => bundles) + // Lowlands is immutable authority, both as a gate slot and as a donor. + // Every other slot may borrow physical dry terrain only through the + // explicit, count-balanced repartition staged below. + .filter(bundle => child === 0 ? bundle.child === 0 : bundle.child !== 0) + .sort((first, second) => ( + Number(first.child !== child) - Number(second.child !== child) + || first.score - second.score + || first.child - second.child + || first.edges[0].tierTwoEndpoint - second.edges[0].tierTwoEndpoint + )); + return Object.freeze(options.slice(0, 64)); + }, + ); + if (bundleOptionsByChild.some(options => options.length === 0)) { + reject('DRY_GATE_APRON_OPTIONS_MISSING'); + } + const buildTierOneApronRepartition = ( + bundleByChild: readonly GateApronBundle[], + ): Readonly<{ + tierOneRegionId: Uint8Array; + }> | undefined => { + const scopedArrays: OwnedAllocatorArray[] = []; + const scopedOwn = (array: ArrayType): ArrayType => { + scopedArrays.push(own(array)); + return array; + }; + let retainedTrialRegionId: Uint8Array | undefined; + try { + const trialRegionId = scopedOwn(new Uint8Array(regionId)); + const gateApronSlot = scopedOwn(new Int8Array(grid.cellCount)); + gateApronSlot.fill(-1); + const reservedTierOneSlot = scopedOwn(new Int8Array(grid.cellCount)); + reservedTierOneSlot.fill(-1); + for (let child = 0; child < TIER_I_REGION_COUNT; child += 1) { + const bundle = bundleByChild[child]!; + for (const cell of bundle.tierOneCells) { + if (reservedTierOneSlot[cell] >= 0 && reservedTierOneSlot[cell] !== child) { + return undefined; + } + reservedTierOneSlot[cell] = child; + gateApronSlot[cell] = child; + } + for (const cell of bundle.tierTwoCells) { + if (gateApronSlot[cell] >= 0 && gateApronSlot[cell] !== child) { + return undefined; + } + gateApronSlot[cell] = child; + } + } + const usedPatch = scopedOwn(new Uint8Array(grid.cellCount)); + const usedSwap = scopedOwn(new Uint8Array(grid.cellCount)); + const minimumFootholdCells = 512; + for (let child = 1; child < TIER_I_REGION_COUNT; child += 1) { + const bundle = bundleByChild[child]!; + const source = bundle.child; + if (source === child) continue; + const tree = scopedOwn(new Uint8Array(grid.cellCount)); + const canUse = (cell: number): boolean => ( + cell >= 0 + && tierId[cell] === 1 + && originalRegionId[cell] === source + && waterRegime[cell] === WATER_DRY + && legacyProtectedCell[cell] === 0 + && legacyReserveCell[cell] === 0 + && usedPatch[cell] === 0 + && ( + reservedTierOneSlot[cell] < 0 + || reservedTierOneSlot[cell] === child + ) + ); + const clusters = bundle.edges.map(edge => Object.freeze([ + edge.tierOneEndpoint, + ...edge.tierOneCorridors.flat(), + ])); + if (clusters.some(cluster => cluster.some(cell => !canUse(cell)))) { + return undefined; + } + for (const cell of clusters[0]!) tree[cell] = 1; + for (const cluster of clusters.slice(1)) { + const previous = scopedOwn(new Int32Array(grid.cellCount)); + previous.fill(-2); + const queue = scopedOwn(new Uint32Array(grid.cellCount)); + let head = 0; + let tail = 0; + let target = -1; + for (const cell of cluster) { + if (previous[cell] !== -2) continue; + previous[cell] = -1; + queue[tail++] = cell; + if (tree[cell] === 1) target = cell; + } + while (head < tail && target < 0) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (!canUse(neighbor) || previous[neighbor] !== -2) continue; + previous[neighbor] = cell; + if (tree[neighbor] === 1) { + target = neighbor; + break; + } + queue[tail++] = neighbor; + } + } + if (target < 0) return undefined; + for (let cell = target; cell >= 0; cell = previous[cell]!) tree[cell] = 1; + for (const cell of cluster) tree[cell] = 1; + } + const growthQueue = scopedOwn(new Uint32Array(grid.cellCount)); + let growthHead = 0; + let growthTail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tree[cell] !== 1) continue; + growthQueue[growthTail++] = cell; + } + while (growthHead < growthTail && growthTail < minimumFootholdCells) { + const cell = growthQueue[growthHead++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (!canUse(neighbor) || tree[neighbor] === 1) continue; + tree[neighbor] = 1; + growthQueue[growthTail++] = neighbor; + if (growthTail >= minimumFootholdCells) break; + } + } + if (growthTail < minimumFootholdCells) return undefined; + const patch = Array.from(growthQueue.slice(0, growthTail)); + const swapCandidates = Array.from({ length: grid.cellCount }, (_, cell) => cell) + .filter(cell => ( + tierId[cell] === 1 + && trialRegionId[cell] === child + && !strategicallyPassableSurface(waterRegime[cell]!) + && legacyProtectedCell[cell] === 0 + && legacyReserveCell[cell] === 0 + && reservedTierOneSlot[cell] < 0 + && usedPatch[cell] === 0 + && usedSwap[cell] === 0 + )) + .sort((first, second) => first - second); + if (swapCandidates.length < patch.length) return undefined; + for (const cell of patch) { + trialRegionId[cell] = child; + gateApronSlot[cell] = child; + usedPatch[cell] = 1; + } + for (const cell of swapCandidates.slice(0, patch.length)) { + trialRegionId[cell] = source; + usedSwap[cell] = 1; + } + } + const beforeCounts = scopedOwn(new Uint32Array(TIER_I_REGION_COUNT)); + const afterCounts = scopedOwn(new Uint32Array(TIER_I_REGION_COUNT)); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierId[cell] !== 1) continue; + if (regionId[cell]! < TIER_I_REGION_COUNT) beforeCounts[regionId[cell]!] += 1; + if (trialRegionId[cell]! < TIER_I_REGION_COUNT) afterCounts[trialRegionId[cell]!] += 1; + } + if (beforeCounts.some((count, child) => count !== afterCounts[child])) { + return undefined; + } + const topology = passableRegionTopology( + grid, + trialRegionId, + waterRegime, + scopedOwn(new Uint8Array(grid.cellCount)), + ); + scopedOwn(topology.componentId); + for (let child = 0; child < TIER_I_REGION_COUNT; child += 1) { + const bundle = bundleByChild[child]!; + if (bundle.tierOneCells.some(cell => trialRegionId[cell] !== child)) { + return undefined; + } + if ( + bundle.tierOneCells.some(cell => { + const component = topology.componentId[cell]!; + return component < 0 || topology.componentSizes[component]! < 64; + }) + ) return undefined; + } + retainedTrialRegionId = trialRegionId; + return Object.freeze({ tierOneRegionId: trialRegionId }); + } finally { + for (const array of scopedArrays) { + if (array !== retainedTrialRegionId) release(array); + } + } + }; + const buildTierTwoOwnershipForests = ( + bundleByChild: readonly GateApronBundle[], + ): readonly (readonly number[])[] | undefined => { + const scopedArrays: OwnedAllocatorArray[] = []; + const scopedOwn = (array: ArrayType): ArrayType => { + scopedArrays.push(own(array)); + return array; + }; + try { + const forestByParent: Array = Array( + TIER_II_REGION_COUNT, + ).fill(undefined); + const reservedApronOwner = scopedOwn(new Int8Array(grid.cellCount)); + reservedApronOwner.fill(-1); + for (let child = 0; child < TIER_I_REGION_COUNT; child += 1) { + const parent = parentByChild[child]!; + for (const cell of bundleByChild[child]!.tierTwoCells) { + if (reservedApronOwner[cell] >= 0 && reservedApronOwner[cell] !== parent) { + return undefined; + } + reservedApronOwner[cell] = parent; + } + } + for (const component of components) { + const parents = Array.from({ length: TIER_II_REGION_COUNT }, (_, parent) => parent) + .filter(parent => capacityAssignment.componentByParent[parent] === component.id); + if (parents.length === 0) continue; + const member = scopedOwn(new Uint8Array(grid.cellCount)); + const innerBoundary = scopedOwn(new Uint8Array(grid.cellCount)); + for (const cell of component.cells) member[cell] = 1; + for (const cell of component.innerBoundary) innerBoundary[cell] = 1; + let componentForests: Map | undefined; + for (const order of parentOrders(parents)) { + const claimedOwner = scopedOwn(new Int8Array(grid.cellCount)); + claimedOwner.fill(-1); + const trialForests = new Map(); + let complete = true; + for (const parent of order) { + const tree = scopedOwn(new Uint8Array(grid.cellCount)); + const clusters = bundleByChild.flatMap((bundle, child) => ( + parentByChild[child] === parent + ? bundle.edges.map(edge => Object.freeze([ + edge.tierTwoEndpoint, + ...edge.tierTwoCorridors.flat(), + ])) + : [] + )); + const canUse = (cell: number): boolean => ( + cell >= 0 + && member[cell] === 1 + && strategicallyPassableSurface(waterRegime[cell]!) + && legacyProtectedCell[cell] === 0 + && legacyReserveCell[cell] === 0 + && claimedOwner[cell] < 0 + && (reservedApronOwner[cell] < 0 || reservedApronOwner[cell] === parent) + ); + if ( + clusters.length !== 4 + || clusters.some(cluster => cluster.some(cell => !canUse(cell))) + ) { + complete = false; + break; + } + // The canonical first apron cluster is the stable political seed. + // Other apron options are capacity evidence only; final gate repair + // independently re-proves every published gate. Retaining a single + // seed avoids forcing one realm to span mutually separated sectors. + for (const cell of clusters[0]!) tree[cell] = 1; + const previous = scopedOwn(new Int32Array(grid.cellCount)); + previous.fill(-2); + const queue = scopedOwn(new Uint32Array(grid.cellCount)); + let head = 0; + let tail = 0; + let target = -1; + for (const cell of clusters[0]!) { + if (previous[cell] !== -2) continue; + previous[cell] = -1; + queue[tail++] = cell; + if (innerBoundary[cell] === 1) target = cell; + } + while (head < tail && target < 0) { + const cell = queue[head++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (!canUse(neighbor) || previous[neighbor] !== -2) continue; + previous[neighbor] = cell; + if (innerBoundary[neighbor] === 1) { + target = neighbor; + break; + } + queue[tail++] = neighbor; + } + } + if (target < 0) { + complete = false; + break; + } + for (let cell = target; cell >= 0; cell = previous[cell]!) tree[cell] = 1; + const forest: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tree[cell] !== 1) continue; + if (claimedOwner[cell] >= 0) { + complete = false; + break; + } + claimedOwner[cell] = parent; + forest.push(cell); + } + if (!complete) break; + trialForests.set(parent, Object.freeze(forest)); + } + if (complete && trialForests.size === parents.length) { + componentForests = trialForests; + break; + } + } + if (!componentForests) return undefined; + for (const [parent, forest] of componentForests) forestByParent[parent] = forest; + } + if (forestByParent.some(forest => forest === undefined)) return undefined; + return Object.freeze(forestByParent as readonly (readonly number[])[]); + } finally { + for (const array of scopedArrays) release(array); + } + }; + const orderedChildren = Array.from({ length: TIER_I_REGION_COUNT }, (_, child) => child) + .sort((first, second) => { + const firstOwn = bundleOptionsByChild[first]!.filter(bundle => bundle.child === first).length; + const secondOwn = bundleOptionsByChild[second]!.filter(bundle => bundle.child === second).length; + return firstOwn - secondOwn + || bundleOptionsByChild[first]!.length - bundleOptionsByChild[second]!.length + || first - second; + }); + const chosen = new Array(TIER_I_REGION_COUNT); + const occupiedTierOne = new Set(); + const occupiedTierTwo = new Set(); + let selectedRepartition: ReturnType; + let selectedOwnershipForests: readonly (readonly number[])[] | undefined; + let gateApronSearchNodes = 0; + let gateApronCompletePlans = 0; + const MAX_GATE_APRON_SEARCH_NODES = 20_000; + const MAX_GATE_APRON_COMPLETE_PLANS = 128; + const chooseBundles = (depth: number): boolean => { + if (depth === orderedChildren.length) { + if (gateApronCompletePlans >= MAX_GATE_APRON_COMPLETE_PLANS) return false; + gateApronCompletePlans += 1; + const bundleByChild = chosen as GateApronBundle[]; + const repartition = buildTierOneApronRepartition(bundleByChild); + if (!repartition) return false; + const ownershipForests = buildTierTwoOwnershipForests(bundleByChild); + if (!ownershipForests) { + release(repartition.tierOneRegionId); + return false; + } + selectedRepartition = repartition; + selectedOwnershipForests = ownershipForests; + return true; + } + const child = orderedChildren[depth]!; + const parent = parentByChild[child]!; + for (const bundle of bundleOptionsByChild[child]!) { + if (gateApronSearchNodes >= MAX_GATE_APRON_SEARCH_NODES) return false; + gateApronSearchNodes += 1; + if ( + bundle.tierOneCells.some(cell => occupiedTierOne.has(cell)) + || bundle.tierTwoCells.some(cell => occupiedTierTwo.has(cell)) + ) continue; + let separatedFromSibling = true; + for (let sibling = 0; sibling < TIER_I_REGION_COUNT; sibling += 1) { + const selectedSibling = chosen[sibling]; + if (!selectedSibling || parentByChild[sibling] !== parent) continue; + const separation = Math.min(...bundle.edges.flatMap(edge => ( + selectedSibling.edges.map(siblingEdge => axialDistance( + grid.q[edge.tierTwoEndpoint]!, + grid.r[edge.tierTwoEndpoint]!, + grid.q[siblingEdge.tierTwoEndpoint]!, + grid.r[siblingEdge.tierTwoEndpoint]!, + )) + ))); + if (separation < 8) { + separatedFromSibling = false; + break; + } + } + if (!separatedFromSibling) continue; + chosen[child] = bundle; + for (const cell of bundle.tierOneCells) occupiedTierOne.add(cell); + for (const cell of bundle.tierTwoCells) occupiedTierTwo.add(cell); + if (chooseBundles(depth + 1)) return true; + chosen[child] = undefined; + for (const cell of bundle.tierOneCells) occupiedTierOne.delete(cell); + for (const cell of bundle.tierTwoCells) occupiedTierTwo.delete(cell); + } + return false; + }; + if (!chooseBundles(0)) { + reject('DRY_GATE_APRON_SEARCH_EXHAUSTED'); + } + const bundleByChild = chosen as GateApronBundle[]; + if (!selectedRepartition || !selectedOwnershipForests) { + reject('DRY_GATE_APRON_PLAN_MISSING'); + } + const committedRepartition = selectedRepartition as Readonly<{ + tierOneRegionId: Uint8Array; + }>; + const committedOwnershipForests = selectedOwnershipForests as readonly (readonly number[])[]; + regionId.set(committedRepartition.tierOneRegionId); + const gateApronPlan: GateApronPlan = Object.freeze({ + parentByChild: Object.freeze([...parentByChild]), + bundleByChild: Object.freeze([...bundleByChild]), + tierTwoOwnershipForestByParent: committedOwnershipForests, + }); + + const allocationField = own(createGreaterRealmMultiscaleIntegerField( + grid, + candidateSeed, + [ + { + channel: 'strategic-tier-two-passable-capacity', + amplitude: 2_400, + smoothingPasses: 11, + selfWeight: 3, + }, + ], + )); + const assignedPassable = own(new Uint8Array(grid.cellCount)); + const demotedPassable: number[] = []; + const usedComponents = new Set(capacityAssignment.componentByParent); + const tierTwoSpineOwner = own(new Int8Array(grid.cellCount)); + tierTwoSpineOwner.fill(-1); + + for (const component of components) { + if (!usedComponents.has(component.id)) { + demotedPassable.push(...component.cells); + continue; + } + const parents = Array.from({ length: TIER_II_REGION_COUNT }, (_, parent) => parent) + .filter(parent => capacityAssignment.componentByParent[parent] === component.id); + const member = own(new Uint8Array(grid.cellCount)); + for (const cell of component.cells) member[cell] = 1; + const outerBoundaryMask = own(new Uint8Array(grid.cellCount)); + for (const cell of component.outerBoundary) outerBoundaryMask[cell] = 1; + const authoritySeedCellsByParent = new Map(); + for (const parent of parents) { + const ownershipForest = gateApronPlan.tierTwoOwnershipForestByParent[parent]!; + if (ownershipForest.length === 0 || ownershipForest.some(cell => ( + member[cell] !== 1 + || !strategicallyPassableSurface(waterRegime[cell]!) + || legacyProtectedCell[cell] === 1 + || legacyReserveCell[cell] === 1 + ))) reject('STRATEGIC_AUTHORITY_SEED_INVALID'); + authoritySeedCellsByParent.set(parent, ownershipForest); + } + + const owner = own(new Int8Array(grid.cellCount)); + owner.fill(-1); + const counts = own(new Uint32Array(TIER_II_REGION_COUNT)); + let assignedCount = 0; + const seedCellsByParent = new Map(); + for (const parent of parents) { + const seeds = authoritySeedCellsByParent.get(parent)!; + seedCellsByParent.set(parent, seeds); + for (const cell of seeds) { + if (owner[cell] >= 0 && owner[cell] !== parent) reject('SPINE_OVERLAP'); + if (owner[cell] === parent) continue; + owner[cell] = parent; + counts[parent] += 1; + assignedCount += 1; + } + } + const componentCapacity = parents.reduce( + (sum, parent) => sum + targetCounts[parent]!, + 0, + ); + const retainedTarget = Math.min(component.cells.length, componentCapacity); + if (assignedCount > retainedTarget) reject('SPINE_CAPACITY_EXCEEDED'); + const desired = own(new Uint32Array(TIER_II_REGION_COUNT)); + let desiredAssigned = 0; + for (let index = 0; index < parents.length; index += 1) { + const parent = parents[index]!; + desired[parent] = Math.floor(retainedTarget / parents.length) + + (index < retainedTarget % parents.length ? 1 : 0); + desired[parent] = Math.max(desired[parent]!, counts[parent]!); + desiredAssigned += desired[parent]!; + } + while (desiredAssigned > retainedTarget) { + let selected = -1; + for (const parent of [...parents].reverse()) { + if (desired[parent]! <= counts[parent]!) continue; + selected = parent; + break; + } + if (selected < 0) reject('DESIRED_CAPACITY_INVALID'); + desired[selected] -= 1; + desiredAssigned -= 1; + } + while (desiredAssigned < retainedTarget) { + let selected = -1; + for (const parent of parents) { + if (desired[parent]! >= targetCounts[parent]!) continue; + selected = parent; + break; + } + if (selected < 0) reject('DESIRED_CAPACITY_UNDERSIZED'); + desired[selected] += 1; + desiredAssigned += 1; + } + + const heaps = Array.from({ length: TIER_II_REGION_COUNT }, () => ( + [] as GreaterRealmStrategicFrontierEntry[] + )); + const bestPriority = Array.from({ length: TIER_II_REGION_COUNT }, () => { + const values = own(new Int32Array(grid.cellCount)); + values.fill(INT32_MAX); + return values; + }); + const offer = (parent: number, cell: number, depth: number) => { + if (cell < 0 || member[cell] !== 1 || owner[cell] >= 0) return; + const ownershipAffinity = originalRegionId[cell] === TIER_I_REGION_COUNT + parent + ? -480 + : 0; + const priority = clamp( + allocationField[cell]! + depth * 72 + ownershipAffinity, + -INT32_MAX, + INT32_MAX, + ); + if (priority >= bestPriority[parent]![cell]!) return; + bestPriority[parent]![cell] = priority; + strategicFrontierPush( + heaps[parent]!, + Object.freeze({ cell, depth, priority }), + ); + }; + for (const parent of parents) { + for (const cell of seedCellsByParent.get(parent)!) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + offer(parent, grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!, 1); + } + } + } + const pending = new Map(); + const refresh = (parent: number) => { + if (pending.has(parent)) return; + while (heaps[parent]!.length > 0) { + const entry = strategicFrontierPop(heaps[parent]!)!; + if ( + owner[entry.cell] < 0 + && bestPriority[parent]![entry.cell] === entry.priority + ) { + pending.set(parent, entry); + return; + } + } + }; + while (assignedCount < retainedTarget) { + for (const parent of parents) refresh(parent); + let selectedParent = -1; + for (const parent of parents) { + if (!pending.has(parent) || counts[parent]! >= desired[parent]!) continue; + if ( + selectedParent < 0 + || counts[parent]! * desired[selectedParent]! + < counts[selectedParent]! * desired[parent]! + || ( + counts[parent]! * desired[selectedParent]! + === counts[selectedParent]! * desired[parent]! + && parent < selectedParent + ) + ) selectedParent = parent; + } + if (selectedParent < 0) { + for (const parent of parents) { + if (!pending.has(parent) || counts[parent]! >= targetCounts[parent]!) continue; + if (selectedParent < 0 || parent < selectedParent) selectedParent = parent; + } + } + // A competing connected flood can enclose another parent's frontier. + // Do not cross that ownership band or overfill the surviving realm merely + // to consume the component. The unclaimed passable remainder is safely + // returned to Tier I below and balanced with non-passable tier ownership. + if (selectedParent < 0) break; + const entry = pending.get(selectedParent)!; + pending.delete(selectedParent); + if (owner[entry.cell] >= 0) continue; + owner[entry.cell] = selectedParent; + counts[selectedParent] += 1; + assignedCount += 1; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + offer( + selectedParent, + grid.neighbors[entry.cell * HEX_NEIGHBOR_COUNT + direction]!, + entry.depth + 1, + ); + } + } + const innerBoundaryMask = own(new Uint8Array(grid.cellCount)); + for (const cell of component.innerBoundary) innerBoundaryMask[cell] = 1; + const strategicSpineMask = own(new Uint8Array(grid.cellCount)); + const strategicApronMask = own(new Uint8Array(grid.cellCount)); + for (const parent of parents) { + const spineOwner = TIER_I_REGION_COUNT + parent; + for (const cell of seedCellsByParent.get(parent)!) { + if (owner[cell] !== parent || !strategicallyPassableSurface(waterRegime[cell]!)) { + reject('STRATEGIC_AUTHORITY_OWNERSHIP_CHANGED'); + } + if ( + tierTwoSpineOwner[cell] >= 0 + && tierTwoSpineOwner[cell] !== spineOwner + ) reject('SPINE_OWNER_CONFLICT'); + tierTwoSpineOwner[cell] = spineOwner; + strategicSpineMask[cell] = 1; + } + } + for (let child = 0; child < TIER_I_REGION_COUNT; child += 1) { + if (gateApronPlan.parentByChild[child] === undefined) continue; + const bundle = gateApronPlan.bundleByChild[child]!; + if (bundle.componentId !== component.id) continue; + for (const cell of bundle.tierTwoCells) strategicApronMask[cell] = 1; + } + const innerContactCounts = own(new Uint32Array(TIER_II_REGION_COUNT)); + const outerContactCounts = own(new Uint32Array(TIER_II_REGION_COUNT)); + for (const cell of component.cells) { + const parent = owner[cell]!; + if (parent < 0) continue; + if (innerBoundaryMask[cell] === 1) innerContactCounts[parent] += 1; + if (outerBoundaryMask[cell] === 1) outerContactCounts[parent] += 1; + } + const safeDonorBoundaryRemoval = (cell: number, donor: number): boolean => { + let runs = 0; + let previousMatches = false; + let firstMatches = false; + let lastMatches = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + const matches = neighbor >= 0 && owner[neighbor] === donor; + if (direction === 0) firstMatches = matches; + if (matches && !previousMatches) runs += 1; + previousMatches = matches; + lastMatches = matches; + } + if (firstMatches && lastMatches && runs > 1) runs -= 1; + // A single cyclic run means every same-owner neighbour remains linked + // around this hex after removal. This is a conservative local + // articulation guard for an already-connected flood territory. + return runs <= 1; + }; + // Natural choke points can let two balanced fronts enclose a third just + // short of its published land-capacity floor. Move only safe fringe cells + // from surplus neighbours into that connected realm. Each move preserves + // both strategic contacts and cannot sever the donor's local topology. + while (parents.some(parent => counts[parent]! < minimumPrimaryCells)) { + let progress = false; + const deficientParents = [...parents].sort((first, second) => ( + counts[first]! - counts[second]! || first - second + )); + for (const targetParent of deficientParents) { + if (counts[targetParent]! >= minimumPrimaryCells) continue; + const candidates = component.cells.filter(cell => { + const donor = owner[cell]!; + if (donor < 0 || donor === targetParent || counts[donor]! <= minimumPrimaryCells) { + return false; + } + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && owner[neighbor] === targetParent) return true; + } + return false; + }).sort((first, second) => { + const firstDonor = owner[first]!; + const secondDonor = owner[second]!; + return (counts[secondDonor]! - minimumPrimaryCells) + - (counts[firstDonor]! - minimumPrimaryCells) + || allocationField[first]! - allocationField[second]! + || first - second; + }); + for (const cell of candidates) { + if (counts[targetParent]! >= minimumPrimaryCells) break; + const donor = owner[cell]!; + if ( + donor < 0 + || donor === targetParent + || counts[donor]! <= minimumPrimaryCells + || strategicSpineMask[cell] === 1 + || strategicApronMask[cell] === 1 + || ( + innerBoundaryMask[cell] === 1 + && innerContactCounts[donor]! <= 1 + ) + || ( + outerBoundaryMask[cell] === 1 + && outerContactCounts[donor]! <= 1 + ) + || !safeDonorBoundaryRemoval(cell, donor) + ) continue; + let touchesTarget = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor >= 0 && owner[neighbor] === targetParent) { + touchesTarget = true; + break; + } + } + if (!touchesTarget) continue; + owner[cell] = targetParent; + counts[donor] -= 1; + counts[targetParent] += 1; + if (innerBoundaryMask[cell] === 1) { + innerContactCounts[donor] -= 1; + innerContactCounts[targetParent] += 1; + } + if (outerBoundaryMask[cell] === 1) { + outerContactCounts[donor] -= 1; + outerContactCounts[targetParent] += 1; + } + progress = true; + } + } + if (!progress) break; + } + if (parents.some(parent => counts[parent]! < minimumPrimaryCells)) { + reject('PRIMARY_COMPONENT_UNDERSIZED'); + } + for (const cell of component.cells) { + const parent = owner[cell]!; + if (parent < 0) { + demotedPassable.push(cell); + continue; + } + regionId[cell] = TIER_I_REGION_COUNT + parent; + assignedPassable[cell] = 1; + } + } + + for (const cell of demotedPassable) { + if (legacyProtectedCell[cell] === 1 || legacyReserveCell[cell] === 1) { + reject('LOWLANDS_DEMOTION_FORBIDDEN'); + } + tierId[cell] = 1; + } + if (demotedPassable.length > 0) { + const distanceToRetainedTierTwo = own(distanceFromMask(grid, assignedPassable)); + const promotionCandidates = Array.from({ length: grid.cellCount }, (_, cell) => cell) + .filter(cell => ( + tierId[cell] === 1 + && !strategicallyPassableSurface(waterRegime[cell]!) + && legacyProtectedCell[cell] === 0 + && legacyReserveCell[cell] === 0 + )) + .sort((first, second) => ( + distanceToRetainedTierTwo[first]! - distanceToRetainedTierTwo[second]! + || first - second + )); + if (promotionCandidates.length < demotedPassable.length) reject('PROMOTION_CAPACITY_MISSING'); + for (const cell of promotionCandidates.slice(0, demotedPassable.length)) tierId[cell] = 2; + } + + const tierOneMasks = Array.from({ length: TIER_I_REGION_COUNT }, (_, region) => { + const mask = own(new Uint8Array(grid.cellCount)); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierId[cell] === 1 && originalRegionId[cell] === region) mask[cell] = 1; + } + return mask; + }); + const tierOneDistances = tierOneMasks.map(mask => own(distanceFromMask(grid, mask))); + for (const cell of demotedPassable) { + let selectedRegion = 1; + for (let region = 2; region < TIER_I_REGION_COUNT; region += 1) { + if ( + tierOneDistances[region]![cell]! < tierOneDistances[selectedRegion]![cell]! + || ( + tierOneDistances[region]![cell] === tierOneDistances[selectedRegion]![cell] + && region < selectedRegion + ) + ) selectedRegion = region; + } + regionId[cell] = selectedRegion; + } + + const passableCounts = own(new Uint32Array(TIER_II_REGION_COUNT)); + const nonPassableTierTwo: number[] = []; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierId[cell] !== 2) continue; + if (strategicallyPassableSurface(waterRegime[cell]!)) { + const parent = regionId[cell]! - TIER_I_REGION_COUNT; + if (parent < 0 || parent >= TIER_II_REGION_COUNT) reject('PASSABLE_OWNER_INVALID'); + passableCounts[parent] += 1; + } else { + nonPassableTierTwo.push(cell); + } + } + const deficits = targetCounts.map((target, parent) => target - passableCounts[parent]!); + if (deficits.some(value => value < 0)) reject('REGION_CELL_CAPACITY_EXCEEDED'); + if (deficits.reduce((sum, value) => sum + value, 0) !== nonPassableTierTwo.length) { + reject('NON_PASSABLE_BALANCE_INVALID'); + } + const parentDistance = Array.from({ length: TIER_II_REGION_COUNT }, (_, parent) => { + const mask = own(new Uint8Array(grid.cellCount)); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + tierId[cell] === 2 + && regionId[cell] === TIER_I_REGION_COUNT + parent + && strategicallyPassableSurface(waterRegime[cell]!) + ) mask[cell] = 1; + } + return own(distanceFromMask(grid, mask)); + }); + const preferences = nonPassableTierTwo.map(cell => { + const order = Array.from({ length: TIER_II_REGION_COUNT }, (_, parent) => parent) + .sort((first, second) => ( + parentDistance[first]![cell]! - parentDistance[second]![cell]! + || first - second + )); + const margin = parentDistance[order[1]!]![cell]! - parentDistance[order[0]!]![cell]!; + return Object.freeze({ cell, order: Object.freeze(order), margin }); + }).sort((first, second) => ( + second.margin - first.margin + || parentDistance[first.order[0]!]![first.cell]! + - parentDistance[second.order[0]!]![second.cell]! + || first.cell - second.cell + )); + for (const preference of preferences) { + const parent = preference.order.find(candidate => deficits[candidate]! > 0); + if (parent === undefined) reject('NON_PASSABLE_ASSIGNMENT_EXHAUSTED'); + const selectedParent = parent as number; + regionId[preference.cell] = TIER_I_REGION_COUNT + selectedParent; + deficits[selectedParent] -= 1; + } + if (deficits.some(value => value !== 0)) reject('NON_PASSABLE_DEFICIT_REMAINS'); + + const finalTierCounts: [number, number, number] = [0, 0, 0]; + const finalRegionCounts = Array(REGION_COUNT).fill(0); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + finalTierCounts[tierId[cell]! - 1] += 1; + finalRegionCounts[regionId[cell]!] += 1; + if ( + (legacyProtectedCell[cell] === 1 || legacyReserveCell[cell] === 1) + && ( + tierId[cell] !== originalTierId[cell] + || regionId[cell] !== originalRegionId[cell] + ) + ) reject('LOWLANDS_LOCK_CHANGED'); + } + if (finalTierCounts.some((count, tier) => count !== strategy.tierCounts[tier])) { + reject('TIER_TOTAL_CHANGED'); + } + if (targetCounts.some((count, parent) => ( + finalRegionCounts[TIER_I_REGION_COUNT + parent] !== count + ))) reject('REGION_TOTAL_CHANGED'); + if (!regionBalance(finalRegionCounts.slice(TIER_I_REGION_COUNT, TIER_III_REGION_INDEX))) { + reject('REGION_BALANCE_FAILED'); + } + const tierTwoPassableOwner = own(new Int8Array(grid.cellCount)); + tierTwoPassableOwner.fill(-1); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if ( + tierId[cell] === 2 + && strategicallyPassableSurface(waterRegime[cell]!) + ) tierTwoPassableOwner[cell] = regionId[cell]!; + } + assertTierTwoCapacityAuthority({ + grid, + tierId, + regionId, + waterRegime, + tierTwoPassableOwner, + tierTwoSpineOwner, + }); + const topology = passableRegionTopology( + grid, + regionId, + waterRegime, + own(new Uint8Array(grid.cellCount)), + ); + own(topology.componentId); + for (let region = TIER_I_REGION_COUNT; region < TIER_III_REGION_INDEX; region += 1) { + if (topology.largestSharesBasisPoints[region]! < 9_500) { + reject('PASSABLE_COHERENCE_FAILED'); + } + let innerContacts = 0; + let outerContacts = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (regionId[cell] !== region || waterRegime[cell] !== WATER_DRY) continue; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || waterRegime[neighbor] !== WATER_DRY) continue; + if (tierId[neighbor] === 3) innerContacts += 1; + else if (tierId[neighbor] === 1) outerContacts += 1; + } + } + if (innerContacts === 0 || outerContacts === 0) reject('STRATEGIC_FRONTIER_MISSING'); + } + const authority = Object.freeze({ + tierId, + regionId, + tierCounts: Object.freeze(finalTierCounts), + regionCounts: Object.freeze(finalRegionCounts), + tierTwoPassableOwner, + tierTwoSpineOwner, + }); + retainedAuthorityArrays = new Set([ + tierId, + regionId, + tierTwoPassableOwner, + tierTwoSpineOwner, + ]); + return authority; + } finally { + for (const array of ownedAllocatorArrays) { + if (!retainedAuthorityArrays.has(array)) array.fill(0); + } + } +} + function remapTierOneNaturalBasinsByCharacter( grid: IndexedAxialGrid, provisional: Readonly<{ @@ -2437,6 +3800,8 @@ function repairNaturalRegionLandCoherence( }>, waterRegime: Uint8Array, legacyProtectedCell: Uint8Array, + tierTwoPassableOwner: Int8Array, + tierTwoSpineOwner: Int8Array, ): Readonly<{ tierId: Uint8Array; regionId: Uint8Array; @@ -2448,12 +3813,23 @@ function repairNaturalRegionLandCoherence( const tierId = new Uint8Array(strategy.tierId); const regionId = new Uint8Array(strategy.regionId); const initialCounts = [...strategy.regionCounts]; - const regionRepairLocked = new Uint8Array(grid.cellCount); + assertTierTwoCapacityAuthority({ + grid, + tierId, + regionId, + waterRegime, + tierTwoPassableOwner, + tierTwoSpineOwner, + }); + const regionRepairLocked = Uint8Array.from( + tierTwoPassableOwner, + owner => owner >= 0 ? 1 : 0, + ); // Keep each region's dominant land body. A detached component is transferred // only to a same-tier region it physically touches; truly isolated T1 // islands belong to Stonewake. Equal non-passable water ownership is swapped // back so the exact region/tier population contract is unchanged. - const repairOrder = [1, 2, 3, 5, 6, 7, 8, 9, 0, 4] as const; + const repairOrder = [1, 2, 3, 5, 9, 0, 4] as const; for (const sourceRegion of repairOrder) { const included = new Uint8Array(grid.cellCount); for (let cell = 0; cell < grid.cellCount; cell += 1) { @@ -2671,6 +4047,14 @@ function repairNaturalRegionLandCoherence( } if (targetRegion < 0 || targetRegion === sourceRegion) continue; + // Tier membership is authoritative once the water-aware middle-ring + // allocator has committed its exact capacity. A fragmented throne + // island would require a new pre-allocation normalization pass; never + // mutate Tier III into Tier II here and silently invalidate the spines. + if (sourceRegion === TIER_III_REGION_INDEX) { + rejectGreaterRealmCandidate('GREATER_REALM_TIER_TWO_CAPACITY_INVARIANT'); + } + const waterSwapCandidates: number[] = []; for (let cell = 0; cell < grid.cellCount; cell += 1) { if ( @@ -2694,13 +4078,11 @@ function repairNaturalRegionLandCoherence( }); for (const cell of component) { regionId[cell] = targetRegion; - if (sourceRegion === TIER_III_REGION_INDEX) tierId[cell] = 2; regionRepairLocked[cell] = 1; } for (let index = 0; index < component.length; index += 1) { const swap = waterSwapCandidates[index]!; regionId[swap] = sourceRegion; - if (sourceRegion === TIER_III_REGION_INDEX) tierId[swap] = 3; } } // Lowlands (and any analogous island realm) may have its immutable main @@ -2867,7 +4249,7 @@ function repairNaturalRegionLandCoherence( new Uint8Array(grid.cellCount), ); const passableFloor = initialPassableTopology.passableCounts[TIER_III_REGION_INDEX]! + 512; - const regionsByNeed = Array.from({ length: TIER_III_REGION_INDEX }, (_, region) => region) + const regionsByNeed = Array.from({ length: TIER_I_REGION_COUNT }, (_, region) => region) .sort((first, second) => ( initialPassableTopology.passableCounts[first]! - initialPassableTopology.passableCounts[second]! @@ -2968,7 +4350,99 @@ function repairNaturalRegionLandCoherence( } } const noBarrier = new Uint8Array(grid.cellCount); - const topology = passableRegionTopology(grid, regionId, waterRegime, noBarrier); + const naturalRobustTopology = robustRegionTopology( + grid, + regionId, + waterRegime, + noBarrier, + ); + const redundantNaturalEndpoint = (endpoint: number, region: number): boolean => { + const componentCounts = new Map(); + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || regionId[neighbor] !== region + || !strategicallyPassableSurface(waterRegime[neighbor]!) + ) continue; + const component = naturalRobustTopology.componentId[neighbor]!; + if ( + component < 0 + || naturalRobustTopology.componentSizes[component]! < 64 + ) continue; + componentCounts.set(component, (componentCounts.get(component) ?? 0) + 1); + } + return [...componentCounts.values()].some(count => count >= 2); + }; + const tierBoundary = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || tierId[neighbor] === tierId[cell]) continue; + tierBoundary[cell] = 1; + tierBoundary[neighbor] = 1; + } + } + const distanceToTierBoundary = new Uint16Array(grid.cellCount); + distanceToTierBoundary.fill(0xffff); + const tierBoundaryQueue = new Uint32Array(grid.cellCount); + let tierBoundaryHead = 0; + let tierBoundaryTail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierBoundary[cell] !== 1) continue; + distanceToTierBoundary[cell] = 0; + tierBoundaryQueue[tierBoundaryTail++] = cell; + } + while (tierBoundaryHead < tierBoundaryTail) { + const cell = tierBoundaryQueue[tierBoundaryHead++]!; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 + || tierId[neighbor] !== tierId[cell] + || distanceToTierBoundary[neighbor] !== 0xffff + ) continue; + distanceToTierBoundary[neighbor] = distanceToTierBoundary[cell]! + 1; + tierBoundaryQueue[tierBoundaryTail++] = neighbor; + } + } + const hasOuterBandWitness = (endpoint: number): boolean => { + if ( + legacyProtectedCell[endpoint] === 1 + || distanceToTierBoundary[endpoint] !== 0 + ) return false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const shoulder = grid.neighbors[endpoint * HEX_NEIGHBOR_COUNT + direction]!; + if ( + shoulder < 0 + || tierId[shoulder] !== tierId[endpoint] + || legacyProtectedCell[shoulder] === 1 + || distanceToTierBoundary[shoulder] >= 2 + ) continue; + for (let exitDirection = 0; exitDirection < HEX_NEIGHBOR_COUNT; exitDirection += 1) { + const exit = grid.neighbors[shoulder * HEX_NEIGHBOR_COUNT + exitDirection]!; + if ( + exit >= 0 + && tierId[exit] === tierId[endpoint] + && legacyProtectedCell[exit] === 0 + && distanceToTierBoundary[exit]! >= 2 + ) return true; + } + } + return false; + }; + const dryComponentSize = new Uint32Array(grid.cellCount); + for (let region = 0; region < REGION_COUNT; region += 1) { + const dryRegion = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (regionId[cell] === region && waterRegime[cell] === WATER_DRY) { + dryRegion[cell] = 1; + } + } + for (const component of connectedComponents(grid, dryRegion)) { + for (const cell of component) dryComponentSize[cell] = component.length; + } + } const contactScores = Array.from({ length: TIER_I_REGION_COUNT }, () => ( new Uint32Array(TIER_II_REGION_COUNT) )); @@ -2981,14 +4455,19 @@ function repairNaturalRegionLandCoherence( const child = regionId[first]!; const parent = regionId[second]!; if (child >= TIER_I_REGION_COUNT || parent < 6 || parent > 8) continue; - if (waterRegime[first] !== 0 || waterRegime[second] !== 0) continue; - const firstComponent = topology.componentId[first]!; - const secondComponent = topology.componentId[second]!; - const durable = firstComponent >= 0 - && secondComponent >= 0 - && topology.componentSizes[firstComponent]! >= 64 - && topology.componentSizes[secondComponent]! >= 64; - contactScores[child]![parent - 6] += durable ? 101 : 1; + if ( + waterRegime[first] !== 0 + || waterRegime[second] !== 0 + || legacyProtectedCell[first] === 1 + || legacyProtectedCell[second] === 1 + ) continue; + const durable = dryComponentSize[first]! >= 64 + && dryComponentSize[second]! >= 64; + if (!hasOuterBandWitness(first) || !hasOuterBandWitness(second)) continue; + const redundant = redundantNaturalEndpoint(first, child) + && redundantNaturalEndpoint(second, parent); + if (redundant) contactScores[child]![parent - 6] += 10_001; + else if (durable) contactScores[child]![parent - 6] += 101; } } const provisionalParent = new Uint8Array(TIER_I_REGION_COUNT); @@ -3062,6 +4541,9 @@ function repairNaturalRegionLandCoherence( const component = currentTopology.componentId[endpoint]!; if (component < 0) return false; if (currentTopology.componentSizes[component]! >= minimumGateFootholdCells) return true; + if (targetRegion >= TIER_I_REGION_COUNT && targetRegion < TIER_III_REGION_INDEX) { + return false; + } const seen = new Uint8Array(grid.cellCount); const queue = new Uint32Array(grid.cellCount); let head = 0; @@ -3143,6 +4625,9 @@ function repairNaturalRegionLandCoherence( targetRegion: number, adjacentRegion: number, ): boolean => { + if (targetRegion >= TIER_I_REGION_COUNT && targetRegion < TIER_III_REGION_INDEX) { + return false; + } const topology = passableRegionTopology( grid, regionId, @@ -3355,6 +4840,14 @@ function repairNaturalRegionLandCoherence( if (balancedTierCounts.some((count, tier) => count !== strategy.tierCounts[tier])) { fail('GREATER_REALM_GATE_FOOTHOLD_TIER_BALANCE_FAILED'); } + assertTierTwoCapacityAuthority({ + grid, + tierId, + regionId, + waterRegime, + tierTwoPassableOwner, + tierTwoSpineOwner, + }); return Object.freeze({ ...strategy, tierId, @@ -3387,6 +4880,90 @@ function strategicallyPassableSurface(waterRegime: number): boolean { return waterRegime === 0 || waterRegime === 3 || waterRegime === 4; } +function assertTierTwoCapacityAuthority(input: Readonly<{ + grid: IndexedAxialGrid; + tierId: Uint8Array; + regionId: Uint8Array; + waterRegime: Uint8Array; + tierTwoPassableOwner: Int8Array; + tierTwoSpineOwner: Int8Array; + barrier?: Uint8Array; +}>): void { + const reject = (): never => rejectGreaterRealmCandidate( + 'GREATER_REALM_TIER_TWO_CAPACITY_INVARIANT', + ); + const { grid } = input; + if ( + input.tierId.length !== grid.cellCount + || input.regionId.length !== grid.cellCount + || input.waterRegime.length !== grid.cellCount + || input.tierTwoPassableOwner.length !== grid.cellCount + || input.tierTwoSpineOwner.length !== grid.cellCount + || (input.barrier !== undefined && input.barrier.length !== grid.cellCount) + ) reject(); + const spineMasks = Array.from({ length: TIER_II_REGION_COUNT }, () => ( + new Uint8Array(grid.cellCount) + )); + const spineCounts = new Uint32Array(TIER_II_REGION_COUNT); + const touchesOuter = new Uint8Array(TIER_II_REGION_COUNT); + const touchesInner = new Uint8Array(TIER_II_REGION_COUNT); + try { + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const passableOwner = input.tierTwoPassableOwner[cell]!; + if (passableOwner >= 0) { + if ( + passableOwner < TIER_I_REGION_COUNT + || passableOwner >= TIER_III_REGION_INDEX + || input.tierId[cell] !== 2 + || input.regionId[cell] !== passableOwner + || !strategicallyPassableSurface(input.waterRegime[cell]!) + ) reject(); + } + const spineOwner = input.tierTwoSpineOwner[cell]!; + if (spineOwner < 0) continue; + if ( + spineOwner < TIER_I_REGION_COUNT + || spineOwner >= TIER_III_REGION_INDEX + || passableOwner !== spineOwner + || input.tierId[cell] !== 2 + || input.regionId[cell] !== spineOwner + || !strategicallyPassableSurface(input.waterRegime[cell]!) + ) reject(); + const parent = spineOwner - TIER_I_REGION_COUNT; + spineMasks[parent]![cell] = 1; + spineCounts[parent] += 1; + let crossTierAdjacent = false; + for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * HEX_NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || input.tierId[neighbor] === 2) continue; + crossTierAdjacent = true; + if (input.waterRegime[neighbor] !== WATER_DRY) continue; + if (input.tierId[neighbor] === 1) touchesOuter[parent] = 1; + else if (input.tierId[neighbor] === 3) touchesInner[parent] = 1; + } + if ( + input.barrier !== undefined + && !crossTierAdjacent + && input.barrier[cell] !== 0 + ) reject(); + } + for (let parent = 0; parent < TIER_II_REGION_COUNT; parent += 1) { + if ( + spineCounts[parent] === 0 + || touchesOuter[parent] !== 1 + || touchesInner[parent] !== 1 + ) reject(); + const components = connectedComponents(grid, spineMasks[parent]!); + if (components.length !== 1 || components[0]!.length !== spineCounts[parent]) reject(); + } + } finally { + for (const mask of spineMasks) mask.fill(0); + spineCounts.fill(0); + touchesOuter.fill(0); + touchesInner.fill(0); + } +} + function passableRegionTopology( grid: IndexedAxialGrid, regionId: Uint8Array, @@ -4106,7 +5683,7 @@ function barrierApproachPaths( if ( neighbor >= 0 && regionId[neighbor] === region - && waterRegime[neighbor] === 0 + && waterRegime[neighbor] === WATER_DRY ) starts.push(neighbor); } starts.sort((first, second) => first - second); @@ -4147,7 +5724,7 @@ function barrierApproachPaths( || forbidden.has(neighbor) || previous[neighbor] !== -2 || regionId[neighbor] !== region - || waterRegime[neighbor] !== 0 + || waterRegime[neighbor] !== WATER_DRY ) continue; previous[neighbor] = cell; depth[neighbor] = depth[cell]! + 1; @@ -4490,8 +6067,14 @@ function reconcileBarrierMeasuredRegionCoherence( sweepTopology.largestSharesBasisPoints[first]! - sweepTopology.largestSharesBasisPoints[second]! || first - second - )); + )); for (const sourceRegion of ordering) { + // The water-aware capacity allocator is the last political authority for + // passable Tier-II land. Barrier repair may reconnect its terrain but + // must never cure a shoulder split by donating that land to a peer. + if (sourceRegion >= TIER_I_REGION_COUNT && sourceRegion < TIER_III_REGION_INDEX) { + continue; + } const included = new Uint8Array(grid.cellCount); for (let cell = 0; cell < grid.cellCount; cell += 1) { if ( @@ -4675,6 +6258,8 @@ function barriersAndGates( regionId: Uint8Array, gateGraph: readonly GreaterRealmGateGraphEdge[], legacyProtectedCell: Uint8Array, + tierTwoPassableOwner: Int8Array, + tierTwoSpineOwner: Int8Array, ): Readonly<{ barrier: Uint8Array; geologicalBarrierBand: Uint8Array; @@ -4796,6 +6381,13 @@ function barriersAndGates( ) ) barrier[cell] = 1; } + // The allocator's inner-to-outer spines are political and traversal + // authority, not a request to punch extra Crown crossings. Keep only their + // same-tier interior open; every cell touching another tier remains under + // the ordinary sealed-boundary and reviewed-gate rules below. + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (tierTwoSpineOwner[cell] >= 0 && boundary[cell] === 0) barrier[cell] = 0; + } const sidePath = ( endpoint: number, system: 1 | 2, @@ -5583,6 +7175,15 @@ function barriersAndGates( barrier, gates, ); + assertTierTwoCapacityAuthority({ + grid, + tierId, + regionId, + waterRegime, + tierTwoPassableOwner, + tierTwoSpineOwner, + barrier, + }); let futureOpenCrossTierEdges = 0; for (let cell = 0; cell < grid.cellCount; cell += 1) { for (let direction = 0; direction < HEX_NEIGHBOR_COUNT; direction += 1) { @@ -5652,7 +7253,7 @@ function barriersAndGates( if ( neighbor >= 0 && regionId[neighbor] === region - && waterRegime[neighbor] === 0 + && waterRegime[neighbor] === WATER_DRY && barrier[neighbor] === 0 && passableTopology.componentId[neighbor]! >= 0 && passableTopology.componentSizes[passableTopology.componentId[neighbor]!]! >= 64 @@ -5678,7 +7279,7 @@ function barriersAndGates( const cell = path[index]!; if ( regionId[cell] !== region - || waterRegime[cell] !== 0 + || waterRegime[cell] !== WATER_DRY || barrier[cell] !== 0 ) return -1; if (index > 0) { @@ -6468,6 +8069,7 @@ function candidateStageDigests( 'geomorphologyTemperature', 'geomorphologyMoisture', 'geomorphologyTotalDelta', + 'geomorphologyTerraceDelta', 'geomorphologyGlacialDelta', 'geomorphologyAridDelta', 'geomorphologyVolcanicDelta', @@ -6581,9 +8183,17 @@ export function generateGreaterRealmCandidate(input: Readonly<{ surface.biomeId, surface.landformId, ); - const semanticStrategy = remapTierOneNaturalBasinsByCharacter( + const capacityStrategy = allocateTierTwoPassableCapacity( grid, + candidateSeed, provisionalStrategy, + surface.waterRegime, + legacy.protectedCell, + legacy.reserveCell, + ); + const semanticStrategy = remapTierOneNaturalBasinsByCharacter( + grid, + capacityStrategy, reconciled.elevation, uplift, reconciled.flowAccumulation, @@ -6603,6 +8213,8 @@ export function generateGreaterRealmCandidate(input: Readonly<{ semanticStrategy, surface.waterRegime, legacy.protectedCell, + capacityStrategy.tierTwoPassableOwner, + capacityStrategy.tierTwoSpineOwner, ); const topography = deriveGreaterRealmTopography({ grid, @@ -6644,7 +8256,30 @@ export function generateGreaterRealmCandidate(input: Readonly<{ strategy.regionId, strategy.gateGraph, legacy.protectedCell, + capacityStrategy.tierTwoPassableOwner, + capacityStrategy.tierTwoSpineOwner, ); + const finalTierAuthorityCounts: [number, number, number] = [0, 0, 0]; + const finalRegionAuthorityCounts = Array(REGION_COUNT).fill(0); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + finalTierAuthorityCounts[strategy.tierId[cell]! - 1] += 1; + finalRegionAuthorityCounts[strategy.regionId[cell]!] += 1; + if ( + legacy.protectedCell[cell] === 1 + && ( + strategy.tierId[cell] !== capacityStrategy.tierId[cell] + || strategy.regionId[cell] !== capacityStrategy.regionId[cell] + ) + ) { + rejectGreaterRealmCandidate('GREATER_REALM_TIER_TWO_CAPACITY_INVARIANT'); + } + } + if ( + finalTierAuthorityCounts.some((count, tier) => count !== strategy.tierCounts[tier]) + || finalRegionAuthorityCounts.some( + (count, region) => count !== strategy.regionCounts[region], + ) + ) rejectGreaterRealmCandidate('GREATER_REALM_TIER_TWO_CAPACITY_INVARIANT'); const sites = castleAndPotentialSites( grid, candidateSeed, @@ -6761,6 +8396,20 @@ export function generateGreaterRealmCandidate(input: Readonly<{ advancedGeomorphology: geomorphology.metrics.changedCellCount > 0 && geomorphology.metrics.maximumAbsoluteCellDelta <= 8_192 && geomorphology.metrics.protectedChangedCellCount === 0 + && geomorphology.metrics.terraces.changedCellCount > 0 + && geomorphology.metrics.terraces.plateauCellCount + > geomorphology.metrics.terraces.rampCellCount + && geomorphology.metrics.terraces.rampCellCount > 0 + && geomorphology.metrics.terraces.realizedPlateauCellCount * 100 + >= geomorphology.metrics.terraces.eligibleCellCount * 35 + && geomorphology.metrics.terraces.realizedRampCellCount > 0 + && geomorphology.metrics.terraces.spatialRampCellCount > 0 + && geomorphology.metrics.terraces.fullStepEdgeCount === 0 + && geomorphology.metrics.terraces.maximumNewEdgeIncrease <= 1_200 + && geomorphology.metrics.terraces.weatheredDetailCellCount > 0 + && geomorphology.metrics.terraces.maximumAbsoluteCellDelta <= 2_200 + && Math.abs(geomorphology.metrics.terraces.netElevationDelta) + <= geomorphology.metrics.terraces.eligibleCellCount * 300 && geomorphology.metrics.erodedMaterialUnits === geomorphology.metrics.depositedMaterialUnits + geomorphology.metrics.exportedMaterialUnits @@ -6964,6 +8613,7 @@ export function generateGreaterRealmCandidate(input: Readonly<{ geomorphologyTemperature: geomorphology.temperature, geomorphologyMoisture: geomorphology.moisture, geomorphologyTotalDelta: geomorphology.totalDelta, + geomorphologyTerraceDelta: geomorphology.terraceDelta, geomorphologyGlacialDelta: geomorphology.glacialDelta, geomorphologyAridDelta: geomorphology.aridDelta, geomorphologyVolcanicDelta: geomorphology.volcanicDelta, @@ -7028,6 +8678,7 @@ export function generateGreaterRealmCandidate(input: Readonly<{ geomorphologyTemperature: geomorphology.temperature, geomorphologyMoisture: geomorphology.moisture, geomorphologyTotalDelta: geomorphology.totalDelta, + geomorphologyTerraceDelta: geomorphology.terraceDelta, geomorphologyGlacialDelta: geomorphology.glacialDelta, geomorphologyAridDelta: geomorphology.aridDelta, geomorphologyVolcanicDelta: geomorphology.volcanicDelta, diff --git a/scripts/atlas/greater-realm-candidate-package.ts b/scripts/atlas/greater-realm-candidate-package.ts index e5690513..a1c4e4ee 100644 --- a/scripts/atlas/greater-realm-candidate-package.ts +++ b/scripts/atlas/greater-realm-candidate-package.ts @@ -30,8 +30,12 @@ import { inspectGreaterRealmTrustedGit, sha256GreaterRealmAttestedFile, } from './greater-realm-git'; +import { + compositeGreaterRealmAtmosphere, + integrateGreaterRealmHeightFog, +} from './greater-realm-atmosphere'; -const PRIVATE_ATLAS_FORMAT_VERSION = 4; +const PRIVATE_ATLAS_FORMAT_VERSION = 5; const PRIVATE_ATLAS_MAXIMUM_BYTES = 128 * 1024 * 1024; const PRIVATE_PREVIEW_MAXIMUM_BYTES = 16 * 1024 * 1024; const PRIVATE_MANIFEST_MAXIMUM_BYTES = 4 * 1024 * 1024; @@ -58,14 +62,21 @@ const PRIVATE_PREVIEW_WATER_RIVER = 3; const PRIVATE_PREVIEW_WATER_STREAM = 4; const PRIVATE_PREVIEW_WATER_SEA = 5; const PRIVATE_PREVIEW_FOG_COLOR = Object.freeze([24, 22, 31] as const); +const PRIVATE_PREVIEW_SKY_HAZE = Object.freeze([174, 188, 207] as const); +// Candidate elevation is fail-closed to ±60k, so this review camera remains +// physically above every legal peak. +const PRIVATE_PREVIEW_CAMERA_HEIGHT = 80_000; +const PRIVATE_PREVIEW_HORIZONTAL_SCALE = 120; const PRIVATE_CANVAS_RADIUS = 270; const PRIVATE_CHUNK_AXIS_SPAN = 15; const PRIVATE_CHUNK_PARTITION_VERSION = 'axial-bin-15-v1' as const; const PRIVATE_CHUNK_SCHEMA = 'warpkeep.greater-realm.private-chunk-manifest.v1' as const; const PRIVATE_TOPOGRAPHY_PATCH_SCHEMA = 'warpkeep.greater-realm.private-topography-patch.v1' as const; -const PRIVATE_TOPOGRAPHY_VERSION = 'greater-realm-advanced-topography-v1' as const; -const PRIVATE_TOPOGRAPHY_ENCODING_VERSION = 'wkgr-topography-fields-v1' as const; +const PRIVATE_TOPOGRAPHY_VERSION = + 'greater-realm-advanced-topography-v2' as const; +const PRIVATE_TOPOGRAPHY_ENCODING_VERSION = + 'wkgr-topography-fields-v2' as const; const PRIVATE_PINNED_TOOLCHAIN = Object.freeze({ configuredNodeEngine: '>=22.13 <23', @@ -614,6 +625,12 @@ function privateFields(candidate: GreaterRealmPrivateCandidate): readonly Encode width: 4, array: candidate.geomorphologyTotalDelta, }, + { + name: 'geomorphology-terrace-delta', + type: 5, + width: 4, + array: candidate.geomorphologyTerraceDelta, + }, { name: 'geomorphology-glacial-delta', type: 5, @@ -825,6 +842,7 @@ const PRIVATE_TOPOGRAPHY_FIELD_NAMES = Object.freeze([ 'rock-resistance', 'geomorphology-elevation', 'geomorphology-total-delta', + 'geomorphology-terrace-delta', 'geomorphology-glacial-delta', 'geomorphology-arid-delta', 'geomorphology-volcanic-delta', @@ -1273,108 +1291,180 @@ export async function renderGreaterRealmPrivatePreview( let encoded: Buffer | undefined; let distanceToTopographicLand: Uint16Array | undefined; try { - for (let pixel = 0; pixel < pixels.length; pixel += 4) { - pixels[pixel] = PRIVATE_PREVIEW_FOG_COLOR[0]; - pixels[pixel + 1] = PRIVATE_PREVIEW_FOG_COLOR[1]; - pixels[pixel + 2] = PRIVATE_PREVIEW_FOG_COLOR[2]; - pixels[pixel + 3] = 255; - } - let minimumQ = Number.POSITIVE_INFINITY; - let maximumQ = Number.NEGATIVE_INFINITY; - let minimumR = Number.POSITIVE_INFINITY; - let maximumR = Number.NEGATIVE_INFINITY; - for (let index = 0; index < candidate.grid.cellCount; index += 1) { - minimumQ = Math.min(minimumQ, candidate.grid.q[index]!); - maximumQ = Math.max(maximumQ, candidate.grid.q[index]!); - minimumR = Math.min(minimumR, candidate.grid.r[index]!); - maximumR = Math.max(maximumR, candidate.grid.r[index]!); - } - const qSpan = maximumQ - minimumQ + 1; - const rSpan = maximumR - minimumR + 1; - const scale = Math.max(1, Math.floor(Math.min((width - 80) / (qSpan + rSpan / 2), (height - 120) / rSpan))); - const palette = [ - [74, 126, 72], [80, 141, 187], [74, 167, 205], [73, 129, 185], - [92, 159, 204], [119, 160, 83], [80, 116, 72], [178, 190, 204], - [188, 157, 92], [151, 104, 64], [99, 121, 82], [201, 184, 110], - [173, 133, 74], [143, 123, 88], [110, 119, 132], [100, 79, 109], - [65, 76, 88], [184, 194, 207], [129, 139, 98], [108, 108, 112], - [36, 74, 126], [59, 112, 161], [77, 145, 187], [124, 91, 142], - ] as const; - const regionPalette = [ - [87, 144, 84], [117, 159, 195], [205, 151, 76], [84, 147, 127], [103, 124, 164], - [151, 96, 80], [92, 112, 82], [116, 103, 126], [85, 121, 142], [116, 84, 135], - ] as const; - if (mode === 'regions') { - distanceToTopographicLand = privatePreviewDistanceToTopographicLand(candidate); - } - const gateCells = new Set(candidate.gates.flatMap(gate => [gate.firstCell, gate.secondCell])); - for (let index = 0; index < candidate.grid.cellCount; index += 1) { - const x = 40 + Math.round(((candidate.grid.q[index]! - minimumQ) + (candidate.grid.r[index]! - minimumR) / 2) * scale); - const y = 60 + Math.round((candidate.grid.r[index]! - minimumR) * scale * 0.86); - let color: readonly [number, number, number] = [44, 49, 64]; - if (mode === 'silhouette') { - // Rivers and streams are features inside the continental footprint, not - // coastline cuts. Silhouette review therefore follows sea-level land. - color = candidate.elevation[index]! > PRIVATE_PREVIEW_SEA_LEVEL - ? [142, 164, 105] - : [39, 76, 124]; - } else if (mode === 'hillshade') { - const shade = clampPreview(90 + Math.floor((candidate.elevation[index]! + 12_000) / 260)); - color = [shade, shade, Math.min(255, shade + 8)]; - } else if (mode === 'biome') { - color = palette[candidate.biomeId[index]! % palette.length]!; - } else if (mode === 'hydrology') { - color = candidate.waterRegime[index] === 0 ? [109, 118, 91] : [48, 132, 205]; - } else if (mode === 'regions') { - const regime = candidate.waterRegime[index]!; - if (regime === PRIVATE_PREVIEW_WATER_DRY) { - color = regionPalette[candidate.regionId[index]!]!; - } else if (regime === PRIVATE_PREVIEW_WATER_OCEAN) { - const distance = distanceToTopographicLand?.[index] ?? 0; - const band = Math.min(5, Math.floor(distance / 4)); - color = [54 - band * 5, 91 - band * 8, 132 - band * 10]; - } else if ( - regime === PRIVATE_PREVIEW_WATER_LAKE - || regime === PRIVATE_PREVIEW_WATER_SEA - ) { - color = [66, 126, 171]; - } else if ( - regime === PRIVATE_PREVIEW_WATER_RIVER - || regime === PRIVATE_PREVIEW_WATER_STREAM - ) { - color = [72, 143, 190]; + for (let pixel = 0; pixel < pixels.length; pixel += 4) { + pixels[pixel] = PRIVATE_PREVIEW_FOG_COLOR[0]; + pixels[pixel + 1] = PRIVATE_PREVIEW_FOG_COLOR[1]; + pixels[pixel + 2] = PRIVATE_PREVIEW_FOG_COLOR[2]; + pixels[pixel + 3] = 255; + } + let minimumQ = Number.POSITIVE_INFINITY; + let maximumQ = Number.NEGATIVE_INFINITY; + let minimumR = Number.POSITIVE_INFINITY; + let maximumR = Number.NEGATIVE_INFINITY; + let minimumProjectedX = Number.POSITIVE_INFINITY; + let maximumProjectedX = Number.NEGATIVE_INFINITY; + for (let index = 0; index < candidate.grid.cellCount; index += 1) { + const q = candidate.grid.q[index]!; + const r = candidate.grid.r[index]!; + const projectedX = q + r / 2; + minimumQ = Math.min(minimumQ, q); + maximumQ = Math.max(maximumQ, q); + minimumR = Math.min(minimumR, r); + maximumR = Math.max(maximumR, r); + minimumProjectedX = Math.min(minimumProjectedX, projectedX); + maximumProjectedX = Math.max(maximumProjectedX, projectedX); + } + const rSpan = maximumR - minimumR + 1; + const projectedXSpan = maximumProjectedX - minimumProjectedX + 1; + const previewCameraQ = (minimumQ + maximumQ) / 2; + const previewCameraR = (minimumR + maximumR) / 2; + const scale = Math.max( + 1, + Math.floor( + Math.min((width - 80) / projectedXSpan, (height - 120) / rSpan), + ), + ); + const cellPixelSpan = Math.max(1, scale); + const projectedWidth = Math.round( + (maximumProjectedX - minimumProjectedX) * scale, + ) + cellPixelSpan; + const projectedHeight = Math.round((rSpan - 1) * scale * 0.86) + + cellPixelSpan; + const previewOriginX = Math.max(0, Math.round((width - projectedWidth) / 2)); + const previewOriginY = Math.max( + 0, + Math.round((height - 44 - projectedHeight) / 2), + ); + const palette = [ + [74, 126, 72], + [80, 141, 187], + [74, 167, 205], + [73, 129, 185], + [92, 159, 204], + [119, 160, 83], + [80, 116, 72], + [178, 190, 204], + [188, 157, 92], + [151, 104, 64], + [99, 121, 82], + [201, 184, 110], + [173, 133, 74], + [143, 123, 88], + [110, 119, 132], + [100, 79, 109], + [65, 76, 88], + [184, 194, 207], + [129, 139, 98], + [108, 108, 112], + [36, 74, 126], + [59, 112, 161], + [77, 145, 187], + [124, 91, 142], + ] as const; + const regionPalette = [ + [87, 144, 84], + [117, 159, 195], + [205, 151, 76], + [84, 147, 127], + [103, 124, 164], + [151, 96, 80], + [92, 112, 82], + [116, 103, 126], + [85, 121, 142], + [116, 84, 135], + ] as const; + if (mode === 'regions') { + distanceToTopographicLand = + privatePreviewDistanceToTopographicLand(candidate); + } + const gateCells = new Set( + candidate.gates.flatMap(gate => [gate.firstCell, gate.secondCell]), + ); + for (let index = 0; index < candidate.grid.cellCount; index += 1) { + const x = + previewOriginX + + Math.round( + (candidate.grid.q[index]! + + candidate.grid.r[index]! / 2 + - minimumProjectedX) * scale, + ); + const y = + previewOriginY + + Math.round((candidate.grid.r[index]! - minimumR) * scale * 0.86); + let color: readonly [number, number, number] = [44, 49, 64]; + if (mode === 'silhouette') { + // Rivers and streams are features inside the continental footprint, not + // coastline cuts. Silhouette review therefore follows sea-level land. + color = + candidate.elevation[index]! > PRIVATE_PREVIEW_SEA_LEVEL + ? [142, 164, 105] + : [39, 76, 124]; + } else if (mode === 'hillshade') { + color = privatePreviewAtmosphericHillshade( + candidate, + index, + previewCameraQ, + previewCameraR, + ); + } else if (mode === 'biome') { + color = palette[candidate.biomeId[index]! % palette.length]!; + } else if (mode === 'hydrology') { + color = + candidate.waterRegime[index] === 0 ? [109, 118, 91] : [48, 132, 205]; + } else if (mode === 'regions') { + const regime = candidate.waterRegime[index]!; + if (regime === PRIVATE_PREVIEW_WATER_DRY) { + color = regionPalette[candidate.regionId[index]!]!; + } else if (regime === PRIVATE_PREVIEW_WATER_OCEAN) { + const distance = distanceToTopographicLand?.[index] ?? 0; + const band = Math.min(5, Math.floor(distance / 4)); + color = [54 - band * 5, 91 - band * 8, 132 - band * 10]; + } else if ( + regime === PRIVATE_PREVIEW_WATER_LAKE + || regime === PRIVATE_PREVIEW_WATER_SEA + ) { + color = [66, 126, 171]; + } else if ( + regime === PRIVATE_PREVIEW_WATER_RIVER + || regime === PRIVATE_PREVIEW_WATER_STREAM + ) { + color = [72, 143, 190]; + } else { + fail('GREATER_REALM_PRIVATE_PREVIEW_WATER_INVALID'); + } } else { - fail('GREATER_REALM_PRIVATE_PREVIEW_WATER_INVALID'); + color = gateCells.has(index) + ? [236, 194, 82] + : candidate.barrier[index] === 1 + ? [112, 104, 110] + : [73, 94, 78]; } - } else { - color = gateCells.has(index) - ? [236, 194, 82] - : candidate.barrier[index] === 1 - ? [112, 104, 110] - : [73, 94, 78]; - } - for (let offsetY = 0; offsetY < Math.max(1, scale); offsetY += 1) { - for (let offsetX = 0; offsetX < Math.max(1, scale); offsetX += 1) { - const targetX = x + offsetX; - const targetY = y + offsetY; - if (targetX < 0 || targetX >= width || targetY < 0 || targetY >= height) continue; - const pixel = (targetY * width + targetX) * 4; - pixels[pixel] = color[0]; - pixels[pixel + 1] = color[1]; - pixels[pixel + 2] = color[2]; - pixels[pixel + 3] = 255; + for (let offsetY = 0; offsetY < Math.max(1, scale); offsetY += 1) { + for (let offsetX = 0; offsetX < Math.max(1, scale); offsetX += 1) { + const targetX = x + offsetX; + const targetY = y + offsetY; + if (targetX < 0 || targetX >= width || targetY < 0 || targetY >= height) continue; + const pixel = (targetY * width + targetX) * 4; + pixels[pixel] = color[0]; + pixels[pixel + 1] = color[1]; + pixels[pixel + 2] = color[2]; + pixels[pixel + 3] = 255; + } } } - } - const sharpModule = await import('sharp'); - const watermarkLabel = mode === 'regions' - ? 'TOPOLOGY + OUTER-OCEAN PROXY · PRIVATE REVIEW · NOT RUNTIME FOG' - : `PRIVATE OWNER REVIEW — DO NOT DISTRIBUTE · ${mode.toUpperCase()}`; - watermark = Buffer.from( - `${watermarkLabel}`, - 'utf8', - ); - encoded = await sharpModule.default(pixels, { raw: { width, height, channels: 4 } }) + const sharpModule = await import('sharp'); + const watermarkLabel = + mode === 'regions' + ? 'TOPOLOGY + OUTER-OCEAN PROXY · PRIVATE REVIEW · NOT RUNTIME FOG' + : mode === 'hillshade' + ? 'TERRACED RELIEF + HEIGHT ATMOSPHERE · PRIVATE OWNER REVIEW' + : `PRIVATE OWNER REVIEW — DO NOT DISTRIBUTE · ${mode.toUpperCase()}`; + watermark = Buffer.from( + `${watermarkLabel}`, + 'utf8', + ); + encoded = await sharpModule + .default(pixels, { raw: { width, height, channels: 4 } }) .composite([{ input: watermark }]) // Preserve the explicit, fully opaque alpha channel. Palette encoding // silently collapses an all-255 alpha channel to RGB, which breaks the @@ -1394,14 +1484,120 @@ function clampPreview(value: number): number { return Math.max(28, Math.min(236, value)); } -export async function writeGreaterRealmPrivateCandidate(input: Readonly<{ - workspace: GreaterRealmPrivateWorkspace; - batchHandle: string; - candidateHandle: string; - sourceCommit: string; - candidate: GreaterRealmPrivateCandidate; - performance: GreaterRealmCandidatePerformance; -}>): Promise> { +function privatePreviewAtmosphericHillshade( + candidate: GreaterRealmPrivateCandidate, + cell: number, + cameraQ: number, + cameraR: number, +): readonly [number, number, number] { + const elevation = candidate.elevation[cell]!; + let sunwardElevation = 0; + let leewardElevation = 0; + let sunwardCount = 0; + let leewardCount = 0; + let neighborSum = 0; + let neighborCount = 0; + for (let direction = 0; direction < 6; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * 6 + direction]!; + if (neighbor < 0) continue; + const neighborElevation = candidate.elevation[neighbor]!; + neighborSum += neighborElevation; + neighborCount += 1; + if (direction === 4 || direction === 5) { + sunwardElevation += neighborElevation; + sunwardCount += 1; + } else if (direction === 1 || direction === 2) { + leewardElevation += neighborElevation; + leewardCount += 1; + } + } + const sunwardMean = + sunwardCount === 0 ? elevation : sunwardElevation / sunwardCount; + const leewardMean = + leewardCount === 0 ? elevation : leewardElevation / leewardCount; + const neighborMean = + neighborCount === 0 ? elevation : neighborSum / neighborCount; + const directionalLight = Math.max( + -0.24, + Math.min(0.24, (leewardMean - sunwardMean) / 8_000), + ); + const shelter = Math.max( + -0.16, + Math.min(0.12, (elevation - neighborMean) / 7_000), + ); + const illumination = 0.82 + directionalLight + shelter; + + let base: readonly [number, number, number]; + if (candidate.waterRegime[cell] !== PRIVATE_PREVIEW_WATER_DRY) { + const depth = Math.max(0, -elevation); + base = [ + Math.max(28, 54 - Math.floor(depth / 3_000)), + Math.max(60, 111 - Math.floor(depth / 2_500)), + Math.max(92, 158 - Math.floor(depth / 2_200)), + ]; + } else if (elevation >= 15_000) { + base = [190, 194, 187]; + } else if (elevation >= 8_000) { + base = [137, 131, 104]; + } else { + const moisture = candidate.moisture[cell]!; + base = + moisture >= 2_500 + ? [78, 125, 72] + : moisture <= -1_000 + ? [164, 145, 88] + : [110, 143, 79]; + } + const scene = Object.freeze( + base.map(channel => clampPreview(Math.round(channel * illumination))) as [ + number, + number, + number, + ], + ); + + const cameraDeltaQ = candidate.grid.q[cell]! - cameraQ; + const cameraDeltaR = candidate.grid.r[cell]! - cameraR; + const planarDistance = + Math.sqrt( + cameraDeltaQ ** 2 + cameraDeltaR ** 2 + cameraDeltaQ * cameraDeltaR, + ) * PRIVATE_PREVIEW_HORIZONTAL_SCALE; + const rayHeightDelta = elevation - PRIVATE_PREVIEW_CAMERA_HEIGHT; + const rayLength = Math.hypot(planarDistance, rayHeightDelta); + const extinction = integrateGreaterRealmHeightFog({ + originHeight: PRIVATE_PREVIEW_CAMERA_HEIGHT, + rayHeightDelta, + rayLength, + density: 0.000_034, + heightFalloff: 0.000_082, + }); + const inScattering = integrateGreaterRealmHeightFog({ + originHeight: PRIVATE_PREVIEW_CAMERA_HEIGHT, + rayHeightDelta, + rayLength, + density: 0.000_052, + heightFalloff: 0.000_11, + }); + return compositeGreaterRealmAtmosphere({ + scene, + // The private CPU preview has no runtime environment cubemap. This fixed + // muted sky tone is its deliberately non-authoritative blurred-sky proxy. + haze: PRIVATE_PREVIEW_SKY_HAZE, + extinction, + inScattering, + }); +} + +export async function writeGreaterRealmPrivateCandidate( + input: Readonly<{ + workspace: GreaterRealmPrivateWorkspace; + batchHandle: string; + candidateHandle: string; + sourceCommit: string; + candidate: GreaterRealmPrivateCandidate; + performance: GreaterRealmCandidatePerformance; + }>, +): Promise> { if ( !BATCH_HANDLE_PATTERN.test(input.batchHandle) || !CANDIDATE_HANDLE_PATTERN.test(input.candidateHandle) @@ -1591,6 +1787,7 @@ function verifyPrivateAtlasBinary(atlas: Buffer, expectedCellCount: number): voi ['rock-resistance', 5, 4], ['geomorphology-elevation', 5, 4], ['geomorphology-total-delta', 5, 4], + ['geomorphology-terrace-delta', 5, 4], ['geomorphology-glacial-delta', 5, 4], ['geomorphology-arid-delta', 5, 4], ['geomorphology-volcanic-delta', 5, 4], diff --git a/scripts/atlas/greater-realm-geomorphology.ts b/scripts/atlas/greater-realm-geomorphology.ts index 9ded170d..c7f1a17c 100644 --- a/scripts/atlas/greater-realm-geomorphology.ts +++ b/scripts/atlas/greater-realm-geomorphology.ts @@ -7,10 +7,14 @@ import { routeGreaterRealmSingleFlow, type GreaterRealmTerrainSeed, type IndexedAxialGrid, -} from './greater-realm-terrain'; +} from "./greater-realm-terrain"; +import { + shapeGreaterRealmTerraces, + type GreaterRealmTerraceMetrics, +} from "./greater-realm-terraces"; export const GREATER_REALM_GEOMORPHOLOGY_VERSION = - 'greater-realm-geomorphology-v2' as const; + "greater-realm-geomorphology-v3" as const; export const GREATER_REALM_COASTAL_CLASS = Object.freeze({ none: 0, @@ -64,6 +68,7 @@ export type GreaterRealmGeomorphologyMetrics = Readonly<{ volcanicAnchorCount: number; ridgeUpliftAlignmentBasisPoints: number; riverValleyAlignmentBasisPoints: number; + terraces: GreaterRealmTerraceMetrics; glacial: GreaterRealmGeomorphologyProcessMetrics; arid: GreaterRealmGeomorphologyProcessMetrics; coastal: GreaterRealmGeomorphologyProcessMetrics; @@ -74,6 +79,7 @@ export type GreaterRealmGeomorphologyResult = Readonly<{ temperature: Int32Array; moisture: Int32Array; totalDelta: Int32Array; + terraceDelta: Int32Array; glacialDelta: Int32Array; aridDelta: Int32Array; volcanicDelta: Int32Array; @@ -113,23 +119,26 @@ function clamp(value: number, minimum: number, maximum: number): number { function safeAdd(first: number, second: number): number { const value = first + second; - if (!Number.isSafeInteger(value)) fail('GREATER_REALM_GEOMORPHOLOGY_METRIC_OVERFLOW'); + if (!Number.isSafeInteger(value)) + fail("GREATER_REALM_GEOMORPHOLOGY_METRIC_OVERFLOW"); return value; } -function assertInputs(input: Readonly<{ - grid: IndexedAxialGrid; - elevation: Int32Array; - tectonicUplift: Int32Array; - rockResistance: Int32Array; - volcanicPotential: Int32Array; - legacyReserveCell: Uint8Array; - climate?: GreaterRealmGeomorphologyClimate; - seaLevel: number; -}>): void { +function assertInputs( + input: Readonly<{ + grid: IndexedAxialGrid; + elevation: Int32Array; + tectonicUplift: Int32Array; + rockResistance: Int32Array; + volcanicPotential: Int32Array; + legacyReserveCell: Uint8Array; + climate?: GreaterRealmGeomorphologyClimate; + seaLevel: number; + }>, +): void { const { grid } = input; if (!Number.isSafeInteger(input.seaLevel)) { - fail('GREATER_REALM_GEOMORPHOLOGY_SEA_LEVEL_INVALID'); + fail("GREATER_REALM_GEOMORPHOLOGY_SEA_LEVEL_INVALID"); } for (const field of [ input.elevation, @@ -141,19 +150,20 @@ function assertInputs(input: Readonly<{ input.climate?.moisture, ]) { if (field !== undefined && field.length !== grid.cellCount) { - fail('GREATER_REALM_GEOMORPHOLOGY_INPUT_LENGTH_INVALID'); + fail("GREATER_REALM_GEOMORPHOLOGY_INPUT_LENGTH_INVALID"); } } for (let cell = 0; cell < grid.cellCount; cell += 1) { if (input.legacyReserveCell[cell]! > 1) { - fail('GREATER_REALM_GEOMORPHOLOGY_RESERVE_MASK_INVALID'); + fail("GREATER_REALM_GEOMORPHOLOGY_RESERVE_MASK_INVALID"); } if ( - input.rockResistance[cell]! < 0 - || input.rockResistance[cell]! > 20_000 - || input.volcanicPotential[cell]! < 0 - || input.volcanicPotential[cell]! > 10_000 - ) fail('GREATER_REALM_GEOMORPHOLOGY_GEOLOGY_FIELD_INVALID'); + input.rockResistance[cell]! < 0 || + input.rockResistance[cell]! > 20_000 || + input.volcanicPotential[cell]! < 0 || + input.volcanicPotential[cell]! > 10_000 + ) + fail("GREATER_REALM_GEOMORPHOLOGY_GEOLOGY_FIELD_INVALID"); } } @@ -168,42 +178,58 @@ function axialDistance( return Math.max(Math.abs(q), Math.abs(r), Math.abs(-q - r)); } -function maximumNeighborDrop(grid: IndexedAxialGrid, elevation: Int32Array): Uint16Array { +function maximumNeighborDrop( + grid: IndexedAxialGrid, + elevation: Int32Array, +): Uint16Array { const slope = new Uint16Array(grid.cellCount); for (let cell = 0; cell < grid.cellCount; cell += 1) { let maximum = 0; for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; if (neighbor < 0) continue; - maximum = Math.max(maximum, Math.abs(elevation[cell]! - elevation[neighbor]!)); + maximum = Math.max( + maximum, + Math.abs(elevation[cell]! - elevation[neighbor]!), + ); } slope[cell] = clamp(maximum, 0, 0xffff); } return slope; } -function maskDistances(grid: IndexedAxialGrid, starts: Uint8Array): Uint16Array { +function maskDistances( + grid: IndexedAxialGrid, + starts: Uint8Array, +): Uint16Array { const distance = new Uint16Array(grid.cellCount); distance.fill(DISTANCE_UNREACHABLE); const queue = new Uint32Array(grid.cellCount); - let head = 0; - let tail = 0; - for (let cell = 0; cell < grid.cellCount; cell += 1) { - if (starts[cell] !== 1) continue; - distance[cell] = 0; - queue[tail++] = cell; - } - while (head < tail) { - const cell = queue[head++]!; - const nextDistance = distance[cell]! + 1; - for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { - const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; - if (neighbor < 0 || distance[neighbor]! <= nextDistance) continue; - distance[neighbor] = nextDistance; - queue[tail++] = neighbor; + let completed = false; + try { + let head = 0; + let tail = 0; + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (starts[cell] !== 1) continue; + distance[cell] = 0; + queue[tail++] = cell; + } + while (head < tail) { + const cell = queue[head++]!; + const nextDistance = distance[cell]! + 1; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || distance[neighbor]! <= nextDistance) continue; + distance[neighbor] = nextDistance; + queue[tail++] = neighbor; + } } + completed = true; + return distance; + } finally { + queue.fill(0); + if (!completed) distance.fill(0); } - return distance; } function componentFilteredMask( @@ -214,36 +240,58 @@ function componentFilteredMask( const mask = new Uint8Array(grid.cellCount); const seen = new Uint8Array(grid.cellCount); const queue = new Uint32Array(grid.cellCount); - let count = 0; - let minimum = Number.POSITIVE_INFINITY; - for (let start = 0; start < grid.cellCount; start += 1) { - if (candidates[start] !== 1 || seen[start] === 1) continue; - let head = 0; - let tail = 0; - queue[tail++] = start; - seen[start] = 1; - while (head < tail) { - const cell = queue[head++]!; - for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { - const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; - if (neighbor < 0 || candidates[neighbor] !== 1 || seen[neighbor] === 1) continue; - seen[neighbor] = 1; - queue[tail++] = neighbor; + let completed = false; + try { + let count = 0; + let minimum = Number.POSITIVE_INFINITY; + for (let start = 0; start < grid.cellCount; start += 1) { + if (candidates[start] !== 1 || seen[start] === 1) continue; + let head = 0; + let tail = 0; + queue[tail++] = start; + seen[start] = 1; + while (head < tail) { + const cell = queue[head++]!; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 || + candidates[neighbor] !== 1 || + seen[neighbor] === 1 + ) + continue; + seen[neighbor] = 1; + queue[tail++] = neighbor; + } } + if (tail < minimumSize) continue; + count += 1; + minimum = Math.min(minimum, tail); + for (let index = 0; index < tail; index += 1) mask[queue[index]!] = 1; } - if (tail < minimumSize) continue; - count += 1; - minimum = Math.min(minimum, tail); - for (let index = 0; index < tail; index += 1) mask[queue[index]!] = 1; + const result = Object.freeze({ + mask, + metrics: Object.freeze({ count, minimum: count === 0 ? 0 : minimum }), + }); + completed = true; + return result; + } finally { + seen.fill(0); + queue.fill(0); + if (!completed) mask.fill(0); } - return Object.freeze({ - mask, - metrics: Object.freeze({ count, minimum: count === 0 ? 0 : minimum }), - }); } -function componentMetrics(grid: IndexedAxialGrid, mask: Uint8Array): ComponentMetrics { - return componentFilteredMask(grid, mask, 1).metrics; +function componentMetrics( + grid: IndexedAxialGrid, + mask: Uint8Array, +): ComponentMetrics { + const measured = componentFilteredMask(grid, mask, 1); + try { + return measured.metrics; + } finally { + measured.mask.fill(0); + } } function preliminaryHydrology( @@ -253,22 +301,45 @@ function preliminaryHydrology( ): PreliminaryHydrology { const outlets: number[] = []; const contribution = new Uint32Array(grid.cellCount); - for (let cell = 0; cell < grid.cellCount; cell += 1) { - if (elevation[cell]! <= seaLevel) outlets.push(cell); - else contribution[cell] = 1; - } - if (outlets.length === 0) fail('GREATER_REALM_GEOMORPHOLOGY_OCEAN_MISSING'); - const flood = priorityFloodGreaterRealmHexGrid(grid, elevation, outlets); - const routing = routeGreaterRealmSingleFlow(grid, flood); - return Object.freeze({ - receiver: routing.receiver, - accumulation: accumulateGreaterRealmSingleFlow( + let flood: ReturnType | undefined; + let routing: ReturnType | undefined; + let accumulation: BigUint64Array | undefined; + let completed = false; + try { + for (let cell = 0; cell < grid.cellCount; cell += 1) { + if (elevation[cell]! <= seaLevel) outlets.push(cell); + else contribution[cell] = 1; + } + if (outlets.length === 0) fail("GREATER_REALM_GEOMORPHOLOGY_OCEAN_MISSING"); + flood = priorityFloodGreaterRealmHexGrid(grid, elevation, outlets); + routing = routeGreaterRealmSingleFlow(grid, flood); + accumulation = accumulateGreaterRealmSingleFlow( grid, flood.filledElevation, routing, contribution, - ), - }); + ); + const result = Object.freeze({ + receiver: routing.receiver, + accumulation, + }); + completed = true; + return result; + } finally { + contribution.fill(0); + flood?.filledElevation.fill(0); + flood?.floodParent.fill(0); + flood?.order.fill(0); + flood?.rank.fill(0); + flood?.outlets.fill(0); + routing?.order.fill(0); + routing?.rank.fill(0); + routing?.outlets.fill(0); + if (!completed) { + routing?.receiver.fill(0); + accumulation?.fill(0n); + } + } } function accumulationMagnitude(value: bigint): number { @@ -277,53 +348,106 @@ function accumulationMagnitude(value: bigint): number { return magnitude; } -function deriveClimate(input: Readonly<{ - grid: IndexedAxialGrid; - candidateSeed: GreaterRealmTerrainSeed; - elevation: Int32Array; - seaLevel: number; - coastDistance: Uint16Array; - slope: Uint16Array; - accumulation: BigUint64Array; -}>): GreaterRealmGeomorphologyClimate { - const { grid, candidateSeed, elevation, seaLevel, coastDistance, slope, accumulation } = input; - const equatorOffset = ( - greaterRealmCounterRandomU32( +function deriveClimate( + input: Readonly<{ + grid: IndexedAxialGrid; + candidateSeed: GreaterRealmTerrainSeed; + elevation: Int32Array; + seaLevel: number; + coastDistance: Uint16Array; + slope: Uint16Array; + accumulation: BigUint64Array; + }>, +): GreaterRealmGeomorphologyClimate { + const { + grid, + candidateSeed, + elevation, + seaLevel, + coastDistance, + slope, + accumulation, + } = input; + const equatorOffset = + (greaterRealmCounterRandomU32( candidateSeed, - greaterRealmTerrainChannelId('geomorphology-equator-offset'), + greaterRealmTerrainChannelId("geomorphology-equator-offset"), 0, 0, - ) % 41 - ) - 20; - const temperatureNoise = createGreaterRealmMultiscaleIntegerField(grid, candidateSeed, [ - { channel: 'geomorphology-temperature-macro', amplitude: 1_100, smoothingPasses: 12, selfWeight: 3 }, - ]); - const moistureNoise = createGreaterRealmMultiscaleIntegerField(grid, candidateSeed, [ - { channel: 'geomorphology-moisture-macro', amplitude: 4_800, smoothingPasses: 16, selfWeight: 3 }, - { channel: 'geomorphology-moisture-meso', amplitude: 1_400, smoothingPasses: 4, selfWeight: 2 }, - ]); - const temperature = new Int32Array(grid.cellCount); - const moisture = new Int32Array(grid.cellCount); - for (let cell = 0; cell < grid.cellCount; cell += 1) { - temperature[cell] = clamp( - 8_500 - - Math.abs(grid.r[cell]! - equatorOffset) * 26 - - Math.floor(Math.max(0, elevation[cell]! - seaLevel) / 5) - + temperatureNoise[cell]!, - -8_000, - 12_000, + ) % + 41) - + 20; + let temperatureNoise: Int32Array | undefined; + let moistureNoise: Int32Array | undefined; + let temperature: Int32Array | undefined; + let moisture: Int32Array | undefined; + let completed = false; + try { + temperatureNoise = createGreaterRealmMultiscaleIntegerField( + grid, + candidateSeed, + [ + { + channel: "geomorphology-temperature-macro", + amplitude: 1_100, + smoothingPasses: 12, + selfWeight: 3, + }, + ], ); - const drainage = Math.min(4_200, accumulationMagnitude(accumulation[cell]!) * 360); - moisture[cell] = clamp( - moistureNoise[cell]! - + Math.max(0, 1_500 - coastDistance[cell]! * 90) - + drainage - - Math.floor(slope[cell]! / 4), - -10_000, - 16_000, + moistureNoise = createGreaterRealmMultiscaleIntegerField( + grid, + candidateSeed, + [ + { + channel: "geomorphology-moisture-macro", + amplitude: 4_800, + smoothingPasses: 16, + selfWeight: 3, + }, + { + channel: "geomorphology-moisture-meso", + amplitude: 1_400, + smoothingPasses: 4, + selfWeight: 2, + }, + ], ); + temperature = new Int32Array(grid.cellCount); + moisture = new Int32Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + temperature[cell] = clamp( + 8_500 - + Math.abs(grid.r[cell]! - equatorOffset) * 26 - + Math.floor(Math.max(0, elevation[cell]! - seaLevel) / 5) + + temperatureNoise[cell]!, + -8_000, + 12_000, + ); + const drainage = Math.min( + 4_200, + accumulationMagnitude(accumulation[cell]!) * 360, + ); + moisture[cell] = clamp( + moistureNoise[cell]! + + Math.max(0, 1_500 - coastDistance[cell]! * 90) + + drainage - + Math.floor(slope[cell]! / 4), + -10_000, + 16_000, + ); + } + const result = Object.freeze({ temperature, moisture }); + completed = true; + return result; + } finally { + temperatureNoise?.fill(0); + moistureNoise?.fill(0); + if (!completed) { + temperature?.fill(0); + moisture?.fill(0); + } } - return Object.freeze({ temperature, moisture }); } function countMask(mask: Uint8Array): number { @@ -356,20 +480,38 @@ function downstreamDepositionTarget( return -1; } -function applyConservedErosion(input: Readonly<{ - erosion: Uint16Array; - delta: Int32Array; - receiver: Int32Array; - sourceMask: Uint8Array; - reserveMask: Uint8Array; -}>): MaterialBudget { +function applyConservedErosion( + input: Readonly<{ + erosion: Uint16Array; + delta: Int32Array; + elevation: Int32Array; + seaLevel: number; + baselineDelta?: Int32Array; + receiver: Int32Array; + sourceMask: Uint8Array; + reserveMask: Uint8Array; + }>, +): MaterialBudget { let eroded = 0; let deposited = 0; let exported = 0; for (let cell = 0; cell < input.erosion.length; cell += 1) { - const amount = input.erosion[cell]!; + const requested = input.erosion[cell]!; + const baselineDelta = input.baselineDelta?.[cell] ?? 0; + const amount = Math.min( + requested, + Math.max( + 0, + input.elevation[cell]! + + baselineDelta + + input.delta[cell]! - + input.seaLevel - + 1, + ), + ); if (amount === 0) continue; - if (input.reserveMask[cell] === 1) fail('GREATER_REALM_GEOMORPHOLOGY_PROTECTED_EDIT'); + if (input.reserveMask[cell] === 1) + fail("GREATER_REALM_GEOMORPHOLOGY_PROTECTED_EDIT"); input.delta[cell] -= amount; eroded = safeAdd(eroded, amount); const target = downstreamDepositionTarget( @@ -382,13 +524,25 @@ function applyConservedErosion(input: Readonly<{ exported = safeAdd(exported, amount); continue; } - const capacity = Math.max(0, MAX_PROCESS_CELL_DELTA - input.delta[target]!); + const targetBaselineDelta = input.baselineDelta?.[target] ?? 0; + const signCapacity = + input.elevation[target]! <= input.seaLevel + ? input.seaLevel - + input.elevation[target]! - + targetBaselineDelta - + input.delta[target]! + : MAX_PROCESS_CELL_DELTA; + const capacity = Math.max( + 0, + Math.min(MAX_PROCESS_CELL_DELTA - input.delta[target]!, signCapacity), + ); const placed = Math.min(amount, capacity); input.delta[target] += placed; deposited = safeAdd(deposited, placed); exported = safeAdd(exported, amount - placed); } - if (eroded !== deposited + exported) fail('GREATER_REALM_GEOMORPHOLOGY_MATERIAL_BUDGET_INVALID'); + if (eroded !== deposited + exported) + fail("GREATER_REALM_GEOMORPHOLOGY_MATERIAL_BUDGET_INVALID"); return Object.freeze({ eroded, deposited, exported }); } @@ -396,84 +550,120 @@ function addErosion(erosion: Uint16Array, cell: number, amount: number): void { erosion[cell] = clamp(erosion[cell]! + amount, 0, 900); } -function glacialProcess(input: Readonly<{ - grid: IndexedAxialGrid; - elevation: Int32Array; - seaLevel: number; - slope: Uint16Array; - temperature: Int32Array; - accumulation: BigUint64Array; - receiver: Int32Array; - reserveMask: Uint8Array; -}>): Readonly<{ +function glacialProcess( + input: Readonly<{ + grid: IndexedAxialGrid; + elevation: Int32Array; + seaLevel: number; + slope: Uint16Array; + temperature: Int32Array; + accumulation: BigUint64Array; + receiver: Int32Array; + reserveMask: Uint8Array; + }>, +): Readonly<{ mask: Uint8Array; delta: Int32Array; components: ComponentMetrics; budget: MaterialBudget; }> { const candidates = new Uint8Array(input.grid.cellCount); - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if ( - input.reserveMask[cell] === 0 - && input.elevation[cell]! > input.seaLevel + 4_500 - && input.temperature[cell]! <= 2_000 - && (input.slope[cell]! >= 450 || input.accumulation[cell]! >= 6n) - ) candidates[cell] = 1; - } - const coherent = componentFilteredMask(input.grid, candidates, MIN_GLACIAL_SYSTEM_CELLS); - const erosion = new Uint16Array(input.grid.cellCount); - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if (coherent.mask[cell] !== 1) continue; - const amount = clamp( - 50 + accumulationMagnitude(input.accumulation[cell]!) * 18 - + Math.floor(input.slope[cell]! / 20), - 50, - 300, + let coherent: ReturnType | undefined; + let erosion: Uint16Array | undefined; + let delta: Int32Array | undefined; + let completed = false; + try { + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + input.reserveMask[cell] === 0 && + input.elevation[cell]! > input.seaLevel + 4_500 && + input.temperature[cell]! <= 2_000 && + (input.slope[cell]! >= 450 || input.accumulation[cell]! >= 6n) + ) + candidates[cell] = 1; + } + coherent = componentFilteredMask( + input.grid, + candidates, + MIN_GLACIAL_SYSTEM_CELLS, ); - addErosion(erosion, cell, amount); - const downstream = input.receiver[cell]!; - if (downstream < 0) continue; - let flowDirection = -1; - for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { - if (input.grid.neighbors[cell * NEIGHBOR_COUNT + direction] === downstream) { - flowDirection = direction; - break; + erosion = new Uint16Array(input.grid.cellCount); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (coherent.mask[cell] !== 1) continue; + const amount = clamp( + 50 + + accumulationMagnitude(input.accumulation[cell]!) * 18 + + Math.floor(input.slope[cell]! / 20), + 50, + 300, + ); + addErosion(erosion, cell, amount); + const downstream = input.receiver[cell]!; + if (downstream < 0) continue; + let flowDirection = -1; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + if ( + input.grid.neighbors[cell * NEIGHBOR_COUNT + direction] === downstream + ) { + flowDirection = direction; + break; + } } - } - if (flowDirection < 0) continue; - for (const offset of [2, 4] as const) { - const lateral = input.grid.neighbors[ - cell * NEIGHBOR_COUNT + ((flowDirection + offset) % NEIGHBOR_COUNT) - ]!; - if (lateral >= 0 && coherent.mask[lateral] === 1) { - addErosion(erosion, lateral, Math.max(20, Math.floor(amount / 3))); + if (flowDirection < 0) continue; + for (const offset of [2, 4] as const) { + const lateral = + input.grid.neighbors[ + cell * NEIGHBOR_COUNT + ((flowDirection + offset) % NEIGHBOR_COUNT) + ]!; + if (lateral >= 0 && coherent.mask[lateral] === 1) { + addErosion(erosion, lateral, Math.max(20, Math.floor(amount / 3))); + } } } + delta = new Int32Array(input.grid.cellCount); + const budget = applyConservedErosion({ + erosion, + delta, + elevation: input.elevation, + seaLevel: input.seaLevel, + receiver: input.receiver, + sourceMask: coherent.mask, + reserveMask: input.reserveMask, + }); + const result = Object.freeze({ + mask: coherent.mask, + delta, + components: coherent.metrics, + budget, + }); + completed = true; + return result; + } finally { + candidates.fill(0); + erosion?.fill(0); + if (!completed) { + coherent?.mask.fill(0); + delta?.fill(0); + } } - const delta = new Int32Array(input.grid.cellCount); - const budget = applyConservedErosion({ - erosion, - delta, - receiver: input.receiver, - sourceMask: coherent.mask, - reserveMask: input.reserveMask, - }); - return Object.freeze({ mask: coherent.mask, delta, components: coherent.metrics, budget }); } -function aridProcess(input: Readonly<{ - grid: IndexedAxialGrid; - candidateSeed: GreaterRealmTerrainSeed; - elevation: Int32Array; - seaLevel: number; - slope: Uint16Array; - temperature: Int32Array; - moisture: Int32Array; - accumulation: BigUint64Array; - receiver: Int32Array; - rockResistance: Int32Array; - reserveMask: Uint8Array; -}>): Readonly<{ +function aridProcess( + input: Readonly<{ + grid: IndexedAxialGrid; + candidateSeed: GreaterRealmTerrainSeed; + elevation: Int32Array; + seaLevel: number; + slope: Uint16Array; + temperature: Int32Array; + moisture: Int32Array; + accumulation: BigUint64Array; + receiver: Int32Array; + rockResistance: Int32Array; + baselineDelta: Int32Array; + reserveMask: Uint8Array; + }>, +): Readonly<{ mask: Uint8Array; delta: Int32Array; components: ComponentMetrics; @@ -481,186 +671,278 @@ function aridProcess(input: Readonly<{ aeolianMovedMaterialUnits: number; }> { const candidates = new Uint8Array(input.grid.cellCount); - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if ( - input.reserveMask[cell] === 0 - && input.elevation[cell]! > input.seaLevel - && input.temperature[cell]! >= 5_500 - && input.moisture[cell]! <= -1_000 - ) candidates[cell] = 1; - } - const coherent = componentFilteredMask(input.grid, candidates, MIN_ARID_SYSTEM_CELLS); - const erosion = new Uint16Array(input.grid.cellCount); - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if (coherent.mask[cell] !== 1) continue; - const discharge = accumulationMagnitude(input.accumulation[cell]!); - const differentialWeathering = Math.max(0, 7_000 - input.rockResistance[cell]!); - if (input.accumulation[cell]! >= 4n || input.slope[cell]! >= 320 || differentialWeathering > 1_500) { - addErosion(erosion, cell, clamp( - 25 + discharge * 16 + Math.floor(input.slope[cell]! / 28) - + Math.floor(differentialWeathering / 90), - 25, - 240, - )); + let coherent: ReturnType | undefined; + let erosion: Uint16Array | undefined; + let delta: Int32Array | undefined; + let completed = false; + try { + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + input.reserveMask[cell] === 0 && + input.elevation[cell]! > input.seaLevel && + input.temperature[cell]! >= 5_500 && + input.moisture[cell]! <= -1_000 + ) + candidates[cell] = 1; } - } - const delta = new Int32Array(input.grid.cellCount); - const budget = applyConservedErosion({ - erosion, - delta, - receiver: input.receiver, - sourceMask: coherent.mask, - reserveMask: input.reserveMask, - }); - const duneChannel = greaterRealmTerrainChannelId('geomorphology-aeolian-dunes'); - const windDirection = greaterRealmCounterRandomU32( - input.candidateSeed, - duneChannel, - 0, - 0, - ) % NEIGHBOR_COUNT; - let aeolianMovedMaterialUnits = 0; - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if ( - coherent.mask[cell] !== 1 - || input.slope[cell]! > 550 - || input.accumulation[cell]! > 6n - ) continue; - const random = greaterRealmCounterRandomU32( - input.candidateSeed, - duneChannel, - input.grid.q[cell]!, - input.grid.r[cell]!, - 1, + coherent = componentFilteredMask( + input.grid, + candidates, + MIN_ARID_SYSTEM_CELLS, ); - if (random % 5 !== 0) continue; - const target = input.grid.neighbors[cell * NEIGHBOR_COUNT + windDirection]!; - if (target <= cell || target < 0 || coherent.mask[target] !== 1) continue; - const wanted = 30 + (random % 51); - const sourceCapacity = MAX_PROCESS_CELL_DELTA + delta[cell]!; - const targetCapacity = MAX_PROCESS_CELL_DELTA - delta[target]!; - const moved = Math.max(0, Math.min(wanted, sourceCapacity, targetCapacity)); - if (moved === 0) continue; - delta[cell] -= moved; - delta[target] += moved; - aeolianMovedMaterialUnits = safeAdd(aeolianMovedMaterialUnits, moved); + erosion = new Uint16Array(input.grid.cellCount); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (coherent.mask[cell] !== 1) continue; + const discharge = accumulationMagnitude(input.accumulation[cell]!); + const differentialWeathering = Math.max( + 0, + 7_000 - input.rockResistance[cell]!, + ); + if ( + input.accumulation[cell]! >= 4n || + input.slope[cell]! >= 320 || + differentialWeathering > 1_500 + ) { + addErosion( + erosion, + cell, + clamp( + 25 + + discharge * 16 + + Math.floor(input.slope[cell]! / 28) + + Math.floor(differentialWeathering / 90), + 25, + 240, + ), + ); + } + } + delta = new Int32Array(input.grid.cellCount); + const budget = applyConservedErosion({ + erosion, + delta, + elevation: input.elevation, + seaLevel: input.seaLevel, + baselineDelta: input.baselineDelta, + receiver: input.receiver, + sourceMask: coherent.mask, + reserveMask: input.reserveMask, + }); + const duneChannel = greaterRealmTerrainChannelId( + "geomorphology-aeolian-dunes", + ); + const windDirection = + greaterRealmCounterRandomU32(input.candidateSeed, duneChannel, 0, 0) % + NEIGHBOR_COUNT; + let aeolianMovedMaterialUnits = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + coherent.mask[cell] !== 1 || + input.slope[cell]! > 550 || + input.accumulation[cell]! > 6n + ) + continue; + const random = greaterRealmCounterRandomU32( + input.candidateSeed, + duneChannel, + input.grid.q[cell]!, + input.grid.r[cell]!, + 1, + ); + if (random % 5 !== 0) continue; + const target = + input.grid.neighbors[cell * NEIGHBOR_COUNT + windDirection]!; + if (target <= cell || target < 0 || coherent.mask[target] !== 1) continue; + const wanted = 30 + (random % 51); + const sourceCapacity = Math.min( + MAX_PROCESS_CELL_DELTA + delta[cell]!, + input.elevation[cell]! + + input.baselineDelta[cell]! + + delta[cell]! - + input.seaLevel - + 1, + ); + const targetCapacity = MAX_PROCESS_CELL_DELTA - delta[target]!; + const moved = Math.max( + 0, + Math.min(wanted, sourceCapacity, targetCapacity), + ); + if (moved === 0) continue; + delta[cell] -= moved; + delta[target] += moved; + aeolianMovedMaterialUnits = safeAdd(aeolianMovedMaterialUnits, moved); + } + const result = Object.freeze({ + mask: coherent.mask, + delta, + components: coherent.metrics, + budget, + aeolianMovedMaterialUnits, + }); + completed = true; + return result; + } finally { + candidates.fill(0); + erosion?.fill(0); + if (!completed) { + coherent?.mask.fill(0); + delta?.fill(0); + } } - return Object.freeze({ - mask: coherent.mask, - delta, - components: coherent.metrics, - budget, - aeolianMovedMaterialUnits, - }); } -function volcanicProcess(input: Readonly<{ - grid: IndexedAxialGrid; - candidateSeed: GreaterRealmTerrainSeed; - elevation: Int32Array; - seaLevel: number; - tectonicUplift: Int32Array; - volcanicPotential: Int32Array; - reserveMask: Uint8Array; -}>): Readonly<{ +function volcanicProcess( + input: Readonly<{ + grid: IndexedAxialGrid; + candidateSeed: GreaterRealmTerrainSeed; + elevation: Int32Array; + seaLevel: number; + tectonicUplift: Int32Array; + volcanicPotential: Int32Array; + reserveMask: Uint8Array; + }>, +): Readonly<{ mask: Uint8Array; anchorMask: Uint8Array; delta: Int32Array; anchorCount: number; upliftUnits: number; }> { - const channel = greaterRealmTerrainChannelId('geomorphology-volcanic-centers'); + const channel = greaterRealmTerrainChannelId( + "geomorphology-volcanic-centers", + ); const candidates: Array> = []; for (let cell = 0; cell < input.grid.cellCount; cell += 1) { if ( - input.reserveMask[cell] === 1 - || input.elevation[cell]! <= input.seaLevel - || input.volcanicPotential[cell]! < 7_000 - || input.tectonicUplift[cell]! < 2_500 - ) continue; + input.reserveMask[cell] === 1 || + input.elevation[cell]! <= input.seaLevel || + input.volcanicPotential[cell]! < 7_000 || + input.tectonicUplift[cell]! < 2_500 + ) + continue; // Volcanic potential is intentionally constant across each tectonic // domain. Treating that plateau as a local-maximum field reduced an // otherwise broad compatible belt to one arbitrary cell-index minimum, // which could itself sit outside eligible land and yield no anchors. // Rank every compatible cell here; the bounded greedy spacing pass below // remains the authority for selecting distinct volcanic centres. - candidates.push(Object.freeze({ - cell, - score: input.volcanicPotential[cell]! * 4 - + input.tectonicUplift[cell]! - + (greaterRealmCounterRandomU32( - input.candidateSeed, - channel, - input.grid.q[cell]!, - input.grid.r[cell]!, - ) % 501), - })); + candidates.push( + Object.freeze({ + cell, + score: + input.volcanicPotential[cell]! * 4 + + input.tectonicUplift[cell]! + + (greaterRealmCounterRandomU32( + input.candidateSeed, + channel, + input.grid.q[cell]!, + input.grid.r[cell]!, + ) % + 501), + }), + ); } - candidates.sort((first, second) => second.score - first.score || first.cell - second.cell); - const maximumAnchors = clamp(Math.floor(input.grid.cellCount / 25_000) + 2, 2, 8); + candidates.sort( + (first, second) => second.score - first.score || first.cell - second.cell, + ); + const maximumAnchors = clamp( + Math.floor(input.grid.cellCount / 25_000) + 2, + 2, + 8, + ); const anchors: number[] = []; for (const candidate of candidates) { - if (anchors.some(anchor => axialDistance( - input.grid.q[candidate.cell]!, - input.grid.r[candidate.cell]!, - input.grid.q[anchor]!, - input.grid.r[anchor]!, - ) < 14)) continue; + if ( + anchors.some( + (anchor) => + axialDistance( + input.grid.q[candidate.cell]!, + input.grid.r[candidate.cell]!, + input.grid.q[anchor]!, + input.grid.r[anchor]!, + ) < 14, + ) + ) + continue; anchors.push(candidate.cell); if (anchors.length >= maximumAnchors) break; } - const delta = new Int32Array(input.grid.cellCount); - const anchorMask = new Uint8Array(input.grid.cellCount); - for (const anchor of anchors) { - anchorMask[anchor] = 1; - const random = greaterRealmCounterRandomU32( - input.candidateSeed, - channel, - input.grid.q[anchor]!, - input.grid.r[anchor]!, - 1, - ); - const radius = 4 + (random % 4); - const peak = 800 + (random % 801); - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if (input.reserveMask[cell] === 1 || input.elevation[cell]! <= input.seaLevel) continue; - const distance = axialDistance( - input.grid.q[cell]!, - input.grid.r[cell]!, + let delta: Int32Array | undefined; + let anchorMask: Uint8Array | undefined; + let mask: Uint8Array | undefined; + let completed = false; + try { + delta = new Int32Array(input.grid.cellCount); + anchorMask = new Uint8Array(input.grid.cellCount); + for (const anchor of anchors) { + anchorMask[anchor] = 1; + const random = greaterRealmCounterRandomU32( + input.candidateSeed, + channel, input.grid.q[anchor]!, input.grid.r[anchor]!, + 1, ); - if (distance > radius) continue; - let uplift = Math.floor((peak * (radius - distance + 1)) / (radius + 1)); - if (distance === 0) uplift = Math.max(80, Math.floor(uplift / 3)); - else if (distance === 1) uplift = Math.max(60, Math.floor(uplift / 2)); - delta[cell] = Math.min(MAX_PROCESS_CELL_DELTA, delta[cell]! + uplift); + const radius = 4 + (random % 4); + const peak = 800 + (random % 801); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + input.reserveMask[cell] === 1 || + input.elevation[cell]! <= input.seaLevel + ) + continue; + const distance = axialDistance( + input.grid.q[cell]!, + input.grid.r[cell]!, + input.grid.q[anchor]!, + input.grid.r[anchor]!, + ); + if (distance > radius) continue; + let uplift = Math.floor( + (peak * (radius - distance + 1)) / (radius + 1), + ); + if (distance === 0) uplift = Math.max(80, Math.floor(uplift / 3)); + else if (distance === 1) uplift = Math.max(60, Math.floor(uplift / 2)); + delta[cell] = Math.min(MAX_PROCESS_CELL_DELTA, delta[cell]! + uplift); + } + } + mask = changedMask(delta); + let upliftUnits = 0; + for (const value of delta) + upliftUnits = safeAdd(upliftUnits, Math.max(0, value)); + const result = Object.freeze({ + mask, + anchorMask, + delta, + anchorCount: anchors.length, + upliftUnits, + }); + completed = true; + return result; + } finally { + if (!completed) { + delta?.fill(0); + anchorMask?.fill(0); + mask?.fill(0); } } - const mask = changedMask(delta); - let upliftUnits = 0; - for (const value of delta) upliftUnits = safeAdd(upliftUnits, Math.max(0, value)); - return Object.freeze({ - mask, - anchorMask, - delta, - anchorCount: anchors.length, - upliftUnits, - }); } -function coastalProcess(input: Readonly<{ - grid: IndexedAxialGrid; - elevation: Int32Array; - seaLevel: number; - slope: Uint16Array; - temperature: Int32Array; - accumulation: BigUint64Array; - tectonicUplift: Int32Array; - rockResistance: Int32Array; - glacialMask: Uint8Array; - reserveMask: Uint8Array; -}>): Readonly<{ +function coastalProcess( + input: Readonly<{ + grid: IndexedAxialGrid; + elevation: Int32Array; + seaLevel: number; + slope: Uint16Array; + temperature: Int32Array; + accumulation: BigUint64Array; + tectonicUplift: Int32Array; + rockResistance: Int32Array; + glacialMask: Uint8Array; + priorDeltas: readonly Int32Array[]; + reserveMask: Uint8Array; + }>, +): Readonly<{ mask: Uint8Array; coastalClass: Uint8Array; delta: Int32Array; @@ -673,69 +955,129 @@ function coastalProcess(input: Readonly<{ const sourceMask = new Uint8Array(input.grid.cellCount); const coastalClass = new Uint8Array(input.grid.cellCount); const delta = new Int32Array(input.grid.cellCount); - let eroded = 0; - let deposited = 0; - let exported = 0; - const classes = new Set(); - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if (input.reserveMask[cell] === 1 || input.elevation[cell]! <= input.seaLevel) continue; - const seaNeighbors: number[] = []; - for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { - const neighbor = input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + let mask: Uint8Array | undefined; + let completed = false; + try { + let eroded = 0; + let deposited = 0; + let exported = 0; + const classes = new Set(); + const priorDeltaAt = (cell: number): number => + input.priorDeltas.reduce( + (total, values) => safeAdd(total, values[cell]!), + 0, + ); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { if ( - neighbor >= 0 - && input.reserveMask[neighbor] === 0 - && input.elevation[neighbor]! <= input.seaLevel - ) seaNeighbors.push(neighbor); + input.reserveMask[cell] === 1 || + input.elevation[cell]! <= input.seaLevel + ) + continue; + const seaNeighbors: number[] = []; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = + input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if ( + neighbor >= 0 && + input.reserveMask[neighbor] === 0 && + input.elevation[neighbor]! <= input.seaLevel + ) + seaNeighbors.push(neighbor); + } + if (seaNeighbors.length === 0) continue; + sourceMask[cell] = 1; + let classification: number; + if ( + glacialDistance[cell]! <= 4 && + input.temperature[cell]! <= 2_000 && + input.slope[cell]! >= 500 + ) + classification = GREATER_REALM_COASTAL_CLASS.glacialFjord; + else if ( + input.accumulation[cell]! >= 16n && + input.slope[cell]! <= 1_200 + ) { + classification = GREATER_REALM_COASTAL_CLASS.deltaEstuary; + } else if ( + input.rockResistance[cell]! >= 6_000 && + (input.tectonicUplift[cell]! >= 2_500 || input.slope[cell]! >= 900) + ) + classification = GREATER_REALM_COASTAL_CLASS.seaCliff; + else classification = GREATER_REALM_COASTAL_CLASS.beachShelf; + coastalClass[cell] = classification; + classes.add(classification); + const requested = + classification === GREATER_REALM_COASTAL_CLASS.glacialFjord + ? 160 + : classification === GREATER_REALM_COASTAL_CLASS.deltaEstuary + ? 90 + : classification === GREATER_REALM_COASTAL_CLASS.seaCliff + ? clamp( + 120 - Math.floor(input.rockResistance[cell]! / 120), + 35, + 80, + ) + : 45; + const amount = Math.min( + requested, + Math.max( + 0, + input.elevation[cell]! + + priorDeltaAt(cell) + + delta[cell]! - + input.seaLevel - + 1, + ), + ); + if (amount === 0) continue; + delta[cell] -= amount; + eroded = safeAdd(eroded, amount); + seaNeighbors.sort( + (first, second) => + input.elevation[first]! - input.elevation[second]! || first - second, + ); + const target = seaNeighbors[0]!; + const capacity = Math.max( + 0, + Math.min( + MAX_PROCESS_CELL_DELTA - delta[target]!, + input.seaLevel - + input.elevation[target]! - + priorDeltaAt(target) - + delta[target]!, + ), + ); + const placed = Math.min(amount, capacity); + delta[target] += placed; + deposited = safeAdd(deposited, placed); + exported = safeAdd(exported, amount - placed); + if (coastalClass[target] === GREATER_REALM_COASTAL_CLASS.none) { + coastalClass[target] = GREATER_REALM_COASTAL_CLASS.beachShelf; + } } - if (seaNeighbors.length === 0) continue; - sourceMask[cell] = 1; - let classification: number; - if ( - glacialDistance[cell]! <= 4 - && input.temperature[cell]! <= 2_000 - && input.slope[cell]! >= 500 - ) classification = GREATER_REALM_COASTAL_CLASS.glacialFjord; - else if (input.accumulation[cell]! >= 16n && input.slope[cell]! <= 1_200) { - classification = GREATER_REALM_COASTAL_CLASS.deltaEstuary; - } else if ( - input.rockResistance[cell]! >= 6_000 - && (input.tectonicUplift[cell]! >= 2_500 || input.slope[cell]! >= 900) - ) classification = GREATER_REALM_COASTAL_CLASS.seaCliff; - else classification = GREATER_REALM_COASTAL_CLASS.beachShelf; - coastalClass[cell] = classification; - classes.add(classification); - const amount = classification === GREATER_REALM_COASTAL_CLASS.glacialFjord ? 160 - : classification === GREATER_REALM_COASTAL_CLASS.deltaEstuary ? 90 - : classification === GREATER_REALM_COASTAL_CLASS.seaCliff - ? clamp(120 - Math.floor(input.rockResistance[cell]! / 120), 35, 80) - : 45; - delta[cell] -= amount; - eroded = safeAdd(eroded, amount); - seaNeighbors.sort((first, second) => ( - input.elevation[first]! - input.elevation[second]! || first - second - )); - const target = seaNeighbors[0]!; - const capacity = Math.max(0, MAX_PROCESS_CELL_DELTA - delta[target]!); - const placed = Math.min(amount, capacity); - delta[target] += placed; - deposited = safeAdd(deposited, placed); - exported = safeAdd(exported, amount - placed); - if (coastalClass[target] === GREATER_REALM_COASTAL_CLASS.none) { - coastalClass[target] = GREATER_REALM_COASTAL_CLASS.beachShelf; + if (eroded !== deposited + exported) + fail("GREATER_REALM_GEOMORPHOLOGY_MATERIAL_BUDGET_INVALID"); + mask = changedMask(delta); + const result = Object.freeze({ + mask, + coastalClass, + delta, + sourceMask, + components: componentMetrics(input.grid, sourceMask), + classCount: classes.size, + budget: Object.freeze({ eroded, deposited, exported }), + }); + completed = true; + return result; + } finally { + glacialDistance.fill(0); + if (!completed) { + mask?.fill(0); + coastalClass.fill(0); + delta.fill(0); + sourceMask.fill(0); } } - if (eroded !== deposited + exported) fail('GREATER_REALM_GEOMORPHOLOGY_MATERIAL_BUDGET_INVALID'); - const mask = changedMask(delta); - return Object.freeze({ - mask, - coastalClass, - delta, - sourceMask, - components: componentMetrics(input.grid, sourceMask), - classCount: classes.size, - budget: Object.freeze({ eroded, deposited, exported }), - }); } function compatibilityBasisPoints( @@ -752,73 +1094,107 @@ function compatibilityBasisPoints( return total === 0 ? 0 : Math.round((compatible * 10_000) / total); } -function alignmentMetrics(input: Readonly<{ - grid: IndexedAxialGrid; - elevation: Int32Array; - seaLevel: number; - tectonicUplift: Int32Array; - volcanicMask: Uint8Array; - reserveMask: Uint8Array; -}>): Readonly<{ +function alignmentMetrics( + input: Readonly<{ + grid: IndexedAxialGrid; + elevation: Int32Array; + seaLevel: number; + tectonicUplift: Int32Array; + volcanicMask: Uint8Array; + reserveMask: Uint8Array; + }>, +): Readonly<{ ridgeUpliftAlignmentBasisPoints: number; riverValleyAlignmentBasisPoints: number; }> { const slope = maximumNeighborDrop(input.grid, input.elevation); - let ridgeCount = 0; - let alignedRidgeCount = 0; - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if (input.elevation[cell]! <= input.seaLevel || input.reserveMask[cell] === 1) continue; - let neighborSum = 0; - let neighborCount = 0; - for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { - const neighbor = input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; - if (neighbor < 0) continue; - neighborSum += input.elevation[neighbor]!; - neighborCount += 1; - } - const exposure = neighborCount === 0 - ? 0 - : input.elevation[cell]! - Math.round(neighborSum / neighborCount); - if (slope[cell]! < 900 || exposure < 300) continue; - ridgeCount += 1; - if (input.tectonicUplift[cell]! >= 2_500 || input.volcanicMask[cell] === 1) { - alignedRidgeCount += 1; + let hydrology: PreliminaryHydrology | undefined; + try { + let ridgeCount = 0; + let alignedRidgeCount = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + input.elevation[cell]! <= input.seaLevel || + input.reserveMask[cell] === 1 + ) + continue; + let neighborSum = 0; + let neighborCount = 0; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = + input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0) continue; + neighborSum += input.elevation[neighbor]!; + neighborCount += 1; + } + const exposure = + neighborCount === 0 + ? 0 + : input.elevation[cell]! - Math.round(neighborSum / neighborCount); + if (slope[cell]! < 900 || exposure < 300) continue; + ridgeCount += 1; + if ( + input.tectonicUplift[cell]! >= 2_500 || + input.volcanicMask[cell] === 1 + ) { + alignedRidgeCount += 1; + } } - } - const hydrology = preliminaryHydrology(input.grid, input.elevation, input.seaLevel); - let riverCount = 0; - let alignedRiverCount = 0; - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if ( - input.reserveMask[cell] === 1 - || input.elevation[cell]! <= input.seaLevel - || hydrology.accumulation[cell]! < 24n - ) continue; - const receiver = hydrology.receiver[cell]!; - if (receiver < 0) continue; - let crossSlopeSum = 0; - let crossSlopeCount = 0; - for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { - const neighbor = input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; - if (neighbor < 0 || neighbor === receiver || hydrology.receiver[neighbor] === cell) continue; - crossSlopeSum += input.elevation[neighbor]!; - crossSlopeCount += 1; - } - if (crossSlopeCount === 0) continue; - riverCount += 1; - if (input.elevation[cell]! <= Math.round(crossSlopeSum / crossSlopeCount) + 150) { - alignedRiverCount += 1; + hydrology = preliminaryHydrology( + input.grid, + input.elevation, + input.seaLevel, + ); + let riverCount = 0; + let alignedRiverCount = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + input.reserveMask[cell] === 1 || + input.elevation[cell]! <= input.seaLevel || + hydrology.accumulation[cell]! < 24n + ) + continue; + const receiver = hydrology.receiver[cell]!; + if (receiver < 0) continue; + let crossSlopeSum = 0; + let crossSlopeCount = 0; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = + input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if ( + neighbor < 0 || + neighbor === receiver || + hydrology.receiver[neighbor] === cell + ) + continue; + crossSlopeSum += input.elevation[neighbor]!; + crossSlopeCount += 1; + } + if (crossSlopeCount === 0) continue; + riverCount += 1; + if ( + input.elevation[cell]! <= + Math.round(crossSlopeSum / crossSlopeCount) + 150 + ) { + alignedRiverCount += 1; + } } + return Object.freeze({ + ridgeUpliftAlignmentBasisPoints: + ridgeCount === 0 + ? 0 + : Math.round((alignedRidgeCount * 10_000) / ridgeCount), + riverValleyAlignmentBasisPoints: + riverCount === 0 + ? 0 + : Math.round((alignedRiverCount * 10_000) / riverCount), + }); + } finally { + slope.fill(0); + hydrology?.receiver.fill(0); + hydrology?.accumulation.fill(0n); } - return Object.freeze({ - ridgeUpliftAlignmentBasisPoints: ridgeCount === 0 - ? 0 - : Math.round((alignedRidgeCount * 10_000) / ridgeCount), - riverValleyAlignmentBasisPoints: riverCount === 0 - ? 0 - : Math.round((alignedRiverCount * 10_000) / riverCount), - }); } /** @@ -826,219 +1202,295 @@ function alignmentMetrics(input: Readonly<{ * pass. This stage changes private elevation authority; labels and biomes must * be derived only after final hydrology is recomputed by the caller. */ -export function shapeGreaterRealmGeomorphology(input: Readonly<{ - grid: IndexedAxialGrid; - candidateSeed: GreaterRealmTerrainSeed; - elevation: Int32Array; - tectonicUplift: Int32Array; - rockResistance: Int32Array; - volcanicPotential: Int32Array; - legacyReserveCell: Uint8Array; - seaLevel?: number; - climate?: GreaterRealmGeomorphologyClimate; -}>): GreaterRealmGeomorphologyResult { +export function shapeGreaterRealmGeomorphology( + input: Readonly<{ + grid: IndexedAxialGrid; + candidateSeed: GreaterRealmTerrainSeed; + elevation: Int32Array; + tectonicUplift: Int32Array; + rockResistance: Int32Array; + volcanicPotential: Int32Array; + legacyReserveCell: Uint8Array; + seaLevel?: number; + climate?: GreaterRealmGeomorphologyClimate; + }>, +): GreaterRealmGeomorphologyResult { const seaLevel = input.seaLevel ?? 0; assertInputs({ ...input, seaLevel }); - const initialElevation = new Int32Array(input.elevation); - const slope = maximumNeighborDrop(input.grid, initialElevation); - const seaMask = new Uint8Array(input.grid.cellCount); - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - if (initialElevation[cell]! <= seaLevel) seaMask[cell] = 1; - } - const coastDistance = maskDistances(input.grid, seaMask); - if (coastDistance.every(distance => distance === DISTANCE_UNREACHABLE)) { - fail('GREATER_REALM_GEOMORPHOLOGY_OCEAN_MISSING'); - } - const hydrology = preliminaryHydrology(input.grid, initialElevation, seaLevel); - const climate = input.climate === undefined - ? deriveClimate({ - grid: input.grid, - candidateSeed: input.candidateSeed, - elevation: initialElevation, - seaLevel, - coastDistance, - slope, - accumulation: hydrology.accumulation, - }) - : Object.freeze({ - temperature: new Int32Array(input.climate.temperature), - moisture: new Int32Array(input.climate.moisture), - }); - - const glacial = glacialProcess({ - grid: input.grid, - elevation: initialElevation, - seaLevel, - slope, - temperature: climate.temperature, - accumulation: hydrology.accumulation, - receiver: hydrology.receiver, - reserveMask: input.legacyReserveCell, - }); - const arid = aridProcess({ - grid: input.grid, - candidateSeed: input.candidateSeed, - elevation: initialElevation, - seaLevel, - slope, - temperature: climate.temperature, - moisture: climate.moisture, - accumulation: hydrology.accumulation, - receiver: hydrology.receiver, - rockResistance: input.rockResistance, - reserveMask: input.legacyReserveCell, - }); - const volcanic = volcanicProcess({ - grid: input.grid, - candidateSeed: input.candidateSeed, - elevation: initialElevation, - seaLevel, - tectonicUplift: input.tectonicUplift, - volcanicPotential: input.volcanicPotential, - reserveMask: input.legacyReserveCell, - }); - const coastal = coastalProcess({ - grid: input.grid, - elevation: initialElevation, - seaLevel, - slope, - temperature: climate.temperature, - accumulation: hydrology.accumulation, - tectonicUplift: input.tectonicUplift, - rockResistance: input.rockResistance, - glacialMask: glacial.mask, - reserveMask: input.legacyReserveCell, - }); - - const totalDelta = new Int32Array(input.grid.cellCount); - const elevation = new Int32Array(input.grid.cellCount); - let changedCellCount = 0; - let maximumAbsoluteCellDelta = 0; - let protectedCellCount = 0; - let protectedChangedCellCount = 0; - for (let cell = 0; cell < input.grid.cellCount; cell += 1) { - const delta = glacial.delta[cell]! + arid.delta[cell]! + volcanic.delta[cell]! - + coastal.delta[cell]!; - if (!Number.isSafeInteger(delta) || Math.abs(delta) > MAX_TOTAL_CELL_DELTA) { - fail('GREATER_REALM_GEOMORPHOLOGY_DELTA_OUT_OF_RANGE'); + const transientNumberArrays: Array<{ fill(value: number): unknown }> = []; + const transientBigUint64Arrays: BigUint64Array[] = []; + const failureNumberArrays: Array<{ fill(value: number): unknown }> = []; + let completed = false; + try { + const sourceElevation = new Int32Array(input.elevation); + transientNumberArrays.push(sourceElevation); + const terraces = shapeGreaterRealmTerraces({ + grid: input.grid, + candidateSeed: input.candidateSeed, + elevation: sourceElevation, + legacyReserveCell: input.legacyReserveCell, + seaLevel, + }); + transientNumberArrays.push(terraces.elevation); + failureNumberArrays.push(terraces.delta); + const initialElevation = terraces.elevation; + const slope = maximumNeighborDrop(input.grid, initialElevation); + transientNumberArrays.push(slope); + const seaMask = new Uint8Array(input.grid.cellCount); + transientNumberArrays.push(seaMask); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (initialElevation[cell]! <= seaLevel) seaMask[cell] = 1; } - totalDelta[cell] = delta; - const nextElevation = initialElevation[cell]! + delta; - if (nextElevation < -0x8000_0000 || nextElevation > 0x7fff_ffff) { - fail('GREATER_REALM_GEOMORPHOLOGY_ELEVATION_OVERFLOW'); + const coastDistance = maskDistances(input.grid, seaMask); + transientNumberArrays.push(coastDistance); + if (coastDistance.every((distance) => distance === DISTANCE_UNREACHABLE)) { + fail("GREATER_REALM_GEOMORPHOLOGY_OCEAN_MISSING"); } - elevation[cell] = nextElevation; - if (delta !== 0) changedCellCount += 1; - maximumAbsoluteCellDelta = Math.max(maximumAbsoluteCellDelta, Math.abs(delta)); - if (input.legacyReserveCell[cell] === 1) { - protectedCellCount += 1; - if (delta !== 0) protectedChangedCellCount += 1; + const hydrology = preliminaryHydrology( + input.grid, + initialElevation, + seaLevel, + ); + transientNumberArrays.push(hydrology.receiver); + transientBigUint64Arrays.push(hydrology.accumulation); + const climate = + input.climate === undefined + ? deriveClimate({ + grid: input.grid, + candidateSeed: input.candidateSeed, + elevation: initialElevation, + seaLevel, + coastDistance, + slope, + accumulation: hydrology.accumulation, + }) + : Object.freeze({ + temperature: new Int32Array(input.climate.temperature), + moisture: new Int32Array(input.climate.moisture), + }); + failureNumberArrays.push(climate.temperature, climate.moisture); + + const glacial = glacialProcess({ + grid: input.grid, + elevation: initialElevation, + seaLevel, + slope, + temperature: climate.temperature, + accumulation: hydrology.accumulation, + receiver: hydrology.receiver, + reserveMask: input.legacyReserveCell, + }); + failureNumberArrays.push(glacial.mask, glacial.delta); + const arid = aridProcess({ + grid: input.grid, + candidateSeed: input.candidateSeed, + elevation: initialElevation, + seaLevel, + slope, + temperature: climate.temperature, + moisture: climate.moisture, + accumulation: hydrology.accumulation, + receiver: hydrology.receiver, + rockResistance: input.rockResistance, + baselineDelta: glacial.delta, + reserveMask: input.legacyReserveCell, + }); + failureNumberArrays.push(arid.mask, arid.delta); + const volcanic = volcanicProcess({ + grid: input.grid, + candidateSeed: input.candidateSeed, + elevation: initialElevation, + seaLevel, + tectonicUplift: input.tectonicUplift, + volcanicPotential: input.volcanicPotential, + reserveMask: input.legacyReserveCell, + }); + failureNumberArrays.push( + volcanic.mask, + volcanic.anchorMask, + volcanic.delta, + ); + const coastal = coastalProcess({ + grid: input.grid, + elevation: initialElevation, + seaLevel, + slope, + temperature: climate.temperature, + accumulation: hydrology.accumulation, + tectonicUplift: input.tectonicUplift, + rockResistance: input.rockResistance, + glacialMask: glacial.mask, + priorDeltas: Object.freeze([glacial.delta, arid.delta, volcanic.delta]), + reserveMask: input.legacyReserveCell, + }); + transientNumberArrays.push(coastal.sourceMask); + failureNumberArrays.push(coastal.mask, coastal.coastalClass, coastal.delta); + + const totalDelta = new Int32Array(input.grid.cellCount); + const elevation = new Int32Array(input.grid.cellCount); + failureNumberArrays.push(totalDelta, elevation); + let changedCellCount = 0; + let maximumAbsoluteCellDelta = 0; + let protectedCellCount = 0; + let protectedChangedCellCount = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + const delta = + terraces.delta[cell]! + + glacial.delta[cell]! + + arid.delta[cell]! + + volcanic.delta[cell]! + + coastal.delta[cell]!; + if ( + !Number.isSafeInteger(delta) || + Math.abs(delta) > MAX_TOTAL_CELL_DELTA + ) { + fail("GREATER_REALM_GEOMORPHOLOGY_DELTA_OUT_OF_RANGE"); + } + totalDelta[cell] = delta; + const nextElevation = sourceElevation[cell]! + delta; + if (nextElevation < -0x8000_0000 || nextElevation > 0x7fff_ffff) { + fail("GREATER_REALM_GEOMORPHOLOGY_ELEVATION_OVERFLOW"); + } + elevation[cell] = nextElevation; + if (sourceElevation[cell]! > seaLevel !== nextElevation > seaLevel) { + fail("GREATER_REALM_GEOMORPHOLOGY_COASTLINE_SIGN_CHANGED"); + } + if (delta !== 0) changedCellCount += 1; + maximumAbsoluteCellDelta = Math.max( + maximumAbsoluteCellDelta, + Math.abs(delta), + ); + if (input.legacyReserveCell[cell] === 1) { + protectedCellCount += 1; + if (delta !== 0) protectedChangedCellCount += 1; + } } - } - if (protectedChangedCellCount !== 0) fail('GREATER_REALM_GEOMORPHOLOGY_PROTECTED_EDIT'); + if (protectedChangedCellCount !== 0) + fail("GREATER_REALM_GEOMORPHOLOGY_PROTECTED_EDIT"); - const erodedMaterialUnits = safeAdd( - safeAdd(glacial.budget.eroded, arid.budget.eroded), - coastal.budget.eroded, - ); - const depositedMaterialUnits = safeAdd( - safeAdd(glacial.budget.deposited, arid.budget.deposited), - coastal.budget.deposited, - ); - const exportedMaterialUnits = safeAdd( - safeAdd(glacial.budget.exported, arid.budget.exported), - coastal.budget.exported, - ); - if (erodedMaterialUnits !== depositedMaterialUnits + exportedMaterialUnits) { - fail('GREATER_REALM_GEOMORPHOLOGY_MATERIAL_BUDGET_INVALID'); - } - const alignment = alignmentMetrics({ - grid: input.grid, - elevation, - seaLevel, - tectonicUplift: input.tectonicUplift, - volcanicMask: volcanic.mask, - reserveMask: input.legacyReserveCell, - }); - const coastalSourceMask = coastal.sourceMask; - const glacialChanged = changedMask(glacial.delta); - const aridChanged = changedMask(arid.delta); - const coastalChanged = changedMask(coastal.delta); + const erodedMaterialUnits = safeAdd( + safeAdd(glacial.budget.eroded, arid.budget.eroded), + coastal.budget.eroded, + ); + const depositedMaterialUnits = safeAdd( + safeAdd(glacial.budget.deposited, arid.budget.deposited), + coastal.budget.deposited, + ); + const exportedMaterialUnits = safeAdd( + safeAdd(glacial.budget.exported, arid.budget.exported), + coastal.budget.exported, + ); + if ( + erodedMaterialUnits !== + depositedMaterialUnits + exportedMaterialUnits + ) { + fail("GREATER_REALM_GEOMORPHOLOGY_MATERIAL_BUDGET_INVALID"); + } + const alignment = alignmentMetrics({ + grid: input.grid, + elevation, + seaLevel, + tectonicUplift: input.tectonicUplift, + volcanicMask: volcanic.mask, + reserveMask: input.legacyReserveCell, + }); + const coastalSourceMask = coastal.sourceMask; + const glacialChanged = changedMask(glacial.delta); + const aridChanged = changedMask(arid.delta); + const coastalChanged = changedMask(coastal.delta); + transientNumberArrays.push(glacialChanged, aridChanged, coastalChanged); - return Object.freeze({ - elevation, - temperature: climate.temperature, - moisture: climate.moisture, - totalDelta, - glacialDelta: glacial.delta, - aridDelta: arid.delta, - volcanicDelta: volcanic.delta, - coastalDelta: coastal.delta, - glacialMask: glacial.mask, - aridMask: arid.mask, - volcanicMask: volcanic.mask, - volcanicAnchorMask: volcanic.anchorMask, - coastalMask: coastal.mask, - coastalClass: coastal.coastalClass, - metrics: Object.freeze({ - changedCellCount, - maximumAbsoluteCellDelta, - protectedCellCount, - protectedChangedCellCount, - erodedMaterialUnits, - depositedMaterialUnits, - exportedMaterialUnits, - endogenicUpliftUnits: volcanic.upliftUnits, - aeolianMovedMaterialUnits: arid.aeolianMovedMaterialUnits, - glacialClimateCompatibilityBasisPoints: compatibilityBasisPoints( - glacial.mask, - cell => climate.temperature[cell]! <= 2_000 - && initialElevation[cell]! > seaLevel + 4_500, - ), - aridClimateCompatibilityBasisPoints: compatibilityBasisPoints( - arid.mask, - cell => climate.temperature[cell]! >= 5_500 && climate.moisture[cell]! <= -1_000, - ), - volcanicTectonicCompatibilityBasisPoints: compatibilityBasisPoints( - volcanic.anchorMask, - cell => input.volcanicPotential[cell]! >= 7_000 - && input.tectonicUplift[cell]! >= 2_500, - ), - coastalProximityCompatibilityBasisPoints: compatibilityBasisPoints( - coastalSourceMask, - cell => coastDistance[cell] === 1, - ), - coastalClassCount: coastal.classCount, - volcanicAnchorCount: volcanic.anchorCount, - ridgeUpliftAlignmentBasisPoints: alignment.ridgeUpliftAlignmentBasisPoints, - riverValleyAlignmentBasisPoints: alignment.riverValleyAlignmentBasisPoints, - glacial: Object.freeze({ - sourceCellCount: countMask(glacial.mask), - changedCellCount: countMask(glacialChanged), - systemCount: glacial.components.count, - minimumSystemCellCount: glacial.components.minimum, - erodedMaterialUnits: glacial.budget.eroded, - depositedMaterialUnits: glacial.budget.deposited, - exportedMaterialUnits: glacial.budget.exported, - }), - arid: Object.freeze({ - sourceCellCount: countMask(arid.mask), - changedCellCount: countMask(aridChanged), - systemCount: arid.components.count, - minimumSystemCellCount: arid.components.minimum, - erodedMaterialUnits: arid.budget.eroded, - depositedMaterialUnits: arid.budget.deposited, - exportedMaterialUnits: arid.budget.exported, + const result = Object.freeze({ + elevation, + temperature: climate.temperature, + moisture: climate.moisture, + totalDelta, + terraceDelta: terraces.delta, + glacialDelta: glacial.delta, + aridDelta: arid.delta, + volcanicDelta: volcanic.delta, + coastalDelta: coastal.delta, + glacialMask: glacial.mask, + aridMask: arid.mask, + volcanicMask: volcanic.mask, + volcanicAnchorMask: volcanic.anchorMask, + coastalMask: coastal.mask, + coastalClass: coastal.coastalClass, + metrics: Object.freeze({ + changedCellCount, + maximumAbsoluteCellDelta, + protectedCellCount, + protectedChangedCellCount, + erodedMaterialUnits, + depositedMaterialUnits, + exportedMaterialUnits, + endogenicUpliftUnits: volcanic.upliftUnits, + aeolianMovedMaterialUnits: arid.aeolianMovedMaterialUnits, + glacialClimateCompatibilityBasisPoints: compatibilityBasisPoints( + glacial.mask, + (cell) => + climate.temperature[cell]! <= 2_000 && + initialElevation[cell]! > seaLevel + 4_500, + ), + aridClimateCompatibilityBasisPoints: compatibilityBasisPoints( + arid.mask, + (cell) => + climate.temperature[cell]! >= 5_500 && + climate.moisture[cell]! <= -1_000, + ), + volcanicTectonicCompatibilityBasisPoints: compatibilityBasisPoints( + volcanic.anchorMask, + (cell) => + input.volcanicPotential[cell]! >= 7_000 && + input.tectonicUplift[cell]! >= 2_500, + ), + coastalProximityCompatibilityBasisPoints: compatibilityBasisPoints( + coastalSourceMask, + (cell) => coastDistance[cell] === 1, + ), + coastalClassCount: coastal.classCount, + volcanicAnchorCount: volcanic.anchorCount, + ridgeUpliftAlignmentBasisPoints: + alignment.ridgeUpliftAlignmentBasisPoints, + riverValleyAlignmentBasisPoints: + alignment.riverValleyAlignmentBasisPoints, + terraces: terraces.metrics, + glacial: Object.freeze({ + sourceCellCount: countMask(glacial.mask), + changedCellCount: countMask(glacialChanged), + systemCount: glacial.components.count, + minimumSystemCellCount: glacial.components.minimum, + erodedMaterialUnits: glacial.budget.eroded, + depositedMaterialUnits: glacial.budget.deposited, + exportedMaterialUnits: glacial.budget.exported, + }), + arid: Object.freeze({ + sourceCellCount: countMask(arid.mask), + changedCellCount: countMask(aridChanged), + systemCount: arid.components.count, + minimumSystemCellCount: arid.components.minimum, + erodedMaterialUnits: arid.budget.eroded, + depositedMaterialUnits: arid.budget.deposited, + exportedMaterialUnits: arid.budget.exported, + }), + coastal: Object.freeze({ + sourceCellCount: countMask(coastalSourceMask), + changedCellCount: countMask(coastalChanged), + systemCount: coastal.components.count, + minimumSystemCellCount: coastal.components.minimum, + erodedMaterialUnits: coastal.budget.eroded, + depositedMaterialUnits: coastal.budget.deposited, + exportedMaterialUnits: coastal.budget.exported, + }), }), - coastal: Object.freeze({ - sourceCellCount: countMask(coastalSourceMask), - changedCellCount: countMask(coastalChanged), - systemCount: coastal.components.count, - minimumSystemCellCount: coastal.components.minimum, - erodedMaterialUnits: coastal.budget.eroded, - depositedMaterialUnits: coastal.budget.deposited, - exportedMaterialUnits: coastal.budget.exported, - }), - }), - }); + }); + completed = true; + return result; + } finally { + for (const field of transientNumberArrays) field.fill(0); + for (const field of transientBigUint64Arrays) field.fill(0n); + if (!completed) { + for (const field of failureNumberArrays) field.fill(0); + } + } } diff --git a/scripts/atlas/greater-realm-terraces.ts b/scripts/atlas/greater-realm-terraces.ts new file mode 100644 index 00000000..db6d3302 --- /dev/null +++ b/scripts/atlas/greater-realm-terraces.ts @@ -0,0 +1,504 @@ +import { + createGreaterRealmMultiscaleIntegerField, + type GreaterRealmTerrainSeed, + type IndexedAxialGrid, +} from './greater-realm-terrain'; + +export const GREATER_REALM_TERRACE_VERSION = + 'greater-realm-low-frequency-terraces-v1' as const; + +const NEIGHBOR_COUNT = 6; +const MACRO_SMOOTHING_PASSES = 12; +const TERRACE_STEP = 2_400; +const TERRACE_RAMP = 480; +const TERRACE_PLATEAU = TERRACE_STEP - TERRACE_RAMP; +const COAST_PROTECTION_HEIGHT = 1_800; +const FULL_STRENGTH_HEIGHT = 4_200; +const MAXIMUM_TERRACE_CELL_DELTA = 2_200; +const SPATIAL_RAMP_EDGE_LIMIT = 1_200; +const SPATIAL_RAMP_RELAXATION_PASSES = 24; +const WEATHERED_EDGE_ALLOWANCE = 560; +const FULL_STEP_EDGE_THRESHOLD = 2_000; +const FIXED_ONE = 4_096; + +export type GreaterRealmTerraceMetrics = Readonly<{ + eligibleCellCount: number; + changedCellCount: number; + plateauCellCount: number; + rampCellCount: number; + realizedPlateauCellCount: number; + realizedRampCellCount: number; + spatialRampCellCount: number; + fullStepEdgeCount: number; + maximumNewEdgeIncrease: number; + weatheredDetailCellCount: number; + maximumAbsoluteCellDelta: number; + netElevationDelta: number; +}>; + +export type GreaterRealmTerraceResult = Readonly<{ + elevation: Int32Array; + delta: Int32Array; + metrics: GreaterRealmTerraceMetrics; +}>; + +function fail(code: string): never { + throw new Error(code); +} + +function clamp(value: number, minimum: number, maximum: number): number { + return Math.max(minimum, Math.min(maximum, value)); +} + +function roundDivide(numerator: number, denominator: number): number { + if ( + !Number.isSafeInteger(numerator) || + !Number.isSafeInteger(denominator) || + denominator <= 0 + ) { + fail('GREATER_REALM_TERRACE_ARITHMETIC_INVALID'); + } + return numerator >= 0 + ? Math.floor((numerator + Math.floor(denominator / 2)) / denominator) + : -Math.floor((-numerator + Math.floor(denominator / 2)) / denominator); +} + +function smoothElevation( + grid: IndexedAxialGrid, + elevation: Int32Array, +): Int32Array { + let current: Int32Array = new Int32Array(elevation); + let completed = false; + try { + for (let pass = 0; pass < MACRO_SMOOTHING_PASSES; pass += 1) { + let next: Int32Array | undefined; + try { + next = new Int32Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + let numerator = current[cell]! * 3; + let denominator = 3; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0) continue; + numerator += current[neighbor]!; + denominator += 1; + } + next[cell] = roundDivide(numerator, denominator); + } + } catch (error) { + next?.fill(0); + throw error; + } + current.fill(0); + current = next; + } + completed = true; + return current; + } finally { + if (!completed) current.fill(0); + } +} + +function relaxSpatialTerraceEdges( + input: Readonly<{ + grid: IndexedAxialGrid; + sourceElevation: Int32Array; + eligible: Uint8Array; + target: Int32Array; + maximumAbsoluteDelta?: number; + minimumValue?: number; + }>, +): Uint8Array { + const adjustment = new Int32Array(input.grid.cellCount); + const adjustmentCount = new Uint8Array(input.grid.cellCount); + const spatialRampCell = new Uint8Array(input.grid.cellCount); + let completed = false; + try { + for (let pass = 0; pass < SPATIAL_RAMP_RELAXATION_PASSES; pass += 1) { + adjustment.fill(0); + adjustmentCount.fill(0); + let violatingEdges = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (input.eligible[cell] !== 1) continue; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = + input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor <= cell || input.eligible[neighbor] !== 1) continue; + const sourceEdge = Math.abs( + input.sourceElevation[cell]! - input.sourceElevation[neighbor]!, + ); + const allowedEdge = Math.max( + SPATIAL_RAMP_EDGE_LIMIT, + sourceEdge + WEATHERED_EDGE_ALLOWANCE, + ); + const difference = input.target[neighbor]! - input.target[cell]!; + const excess = Math.abs(difference) - allowedEdge; + if (excess <= 0) continue; + violatingEdges += 1; + spatialRampCell[cell] = 1; + spatialRampCell[neighbor] = 1; + const firstCorrection = Math.floor(excess / 2); + const secondCorrection = excess - firstCorrection; + if (difference > 0) { + adjustment[cell] += secondCorrection; + adjustment[neighbor] -= firstCorrection; + } else { + adjustment[cell] -= secondCorrection; + adjustment[neighbor] += firstCorrection; + } + adjustmentCount[cell] += 1; + adjustmentCount[neighbor] += 1; + } + } + if (violatingEdges === 0) break; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (adjustmentCount[cell] === 0) continue; + const correction = clamp( + roundDivide(adjustment[cell]!, adjustmentCount[cell]!), + -Math.floor(TERRACE_STEP / 4), + Math.floor(TERRACE_STEP / 4), + ); + const nextValue = input.target[cell]! + correction; + input.target[cell] = + input.maximumAbsoluteDelta === undefined + ? nextValue + : clamp( + nextValue, + Math.max( + input.minimumValue ?? -0x8000_0000, + input.sourceElevation[cell]! - input.maximumAbsoluteDelta, + ), + input.sourceElevation[cell]! + input.maximumAbsoluteDelta, + ); + } + } + completed = true; + return spatialRampCell; + } finally { + adjustment.fill(0); + adjustmentCount.fill(0); + if (!completed) spatialRampCell.fill(0); + } +} + +function positiveModulo(value: number, divisor: number): number { + return ((value % divisor) + divisor) % divisor; +} + +function smoothstepFixed(value: number, maximum: number): number { + const t = clamp(roundDivide(value * FIXED_ONE, maximum), 0, FIXED_ONE); + return roundDivide(t * t * (3 * FIXED_ONE - 2 * t), FIXED_ONE * FIXED_ONE); +} + +function terracedCarrier( + value: number, +): Readonly<{ value: number; ramp: boolean }> { + const local = positiveModulo(value, TERRACE_STEP); + const base = value - local; + // Centre each plateau in its source band instead of flooring the whole + // landscape. Adjacent ramp endpoints still meet exactly, while the shaping + // no longer introduces a continent-wide downward elevation bias. + const plateauHeight = base + Math.floor(TERRACE_STEP / 2); + if (local <= TERRACE_PLATEAU) { + return Object.freeze({ value: plateauHeight, ramp: false }); + } + const rampProgress = smoothstepFixed(local - TERRACE_PLATEAU, TERRACE_RAMP); + return Object.freeze({ + value: plateauHeight + roundDivide(TERRACE_STEP * rampProgress, FIXED_ONE), + ramp: true, + }); +} + +/** + * Shape broad elevation bands into plateaus connected by short smooth ramps, + * then restore bounded weathered detail. This runs before authoritative + * fluvial routing; coast sign and the legacy reserve remain immutable. + */ +export function shapeGreaterRealmTerraces( + input: Readonly<{ + grid: IndexedAxialGrid; + candidateSeed: GreaterRealmTerrainSeed; + elevation: Int32Array; + legacyReserveCell: Uint8Array; + seaLevel?: number; + }>, +): GreaterRealmTerraceResult { + const seaLevel = input.seaLevel ?? 0; + if ( + input.elevation.length !== input.grid.cellCount || + input.legacyReserveCell.length !== input.grid.cellCount || + !Number.isSafeInteger(seaLevel) + ) + fail('GREATER_REALM_TERRACE_INPUT_INVALID'); + for (const value of input.legacyReserveCell) { + if (value > 1) fail('GREATER_REALM_TERRACE_RESERVE_MASK_INVALID'); + } + + let macroElevation: Int32Array | undefined; + let contourWarp: Int32Array | undefined; + let weatheredDetail: Int32Array | undefined; + let elevation: Int32Array | undefined; + let delta: Int32Array | undefined; + let eligible: Uint8Array | undefined; + let intendedRampCell: Uint8Array | undefined; + let weatheredTarget: Int32Array | undefined; + let unweatheredTarget: Int32Array | undefined; + let unweatheredElevation: Int32Array | undefined; + let spatialRampCell: Uint8Array | undefined; + let unweatheredSpatialRampCell: Uint8Array | undefined; + let appliedSpatialRampCell: Uint8Array | undefined; + let appliedUnweatheredSpatialRampCell: Uint8Array | undefined; + let completed = false; + try { + macroElevation = smoothElevation(input.grid, input.elevation); + contourWarp = createGreaterRealmMultiscaleIntegerField( + input.grid, + input.candidateSeed, + [ + { + channel: 'geomorphology-terrace-contour-warp-macro', + amplitude: 900, + smoothingPasses: 18, + selfWeight: 3, + }, + { + channel: 'geomorphology-terrace-contour-warp-meso', + amplitude: 320, + smoothingPasses: 7, + selfWeight: 2, + }, + ], + ); + weatheredDetail = createGreaterRealmMultiscaleIntegerField( + input.grid, + input.candidateSeed, + [ + { + channel: 'geomorphology-terrace-weathering-meso', + amplitude: 210, + smoothingPasses: 4, + selfWeight: 2, + }, + { + channel: 'geomorphology-terrace-weathering-detail', + amplitude: 70, + smoothingPasses: 1, + selfWeight: 2, + }, + ], + ); + elevation = new Int32Array(input.elevation); + delta = new Int32Array(input.grid.cellCount); + eligible = new Uint8Array(input.grid.cellCount); + intendedRampCell = new Uint8Array(input.grid.cellCount); + weatheredTarget = new Int32Array(input.grid.cellCount); + unweatheredTarget = new Int32Array(input.grid.cellCount); + unweatheredElevation = new Int32Array(input.elevation); + + let eligibleCellCount = 0; + let changedCellCount = 0; + let plateauCellCount = 0; + let rampCellCount = 0; + let weatheredDetailCellCount = 0; + let maximumAbsoluteCellDelta = 0; + let netElevationDelta = 0; + + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + const original = input.elevation[cell]!; + if ( + input.legacyReserveCell[cell] === 1 || + original <= seaLevel + COAST_PROTECTION_HEIGHT + ) + continue; + eligible[cell] = 1; + eligibleCellCount += 1; + const carrier = macroElevation[cell]! + contourWarp[cell]!; + const terraced = terracedCarrier(carrier); + if (terraced.ramp) { + rampCellCount += 1; + intendedRampCell[cell] = 1; + } else plateauCellCount += 1; + + const inheritedDetail = clamp( + roundDivide(original - macroElevation[cell]!, 2), + -180, + 180, + ); + unweatheredTarget[cell] = + terraced.value - contourWarp[cell]! + inheritedDetail; + weatheredTarget[cell] = unweatheredTarget[cell]! + weatheredDetail[cell]!; + } + + spatialRampCell = relaxSpatialTerraceEdges({ + grid: input.grid, + sourceElevation: input.elevation, + eligible, + target: weatheredTarget, + }); + unweatheredSpatialRampCell = relaxSpatialTerraceEdges({ + grid: input.grid, + sourceElevation: input.elevation, + eligible, + target: unweatheredTarget, + }); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (unweatheredSpatialRampCell[cell] === 1) spatialRampCell[cell] = 1; + if (eligible[cell] !== 1) continue; + const original = input.elevation[cell]!; + const strength = clamp( + roundDivide( + (original - seaLevel - COAST_PROTECTION_HEIGHT) * FIXED_ONE, + FULL_STRENGTH_HEIGHT - COAST_PROTECTION_HEIGHT, + ), + 0, + FIXED_ONE, + ); + let cellDelta = roundDivide( + (weatheredTarget[cell]! - original) * strength, + FIXED_ONE, + ); + cellDelta = clamp( + cellDelta, + -MAXIMUM_TERRACE_CELL_DELTA, + MAXIMUM_TERRACE_CELL_DELTA, + ); + const nextElevation = Math.max(seaLevel + 1, original + cellDelta); + cellDelta = nextElevation - original; + let unweatheredDelta = roundDivide( + (unweatheredTarget[cell]! - original) * strength, + FIXED_ONE, + ); + unweatheredDelta = clamp( + unweatheredDelta, + -MAXIMUM_TERRACE_CELL_DELTA, + MAXIMUM_TERRACE_CELL_DELTA, + ); + unweatheredDelta = + Math.max(seaLevel + 1, original + unweatheredDelta) - original; + if (!Number.isSafeInteger(nextElevation) || nextElevation > 0x7fff_ffff) { + fail('GREATER_REALM_TERRACE_ELEVATION_OVERFLOW'); + } + elevation[cell] = nextElevation; + unweatheredElevation[cell] = original + unweatheredDelta; + } + + // Strength blending near the coast and the final per-cell displacement + // clamp can reintroduce a hard edge after the target fields were relaxed. + // Project the actually applied elevations once more so the proof describes + // the rendered terrain, not merely an intermediate carrier field. + appliedSpatialRampCell = relaxSpatialTerraceEdges({ + grid: input.grid, + sourceElevation: input.elevation, + eligible, + target: elevation, + maximumAbsoluteDelta: MAXIMUM_TERRACE_CELL_DELTA, + minimumValue: seaLevel + 1, + }); + appliedUnweatheredSpatialRampCell = relaxSpatialTerraceEdges({ + grid: input.grid, + sourceElevation: input.elevation, + eligible, + target: unweatheredElevation, + maximumAbsoluteDelta: MAXIMUM_TERRACE_CELL_DELTA, + minimumValue: seaLevel + 1, + }); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if ( + appliedSpatialRampCell[cell] === 1 || + appliedUnweatheredSpatialRampCell[cell] === 1 + ) + spatialRampCell[cell] = 1; + if (eligible[cell] !== 1) continue; + const cellDelta = elevation[cell]! - input.elevation[cell]!; + if (!Number.isSafeInteger(cellDelta)) { + fail('GREATER_REALM_TERRACE_ELEVATION_OVERFLOW'); + } + delta[cell] = cellDelta; + if (cellDelta !== 0) changedCellCount += 1; + if (elevation[cell] !== unweatheredElevation[cell]) + weatheredDetailCellCount += 1; + maximumAbsoluteCellDelta = Math.max( + maximumAbsoluteCellDelta, + Math.abs(cellDelta), + ); + netElevationDelta += cellDelta; + if (!Number.isSafeInteger(netElevationDelta)) { + fail('GREATER_REALM_TERRACE_METRIC_OVERFLOW'); + } + } + + let realizedPlateauCellCount = 0; + let realizedRampCellCount = 0; + let spatialRampCellCount = 0; + let fullStepEdgeCount = 0; + let maximumNewEdgeIncrease = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (eligible[cell] !== 1) continue; + if (spatialRampCell[cell] === 1) spatialRampCellCount += 1; + let maximumNeighborDelta = 0; + for (let direction = 0; direction < NEIGHBOR_COUNT; direction += 1) { + const neighbor = + input.grid.neighbors[cell * NEIGHBOR_COUNT + direction]!; + if (neighbor < 0 || eligible[neighbor] !== 1) continue; + const shapedEdge = Math.abs(elevation[cell]! - elevation[neighbor]!); + maximumNeighborDelta = Math.max(maximumNeighborDelta, shapedEdge); + if (neighbor <= cell) continue; + const sourceEdge = Math.abs( + input.elevation[cell]! - input.elevation[neighbor]!, + ); + maximumNewEdgeIncrease = Math.max( + maximumNewEdgeIncrease, + Math.max(0, shapedEdge - sourceEdge), + ); + if ( + sourceEdge < SPATIAL_RAMP_EDGE_LIMIT && + shapedEdge >= FULL_STEP_EDGE_THRESHOLD + ) + fullStepEdgeCount += 1; + } + const rampEvidence = + intendedRampCell[cell] === 1 || spatialRampCell[cell] === 1; + if (rampEvidence && maximumNeighborDelta > 80) realizedRampCellCount += 1; + else if (!rampEvidence && maximumNeighborDelta <= 800) + realizedPlateauCellCount += 1; + } + + const result = Object.freeze({ + elevation, + delta, + metrics: Object.freeze({ + eligibleCellCount, + changedCellCount, + plateauCellCount, + rampCellCount, + realizedPlateauCellCount, + realizedRampCellCount, + spatialRampCellCount, + fullStepEdgeCount, + maximumNewEdgeIncrease, + weatheredDetailCellCount, + maximumAbsoluteCellDelta, + netElevationDelta, + }), + }); + completed = true; + return result; + } finally { + macroElevation?.fill(0); + contourWarp?.fill(0); + weatheredDetail?.fill(0); + eligible?.fill(0); + intendedRampCell?.fill(0); + weatheredTarget?.fill(0); + unweatheredTarget?.fill(0); + unweatheredElevation?.fill(0); + spatialRampCell?.fill(0); + unweatheredSpatialRampCell?.fill(0); + appliedSpatialRampCell?.fill(0); + appliedUnweatheredSpatialRampCell?.fill(0); + if (!completed) { + elevation?.fill(0); + delta?.fill(0); + } + } +} diff --git a/scripts/atlas/greater-realm-terrain.ts b/scripts/atlas/greater-realm-terrain.ts index dbf41a55..ef800eba 100644 --- a/scripts/atlas/greater-realm-terrain.ts +++ b/scripts/atlas/greater-realm-terrain.ts @@ -1,7 +1,8 @@ import { Buffer, constants as bufferConstants } from 'node:buffer'; import { createHash } from 'node:crypto'; -export const GREATER_REALM_TERRAIN_CORE_VERSION = 'greater-realm-terrain-v1' as const; +export const GREATER_REALM_TERRAIN_CORE_VERSION = + 'greater-realm-terrain-v1' as const; export type AxialCoordinate = Readonly<{ q: number; @@ -16,9 +17,7 @@ export type AxialCoordinate = Readonly<{ * for small deterministic fixtures. */ export type GreaterRealmTerrainSeed = - | number - | readonly [number, number, number, number] - | Uint32Array; + number | readonly [number, number, number, number] | Uint32Array; type GreaterRealmTerrainSeedWords = Readonly>; @@ -70,7 +69,11 @@ function checkedSafeSum(first: number, second: number, code: string): number { } function roundDivide(numerator: number, denominator: number): number { - if (!Number.isSafeInteger(numerator) || !Number.isSafeInteger(denominator) || denominator <= 0) { + if ( + !Number.isSafeInteger(numerator) || + !Number.isSafeInteger(denominator) || + denominator <= 0 + ) { fail('GREATER_REALM_INTEGER_DIVISION_INVALID'); } const sign = numerator < 0 ? -1 : 1; @@ -97,7 +100,11 @@ export function greaterRealmHexDistance( const q = first.q - second.q; const r = first.r - second.r; const s = -q - r; - if (!Number.isSafeInteger(q) || !Number.isSafeInteger(r) || !Number.isSafeInteger(s)) { + if ( + !Number.isSafeInteger(q) || + !Number.isSafeInteger(r) || + !Number.isSafeInteger(s) + ) { fail('GREATER_REALM_AXIAL_DISTANCE_OVERFLOW'); } return Math.max(Math.abs(q), Math.abs(r), Math.abs(s)); @@ -108,10 +115,18 @@ export function greaterRealmAxialNeighbors( ): readonly AxialCoordinate[] { assertInt32(coordinate.q, 'GREATER_REALM_AXIAL_Q_INVALID'); assertInt32(coordinate.r, 'GREATER_REALM_AXIAL_R_INVALID'); - return GREATER_REALM_AXIAL_DIRECTIONS.map((direction) => Object.freeze({ - q: checkedInt32(coordinate.q + direction.q, 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW'), - r: checkedInt32(coordinate.r + direction.r, 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW'), - })); + return GREATER_REALM_AXIAL_DIRECTIONS.map((direction) => + Object.freeze({ + q: checkedInt32( + coordinate.q + direction.q, + 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW', + ), + r: checkedInt32( + coordinate.r + direction.r, + 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW', + ), + }), + ); } export type IndexedAxialGrid = Readonly<{ @@ -137,11 +152,13 @@ export function indexGreaterRealmAxialGrid( if (coordinates.length === 0 || coordinates.length > INT32_MAX) { fail('GREATER_REALM_AXIAL_GRID_SIZE_INVALID'); } - const canonical = coordinates.map((coordinate) => { - assertInt32(coordinate.q, 'GREATER_REALM_AXIAL_Q_INVALID'); - assertInt32(coordinate.r, 'GREATER_REALM_AXIAL_R_INVALID'); - return { q: coordinate.q, r: coordinate.r }; - }).sort((first, second) => first.q - second.q || first.r - second.r); + const canonical = coordinates + .map((coordinate) => { + assertInt32(coordinate.q, 'GREATER_REALM_AXIAL_Q_INVALID'); + assertInt32(coordinate.r, 'GREATER_REALM_AXIAL_R_INVALID'); + return { q: coordinate.q, r: coordinate.r }; + }) + .sort((first, second) => first.q - second.q || first.r - second.r); const q = new Int32Array(canonical.length); const r = new Int32Array(canonical.length); @@ -158,13 +175,22 @@ export function indexGreaterRealmAxialGrid( const neighbors = new Int32Array(canonical.length * NEIGHBOR_COUNT); neighbors.fill(-1); for (let index = 0; index < canonical.length; index += 1) { - for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + for ( + let directionIndex = 0; + directionIndex < NEIGHBOR_COUNT; + directionIndex += 1 + ) { const direction = GREATER_REALM_AXIAL_DIRECTIONS[directionIndex]!; - const neighborQ = checkedInt32(q[index]! + direction.q, 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW'); - const neighborR = checkedInt32(r[index]! + direction.r, 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW'); - neighbors[index * NEIGHBOR_COUNT + directionIndex] = indexByKey.get( - `${neighborQ},${neighborR}`, - ) ?? -1; + const neighborQ = checkedInt32( + q[index]! + direction.q, + 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW', + ); + const neighborR = checkedInt32( + r[index]! + direction.r, + 'GREATER_REALM_AXIAL_NEIGHBOR_OVERFLOW', + ); + neighbors[index * NEIGHBOR_COUNT + directionIndex] = + indexByKey.get(`${neighborQ},${neighborR}`) ?? -1; } } @@ -202,7 +228,8 @@ function terrainSeedWords( return [seed, 0, 0, 0]; } if (seed.length !== 4) fail('GREATER_REALM_TERRAIN_SEED_INVALID'); - for (const word of seed) assertUint32(word, 'GREATER_REALM_TERRAIN_SEED_INVALID'); + for (const word of seed) + assertUint32(word, 'GREATER_REALM_TERRAIN_SEED_INVALID'); return seed; } @@ -274,7 +301,13 @@ export function greaterRealmCounterRandomU32( assertInt32(r, 'GREATER_REALM_AXIAL_R_INVALID'); assertUint32(sampleIndex, 'GREATER_REALM_TERRAIN_SAMPLE_INVALID'); - return counterRandomFromSeedWords(terrainSeedWords(seed), channel, q, r, sampleIndex); + return counterRandomFromSeedWords( + terrainSeedWords(seed), + channel, + q, + r, + sampleIndex, + ); } export type IntegerFieldLayer = Readonly<{ @@ -302,68 +335,93 @@ export function createGreaterRealmMultiscaleIntegerField( const seedWords = terrainSeedWords(seed); if (layers.length === 0) fail('GREATER_REALM_TERRAIN_LAYERS_EMPTY'); const output = new Int32Array(grid.cellCount); - - for (const layer of layers) { - assertInt32(layer.amplitude, 'GREATER_REALM_TERRAIN_AMPLITUDE_INVALID'); - if (layer.amplitude < 0) fail('GREATER_REALM_TERRAIN_AMPLITUDE_INVALID'); - assertUint32(layer.smoothingPasses, 'GREATER_REALM_TERRAIN_SMOOTHING_INVALID'); - if (layer.smoothingPasses > MAX_OFFLINE_RELAXATION_PASSES) { - fail('GREATER_REALM_TERRAIN_SMOOTHING_INVALID'); - } - const selfWeight = layer.selfWeight ?? 2; - assertUint32(selfWeight, 'GREATER_REALM_TERRAIN_SELF_WEIGHT_INVALID'); - if (selfWeight === 0) fail('GREATER_REALM_TERRAIN_SELF_WEIGHT_INVALID'); - const channel = layerChannelId(layer.channel); - const span = layer.amplitude * 2 + 1; - if (!Number.isSafeInteger(span) || 0xffff * span > Number.MAX_SAFE_INTEGER) { - fail('GREATER_REALM_TERRAIN_AMPLITUDE_OVERFLOW'); - } - - let current = new Int32Array(grid.cellCount); - for (let index = 0; index < grid.cellCount; index += 1) { - const random = counterRandomFromSeedWords( - seedWords, - channel, - grid.q[index]!, - grid.r[index]!, + let current: Int32Array | undefined; + let next: Int32Array | undefined; + let completed = false; + try { + for (const layer of layers) { + assertInt32(layer.amplitude, 'GREATER_REALM_TERRAIN_AMPLITUDE_INVALID'); + if (layer.amplitude < 0) fail('GREATER_REALM_TERRAIN_AMPLITUDE_INVALID'); + assertUint32( + layer.smoothingPasses, + 'GREATER_REALM_TERRAIN_SMOOTHING_INVALID', ); - const sample = random >>> 16; - current[index] = Math.floor((sample * span) / UINT16_RANGE) - layer.amplitude; - } + if (layer.smoothingPasses > MAX_OFFLINE_RELAXATION_PASSES) { + fail('GREATER_REALM_TERRAIN_SMOOTHING_INVALID'); + } + const selfWeight = layer.selfWeight ?? 2; + assertUint32(selfWeight, 'GREATER_REALM_TERRAIN_SELF_WEIGHT_INVALID'); + if (selfWeight === 0) fail('GREATER_REALM_TERRAIN_SELF_WEIGHT_INVALID'); + const channel = layerChannelId(layer.channel); + const span = layer.amplitude * 2 + 1; + if ( + !Number.isSafeInteger(span) || + 0xffff * span > Number.MAX_SAFE_INTEGER + ) { + fail('GREATER_REALM_TERRAIN_AMPLITUDE_OVERFLOW'); + } - for (let pass = 0; pass < layer.smoothingPasses; pass += 1) { - const next = new Int32Array(grid.cellCount); + current = new Int32Array(grid.cellCount); for (let index = 0; index < grid.cellCount; index += 1) { - let numerator = current[index]! * selfWeight; - let denominator = selfWeight; - if (!Number.isSafeInteger(numerator)) fail('GREATER_REALM_TERRAIN_SMOOTHING_OVERFLOW'); - for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { - const neighbor = grid.neighbors[index * NEIGHBOR_COUNT + directionIndex]!; - if (neighbor < 0) continue; - numerator = checkedSafeSum( - numerator, - current[neighbor]!, + const random = counterRandomFromSeedWords( + seedWords, + channel, + grid.q[index]!, + grid.r[index]!, + ); + const sample = random >>> 16; + current[index] = + Math.floor((sample * span) / UINT16_RANGE) - layer.amplitude; + } + + for (let pass = 0; pass < layer.smoothingPasses; pass += 1) { + next = new Int32Array(grid.cellCount); + for (let index = 0; index < grid.cellCount; index += 1) { + let numerator = current[index]! * selfWeight; + let denominator = selfWeight; + if (!Number.isSafeInteger(numerator)) + fail('GREATER_REALM_TERRAIN_SMOOTHING_OVERFLOW'); + for ( + let directionIndex = 0; + directionIndex < NEIGHBOR_COUNT; + directionIndex += 1 + ) { + const neighbor = + grid.neighbors[index * NEIGHBOR_COUNT + directionIndex]!; + if (neighbor < 0) continue; + numerator = checkedSafeSum( + numerator, + current[neighbor]!, + 'GREATER_REALM_TERRAIN_SMOOTHING_OVERFLOW', + ); + denominator += 1; + } + next[index] = checkedInt32( + roundDivide(numerator, denominator), 'GREATER_REALM_TERRAIN_SMOOTHING_OVERFLOW', ); - denominator += 1; } - next[index] = checkedInt32( - roundDivide(numerator, denominator), - 'GREATER_REALM_TERRAIN_SMOOTHING_OVERFLOW', - ); + current.fill(0); + current = next; + next = undefined; } - current = next; - } - for (let index = 0; index < grid.cellCount; index += 1) { - output[index] = checkedInt32( - output[index]! + current[index]!, - 'GREATER_REALM_TERRAIN_FIELD_OVERFLOW', - ); + for (let index = 0; index < grid.cellCount; index += 1) { + output[index] = checkedInt32( + output[index]! + current[index]!, + 'GREATER_REALM_TERRAIN_FIELD_OVERFLOW', + ); + } + current.fill(0); + current = undefined; } + completed = true; + return output; + } finally { + current?.fill(0); + next?.fill(0); + if (!completed) output.fill(0); } - - return output; } class StableCellMinHeap { @@ -377,7 +435,9 @@ class StableCellMinHeap { #less(first: number, second: number): boolean { const priorityDifference = this.priority[first]! - this.priority[second]!; - return priorityDifference < 0 || (priorityDifference === 0 && first < second); + return ( + priorityDifference < 0 || (priorityDifference === 0 && first < second) + ); } push(cell: number): void { @@ -404,7 +464,10 @@ class StableCellMinHeap { if (left >= this.#cells.length) break; const right = left + 1; let child = left; - if (right < this.#cells.length && this.#less(this.#cells[right]!, this.#cells[left]!)) { + if ( + right < this.#cells.length && + this.#less(this.#cells[right]!, this.#cells[left]!) + ) { child = right; } if (!this.#less(this.#cells[child]!, tail)) break; @@ -433,22 +496,33 @@ function canonicalOutletIndexes( const outlets: number[] = []; if (outletIndexes === undefined) { for (let index = 0; index < grid.cellCount; index += 1) { - for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { - if (grid.neighbors[index * NEIGHBOR_COUNT + directionIndex] !== -1) continue; + for ( + let directionIndex = 0; + directionIndex < NEIGHBOR_COUNT; + directionIndex += 1 + ) { + if (grid.neighbors[index * NEIGHBOR_COUNT + directionIndex] !== -1) + continue; outlets.push(index); break; } } } else { for (const outlet of outletIndexes) { - if (!Number.isSafeInteger(outlet) || outlet < 0 || outlet >= grid.cellCount) { + if ( + !Number.isSafeInteger(outlet) || + outlet < 0 || + outlet >= grid.cellCount + ) { fail('GREATER_REALM_PRIORITY_FLOOD_OUTLET_INVALID'); } outlets.push(outlet); } } outlets.sort((first, second) => first - second); - const unique = outlets.filter((outlet, index) => index === 0 || outlet !== outlets[index - 1]); + const unique = outlets.filter( + (outlet, index) => index === 0 || outlet !== outlets[index - 1], + ); if (unique.length === 0) fail('GREATER_REALM_PRIORITY_FLOOD_OUTLET_MISSING'); return unique; } @@ -459,8 +533,10 @@ export function priorityFloodGreaterRealmHexGrid( elevation: Readonly, outletIndexes?: readonly number[] | Uint32Array, ): GreaterRealmPriorityFlood { - if (elevation.length !== grid.cellCount) fail('GREATER_REALM_ELEVATION_LENGTH_INVALID'); - if (grid.cellCount > UINT32_MAX) fail('GREATER_REALM_PRIORITY_FLOOD_SIZE_OVERFLOW'); + if (elevation.length !== grid.cellCount) + fail('GREATER_REALM_ELEVATION_LENGTH_INVALID'); + if (grid.cellCount > UINT32_MAX) + fail('GREATER_REALM_PRIORITY_FLOOD_SIZE_OVERFLOW'); const filledElevation = new Int32Array(elevation); const floodParent = new Int32Array(grid.cellCount); @@ -484,7 +560,11 @@ export function priorityFloodGreaterRealmHexGrid( rank[cell] = popCount; popCount += 1; - for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + for ( + let directionIndex = 0; + directionIndex < NEIGHBOR_COUNT; + directionIndex += 1 + ) { const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + directionIndex]!; if (neighbor < 0 || discovered[neighbor] === 1) continue; discovered[neighbor] = 1; @@ -496,7 +576,8 @@ export function priorityFloodGreaterRealmHexGrid( } } - if (popCount !== grid.cellCount) fail('GREATER_REALM_PRIORITY_FLOOD_UNREACHABLE'); + if (popCount !== grid.cellCount) + fail('GREATER_REALM_PRIORITY_FLOOD_UNREACHABLE'); return Object.freeze({ filledElevation, floodParent, order, rank, outlets }); } @@ -518,7 +599,8 @@ function betterFlowReceiver( if (elevation[candidate]! !== elevation[currentBest]!) { return elevation[candidate]! < elevation[currentBest]!; } - if (rank[candidate]! !== rank[currentBest]!) return rank[candidate]! < rank[currentBest]!; + if (rank[candidate]! !== rank[currentBest]!) + return rank[candidate]! < rank[currentBest]!; return candidate < currentBest; } @@ -531,22 +613,29 @@ export function routeGreaterRealmSingleFlow( flood: GreaterRealmPriorityFlood, ): GreaterRealmSingleFlowRouting { if ( - flood.filledElevation.length !== grid.cellCount - || flood.order.length !== grid.cellCount - || flood.rank.length !== grid.cellCount - || flood.outlets.length !== grid.cellCount - ) fail('GREATER_REALM_FLOW_INPUT_LENGTH_INVALID'); + flood.filledElevation.length !== grid.cellCount || + flood.order.length !== grid.cellCount || + flood.rank.length !== grid.cellCount || + flood.outlets.length !== grid.cellCount + ) + fail('GREATER_REALM_FLOW_INPUT_LENGTH_INVALID'); const receiver = new Int32Array(grid.cellCount); receiver.fill(-1); for (let cell = 0; cell < grid.cellCount; cell += 1) { if (flood.outlets[cell] === 1) continue; let best = -1; - for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + for ( + let directionIndex = 0; + directionIndex < NEIGHBOR_COUNT; + directionIndex += 1 + ) { const neighbor = grid.neighbors[cell * NEIGHBOR_COUNT + directionIndex]!; if (neighbor < 0 || flood.rank[neighbor]! >= flood.rank[cell]!) continue; - if (flood.filledElevation[neighbor]! > flood.filledElevation[cell]!) continue; - if (betterFlowReceiver(neighbor, best, flood.filledElevation, flood.rank)) best = neighbor; + if (flood.filledElevation[neighbor]! > flood.filledElevation[cell]!) + continue; + if (betterFlowReceiver(neighbor, best, flood.filledElevation, flood.rank)) + best = neighbor; } if (best < 0) fail('GREATER_REALM_FLOW_RECEIVER_MISSING'); receiver[cell] = best; @@ -569,17 +658,22 @@ export function assertGreaterRealmSingleFlow( routing: GreaterRealmSingleFlowRouting, ): void { if ( - filledElevation.length !== grid.cellCount - || routing.receiver.length !== grid.cellCount - || routing.order.length !== grid.cellCount - || routing.rank.length !== grid.cellCount - || routing.outlets.length !== grid.cellCount - ) fail('GREATER_REALM_FLOW_INPUT_LENGTH_INVALID'); + filledElevation.length !== grid.cellCount || + routing.receiver.length !== grid.cellCount || + routing.order.length !== grid.cellCount || + routing.rank.length !== grid.cellCount || + routing.outlets.length !== grid.cellCount + ) + fail('GREATER_REALM_FLOW_INPUT_LENGTH_INVALID'); const seen = new Uint8Array(grid.cellCount); for (let orderIndex = 0; orderIndex < grid.cellCount; orderIndex += 1) { const cell = routing.order[orderIndex]!; - if (cell >= grid.cellCount || seen[cell] === 1 || routing.rank[cell] !== orderIndex) { + if ( + cell >= grid.cellCount || + seen[cell] === 1 || + routing.rank[cell] !== orderIndex + ) { fail('GREATER_REALM_FLOW_ORDER_INVALID'); } seen[cell] = 1; @@ -593,17 +687,24 @@ export function assertGreaterRealmSingleFlow( if (receiver !== -1) fail('GREATER_REALM_FLOW_OUTLET_INVALID'); continue; } - if (receiver < 0 || receiver >= grid.cellCount) fail('GREATER_REALM_FLOW_RECEIVER_INVALID'); + if (receiver < 0 || receiver >= grid.cellCount) + fail('GREATER_REALM_FLOW_RECEIVER_INVALID'); let adjacent = false; - for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + for ( + let directionIndex = 0; + directionIndex < NEIGHBOR_COUNT; + directionIndex += 1 + ) { if (grid.neighbors[cell * NEIGHBOR_COUNT + directionIndex] === receiver) { adjacent = true; break; } } if (!adjacent) fail('GREATER_REALM_FLOW_RECEIVER_NOT_ADJACENT'); - if (filledElevation[receiver]! > filledElevation[cell]!) fail('GREATER_REALM_FLOW_UPHILL'); - if (routing.rank[receiver]! >= routing.rank[cell]!) fail('GREATER_REALM_FLOW_CYCLE'); + if (filledElevation[receiver]! > filledElevation[cell]!) + fail('GREATER_REALM_FLOW_UPHILL'); + if (routing.rank[receiver]! >= routing.rank[cell]!) + fail('GREATER_REALM_FLOW_CYCLE'); } if (outletCount === 0) fail('GREATER_REALM_FLOW_OUTLET_MISSING'); } @@ -616,7 +717,10 @@ export function accumulateGreaterRealmSingleFlow( localContribution?: Readonly, ): BigUint64Array { assertGreaterRealmSingleFlow(grid, filledElevation, routing); - if (localContribution !== undefined && localContribution.length !== grid.cellCount) { + if ( + localContribution !== undefined && + localContribution.length !== grid.cellCount + ) { fail('GREATER_REALM_FLOW_CONTRIBUTION_LENGTH_INVALID'); } const accumulation = new BigUint64Array(grid.cellCount); @@ -625,7 +729,8 @@ export function accumulateGreaterRealmSingleFlow( const contribution = BigInt(localContribution?.[index] ?? 1); accumulation[index] = contribution; localTotal += contribution; - if (localTotal > UINT64_MAX) fail('GREATER_REALM_FLOW_ACCUMULATION_OVERFLOW'); + if (localTotal > UINT64_MAX) + fail('GREATER_REALM_FLOW_ACCUMULATION_OVERFLOW'); } for (let orderIndex = grid.cellCount - 1; orderIndex >= 0; orderIndex -= 1) { @@ -641,7 +746,8 @@ export function accumulateGreaterRealmSingleFlow( for (let index = 0; index < grid.cellCount; index += 1) { if (routing.outlets[index] === 1) outletTotal += accumulation[index]!; } - if (outletTotal !== localTotal) fail('GREATER_REALM_FLOW_ACCUMULATION_MISMATCH'); + if (outletTotal !== localTotal) + fail('GREATER_REALM_FLOW_ACCUMULATION_MISMATCH'); return accumulation; } @@ -666,10 +772,7 @@ function elevationMass(elevation: Readonly): bigint { return mass; } -function talusAt( - talus: number | Readonly, - index: number, -): number { +function talusAt(talus: number | Readonly, index: number): number { return typeof talus === 'number' ? talus : talus[index]!; } @@ -682,7 +785,8 @@ export function erodeGreaterRealmThermally( elevation: Readonly, options: SynchronousThermalErosionOptions, ): SynchronousThermalErosionResult { - if (elevation.length !== grid.cellCount) fail('GREATER_REALM_ELEVATION_LENGTH_INVALID'); + if (elevation.length !== grid.cellCount) + fail('GREATER_REALM_ELEVATION_LENGTH_INVALID'); assertUint32(options.iterations, 'GREATER_REALM_THERMAL_ITERATIONS_INVALID'); if (options.iterations > MAX_OFFLINE_RELAXATION_PASSES) { fail('GREATER_REALM_THERMAL_ITERATIONS_INVALID'); @@ -691,7 +795,8 @@ export function erodeGreaterRealmThermally( assertInt32(options.talus, 'GREATER_REALM_THERMAL_TALUS_INVALID'); if (options.talus < 0) fail('GREATER_REALM_THERMAL_TALUS_INVALID'); } else { - if (options.talus.length !== grid.cellCount) fail('GREATER_REALM_THERMAL_TALUS_LENGTH_INVALID'); + if (options.talus.length !== grid.cellCount) + fail('GREATER_REALM_THERMAL_TALUS_LENGTH_INVALID'); for (const value of options.talus) { if (value < 0) fail('GREATER_REALM_THERMAL_TALUS_INVALID'); } @@ -701,12 +806,13 @@ export function erodeGreaterRealmThermally( assertUint32(transferNumerator, 'GREATER_REALM_THERMAL_TRANSFER_INVALID'); assertUint32(transferDenominator, 'GREATER_REALM_THERMAL_TRANSFER_INVALID'); if ( - transferNumerator === 0 - || transferDenominator === 0 - || transferNumerator > THERMAL_TRANSFER_SCALE_MAX - || transferDenominator > THERMAL_TRANSFER_SCALE_MAX - || transferNumerator * NEIGHBOR_COUNT > transferDenominator - ) fail('GREATER_REALM_THERMAL_TRANSFER_INVALID'); + transferNumerator === 0 || + transferDenominator === 0 || + transferNumerator > THERMAL_TRANSFER_SCALE_MAX || + transferDenominator > THERMAL_TRANSFER_SCALE_MAX || + transferNumerator * NEIGHBOR_COUNT > transferDenominator + ) + fail('GREATER_REALM_THERMAL_TRANSFER_INVALID'); const initialMass = elevationMass(elevation); let movedMaterial = 0n; @@ -715,12 +821,19 @@ export function erodeGreaterRealmThermally( const delta = new Float64Array(grid.cellCount); let movedThisIteration = 0; for (let first = 0; first < grid.cellCount; first += 1) { - for (let directionIndex = 0; directionIndex < NEIGHBOR_COUNT; directionIndex += 1) { + for ( + let directionIndex = 0; + directionIndex < NEIGHBOR_COUNT; + directionIndex += 1 + ) { const second = grid.neighbors[first * NEIGHBOR_COUNT + directionIndex]!; if (second <= first) continue; const difference = current[first]! - current[second]!; const magnitude = Math.abs(difference); - const stableDrop = Math.max(talusAt(options.talus, first), talusAt(options.talus, second)); + const stableDrop = Math.max( + talusAt(options.talus, first), + talusAt(options.talus, second), + ); if (magnitude <= stableDrop) continue; const transfer = Math.floor( ((magnitude - stableDrop) * transferNumerator) / transferDenominator, @@ -730,7 +843,10 @@ export function erodeGreaterRealmThermally( const low = difference > 0 ? second : first; delta[high] -= transfer; delta[low] += transfer; - if (!Number.isSafeInteger(delta[high]) || !Number.isSafeInteger(delta[low])) { + if ( + !Number.isSafeInteger(delta[high]) || + !Number.isSafeInteger(delta[low]) + ) { fail('GREATER_REALM_THERMAL_DELTA_OVERFLOW'); } movedThisIteration = checkedSafeSum( @@ -754,7 +870,12 @@ export function erodeGreaterRealmThermally( const finalMass = elevationMass(current); if (finalMass !== initialMass) fail('GREATER_REALM_THERMAL_MASS_MISMATCH'); - return Object.freeze({ elevation: current, initialMass, finalMass, movedMaterial }); + return Object.freeze({ + elevation: current, + initialMass, + finalMass, + movedMaterial, + }); } export type IntegerTerrainArray = @@ -806,36 +927,46 @@ function encodeIntegerArray(array: IntegerTerrainArray): Readonly<{ } else if (array instanceof Int16Array) { type = 'i16'; width = 2; - write = (buffer, offset, index) => buffer.writeInt16LE(array[index]!, offset); + write = (buffer, offset, index) => + buffer.writeInt16LE(array[index]!, offset); } else if (array instanceof Uint16Array) { type = 'u16'; width = 2; - write = (buffer, offset, index) => buffer.writeUInt16LE(array[index]!, offset); + write = (buffer, offset, index) => + buffer.writeUInt16LE(array[index]!, offset); } else if (array instanceof Int32Array) { type = 'i32'; width = 4; - write = (buffer, offset, index) => buffer.writeInt32LE(array[index]!, offset); + write = (buffer, offset, index) => + buffer.writeInt32LE(array[index]!, offset); } else if (array instanceof Uint32Array) { type = 'u32'; width = 4; - write = (buffer, offset, index) => buffer.writeUInt32LE(array[index]!, offset); + write = (buffer, offset, index) => + buffer.writeUInt32LE(array[index]!, offset); } else if (array instanceof BigInt64Array) { type = 'i64'; width = 8; - write = (buffer, offset, index) => buffer.writeBigInt64LE(array[index]!, offset); + write = (buffer, offset, index) => + buffer.writeBigInt64LE(array[index]!, offset); } else if (array instanceof BigUint64Array) { type = 'u64'; width = 8; - write = (buffer, offset, index) => buffer.writeBigUInt64LE(array[index]!, offset); + write = (buffer, offset, index) => + buffer.writeBigUInt64LE(array[index]!, offset); } else { fail('GREATER_REALM_STAGE_DIGEST_ARRAY_INVALID'); } - if (array.length > UINT32_MAX || array.length * width > bufferConstants.MAX_LENGTH) { + if ( + array.length > UINT32_MAX || + array.length * width > bufferConstants.MAX_LENGTH + ) { fail('GREATER_REALM_STAGE_DIGEST_ARRAY_TOO_LARGE'); } const bytes = Buffer.allocUnsafe(array.length * width); try { - for (let index = 0; index < array.length; index += 1) write(bytes, index * width, index); + for (let index = 0; index < array.length; index += 1) + write(bytes, index * width, index); } catch (error) { bytes.fill(0); throw error; @@ -861,16 +992,20 @@ export function digestGreaterRealmTerrainStage( count.writeUInt32LE(grid.cellCount); digest.update(count); - if (Object.keys(fields).some((name) => name.length === 0 || name.startsWith('@'))) { + if ( + Object.keys(fields).some( + (name) => name.length === 0 || name.startsWith('@'), + ) + ) { fail('GREATER_REALM_STAGE_DIGEST_FIELD_INVALID'); } for (const [name, array] of [ ['@q', grid.q] as const, ['@r', grid.r] as const, - ...Object.entries(fields).sort(([first], [second]) => ( - first < second ? -1 : first > second ? 1 : 0 - )), + ...Object.entries(fields).sort(([first], [second]) => + first < second ? -1 : first > second ? 1 : 0, + ), ]) { const encoded = encodeIntegerArray(array); let length: Buffer | undefined; diff --git a/tests/greaterRealmAtmosphere.test.ts b/tests/greaterRealmAtmosphere.test.ts new file mode 100644 index 00000000..bcd2fcde --- /dev/null +++ b/tests/greaterRealmAtmosphere.test.ts @@ -0,0 +1,130 @@ +import { describe, expect, it } from 'vitest'; + +import { + GREATER_REALM_ATMOSPHERE_VERSION, + compositeGreaterRealmAtmosphere, + integrateGreaterRealmHeightFog, +} from '../scripts/atlas/greater-realm-atmosphere'; + +describe('Greater Realm height atmosphere', () => { + it('fills valleys more heavily while leaving high peaks clearer', () => { + const common = { + originHeight: 24_000, + rayLength: 34_000, + density: 0.000_085, + heightFalloff: 0.000_11, + } as const; + const valley = integrateGreaterRealmHeightFog({ + ...common, + rayHeightDelta: -23_000, + }); + const peak = integrateGreaterRealmHeightFog({ + ...common, + rayHeightDelta: -10_000, + }); + + expect(GREATER_REALM_ATMOSPHERE_VERSION).toBe( + 'greater-realm-height-atmosphere-v1', + ); + expect(valley).toBeGreaterThan(peak); + expect(peak).toBeGreaterThanOrEqual(0); + expect(valley).toBeLessThanOrEqual(1); + }); + + it('stays finite for a horizontal camera ray and rejects invalid authority', () => { + const horizontal = integrateGreaterRealmHeightFog({ + originHeight: 2_000, + rayHeightDelta: 0, + rayLength: 20_000, + density: 0.000_05, + heightFalloff: 0.000_1, + }); + + expect(Number.isFinite(horizontal)).toBe(true); + expect(horizontal).toBeGreaterThan(0); + expect(() => + integrateGreaterRealmHeightFog({ + originHeight: 0, + rayHeightDelta: 0, + rayLength: -1, + density: 1, + heightFalloff: 1, + }), + ).toThrow('GREATER_REALM_HEIGHT_FOG_INPUT_INVALID'); + expect(() => + integrateGreaterRealmHeightFog({ + originHeight: 10, + rayHeightDelta: -11, + rayLength: 10, + density: 0.1, + heightFalloff: 0.1, + }), + ).toThrow('GREATER_REALM_HEIGHT_FOG_INPUT_INVALID'); + }); + + it('accumulates more atmosphere along a longer ray at the same height span', () => { + const common = { + originHeight: 18_000, + rayHeightDelta: -10_000, + density: 0.000_04, + heightFalloff: 0.000_09, + } as const; + const near = integrateGreaterRealmHeightFog({ + ...common, + rayLength: 12_000, + }); + const far = integrateGreaterRealmHeightFog({ + ...common, + rayLength: 32_000, + }); + + expect(far).toBeGreaterThan(near); + }); + + it('keeps extinction and in-scattering as independently testable terms', () => { + const scene = [120, 100, 80] as const; + const haze = [180, 200, 225] as const; + const extincted = compositeGreaterRealmAtmosphere({ + scene, + haze, + extinction: 0.5, + inScattering: 0, + }); + const scattered = compositeGreaterRealmAtmosphere({ + scene, + haze, + extinction: 0, + inScattering: 0.5, + }); + const combined = compositeGreaterRealmAtmosphere({ + scene, + haze, + extinction: 0.5, + inScattering: 0.5, + }); + + expect(extincted).toEqual([60, 50, 40]); + expect(scattered[0]).toBeGreaterThan(scene[0]); + expect(combined[0]).toBeGreaterThan(extincted[0]); + expect(combined[0]).toBeLessThan(scattered[0]); + }); + + it('rejects malformed runtime color tuples instead of emitting NaN channels', () => { + expect(() => + compositeGreaterRealmAtmosphere({ + scene: [120, 100] as unknown as readonly [number, number, number], + haze: [180, 200, 225], + extinction: 0.5, + inScattering: 0.5, + }), + ).toThrow('GREATER_REALM_ATMOSPHERE_COMPOSITE_INVALID'); + expect(() => + compositeGreaterRealmAtmosphere({ + scene: [120, 100, 80], + haze: [180, 200] as unknown as readonly [number, number, number], + extinction: 0.5, + inScattering: 0.5, + }), + ).toThrow('GREATER_REALM_ATMOSPHERE_COMPOSITE_INVALID'); + }); +}); diff --git a/tests/greaterRealmCandidateGenerator.test.ts b/tests/greaterRealmCandidateGenerator.test.ts index d3e01938..4e841643 100644 --- a/tests/greaterRealmCandidateGenerator.test.ts +++ b/tests/greaterRealmCandidateGenerator.test.ts @@ -5,6 +5,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { GREATER_REALM_REGION_SPECS, clearGreaterRealmCandidateSecret, + deriveGreaterRealmCandidateSeedMaterial, generateGreaterRealmCandidate, type GreaterRealmPrivateCandidate, } from '../scripts/atlas/greater-realm-candidate-generator'; @@ -17,7 +18,9 @@ import { } from '../scripts/atlas/greater-realm-legacy-lowlands'; import { digestGreaterRealmTerrainStage, + greaterRealmCounterRandomU32, greaterRealmHexDistance, + greaterRealmTerrainChannelId, type IntegerTerrainArray, } from '../scripts/atlas/greater-realm-terrain'; @@ -26,14 +29,17 @@ const SAME_FIRST_WORD_BASELINE_ROOT_INDEX = 23_248; const SAME_FIRST_WORD_ROOT_INDEX = 41_769; const PINNED_ORDINAL = 9; const SAME_FIRST_WORD_ORDINAL = 0; +const SECONDARY_ROOT_LABEL = 'greater-realm-secondary-fixture'; +const SECONDARY_ORDINAL = 18; const EXPECTED_ACTIVE_CELL_MINIMUM = 100_000; const EXPECTED_ACTIVE_CELL_MAXIMUM = 150_000; const EXPECTED_CASTLES_PER_FRONTIER_REGION = 100; let pinned: GreaterRealmPrivateCandidate | undefined; let replay: GreaterRealmPrivateCandidate | undefined; -let sameFirstWordBaseline: GreaterRealmPrivateCandidate | undefined; -let sameFirstWordVariant: GreaterRealmPrivateCandidate | undefined; +let secondary: GreaterRealmPrivateCandidate | undefined; +let sameFirstWordBaseline: Uint32Array | undefined; +let sameFirstWordVariant: Uint32Array | undefined; let mathRandomCallCount = 0; function programmaticRoot(index: number): Uint8Array { @@ -49,16 +55,34 @@ function pinnedRoot(): Uint8Array { .digest()); } +function deriveTestCandidateSeed(rootSeed: Uint8Array): Uint32Array { + const material = deriveGreaterRealmCandidateSeedMaterial(rootSeed, SAME_FIRST_WORD_ORDINAL); + const digest = createHash('sha256') + .update('warpkeep-greater-realm-u32-v1\0', 'utf8') + .update(material) + .digest(); + try { + return new Uint32Array([ + digest.readUInt32LE(0), + digest.readUInt32LE(4), + digest.readUInt32LE(8), + digest.readUInt32LE(12), + ]); + } finally { + material.fill(0); + digest.fill(0); + } +} + function requireCandidates(): readonly [ GreaterRealmPrivateCandidate, GreaterRealmPrivateCandidate, GreaterRealmPrivateCandidate, - GreaterRealmPrivateCandidate, ] { - if (!pinned || !replay || !sameFirstWordBaseline || !sameFirstWordVariant) { + if (!pinned || !replay || !secondary) { throw new Error('GREATER_REALM_CANDIDATE_FIXTURE_MISSING'); } - return [pinned, replay, sameFirstWordBaseline, sameFirstWordVariant]; + return [pinned, replay, secondary]; } function candidateFields( @@ -79,6 +103,7 @@ function candidateFields( geomorphologyTemperature: candidate.geomorphologyTemperature, geomorphologyMoisture: candidate.geomorphologyMoisture, geomorphologyTotalDelta: candidate.geomorphologyTotalDelta, + geomorphologyTerraceDelta: candidate.geomorphologyTerraceDelta, geomorphologyGlacialDelta: candidate.geomorphologyGlacialDelta, geomorphologyAridDelta: candidate.geomorphologyAridDelta, geomorphologyVolcanicDelta: candidate.geomorphologyVolcanicDelta, @@ -180,6 +205,9 @@ function crossTierGraphAudit( beforeAll(() => { const firstRoot = pinnedRoot(); + const secondaryRoot = Uint8Array.from(createHash('sha256') + .update(`${SECONDARY_ROOT_LABEL}\0`, 'utf8') + .digest()); const collisionRoot = programmaticRoot(SAME_FIRST_WORD_BASELINE_ROOT_INDEX); const secondRoot = programmaticRoot(SAME_FIRST_WORD_ROOT_INDEX); const randomSpy = vi.spyOn(Math, 'random').mockImplementation(() => { @@ -195,17 +223,16 @@ beforeAll(() => { rootSeed: firstRoot, candidateOrdinal: PINNED_ORDINAL, }); - sameFirstWordBaseline = generateGreaterRealmCandidate({ - rootSeed: collisionRoot, - candidateOrdinal: SAME_FIRST_WORD_ORDINAL, - }); - sameFirstWordVariant = generateGreaterRealmCandidate({ - rootSeed: secondRoot, - candidateOrdinal: SAME_FIRST_WORD_ORDINAL, + secondary = generateGreaterRealmCandidate({ + rootSeed: secondaryRoot, + candidateOrdinal: SECONDARY_ORDINAL, }); + sameFirstWordBaseline = deriveTestCandidateSeed(collisionRoot); + sameFirstWordVariant = deriveTestCandidateSeed(secondRoot); } finally { randomSpy.mockRestore(); firstRoot.fill(0); + secondaryRoot.fill(0); collisionRoot.fill(0); secondRoot.fill(0); } @@ -214,8 +241,9 @@ beforeAll(() => { afterAll(() => { if (pinned) clearGreaterRealmCandidateSecret(pinned); if (replay) clearGreaterRealmCandidateSecret(replay); - if (sameFirstWordBaseline) clearGreaterRealmCandidateSecret(sameFirstWordBaseline); - if (sameFirstWordVariant) clearGreaterRealmCandidateSecret(sameFirstWordVariant); + if (secondary) clearGreaterRealmCandidateSecret(secondary); + sameFirstWordBaseline?.fill(0); + sameFirstWordVariant?.fill(0); }); describe('Greater Realm private candidate generator', () => { @@ -238,13 +266,32 @@ describe('Greater Realm private candidate generator', () => { }); it('uses all 128 candidate-seed bits instead of collapsing to the first word', () => { - const [, , candidate, variant] = requireCandidates(); + if (!sameFirstWordBaseline || !sameFirstWordVariant) { + throw new Error('GREATER_REALM_COLLISION_FIXTURE_MISSING'); + } + const candidate = sameFirstWordBaseline; + const variant = sameFirstWordVariant; // These programmatic roots were selected for a collision in word zero. - expect(candidate.candidateSeed[0]).toBe(variant.candidateSeed[0]); - expect(candidate.candidateSeed.slice(1)).not.toEqual(variant.candidateSeed.slice(1)); - expect(candidate.stageDigests.final).not.toBe(variant.stageDigests.final); - expect(candidate.grid.q).not.toEqual(variant.grid.q); + expect(candidate[0]).toBe(variant[0]); + expect(candidate.slice(1)).not.toEqual(variant.slice(1)); + const channel = greaterRealmTerrainChannelId('all-seed-words-regression'); + const firstSamples = Array.from({ length: 8 }, (_, counter) => ( + greaterRealmCounterRandomU32(candidate, channel, 17, -31, counter) + )); + const secondSamples = Array.from({ length: 8 }, (_, counter) => ( + greaterRealmCounterRandomU32(variant, channel, 17, -31, counter) + )); + expect(firstSamples).not.toEqual(secondSamples); + }); + + it('carries seed diversity through a second complete deterministic terrain', () => { + const [candidate, , variant] = requireCandidates(); + + expect(variant.candidateOrdinal).toBe(SECONDARY_ORDINAL); + expect(variant.candidateSeed).not.toEqual(candidate.candidateSeed); + expect(variant.stageDigests.final).not.toBe(candidate.stageDigests.final); + expect(variant.grid.q).not.toEqual(candidate.grid.q); }); it('binds every returned authoritative integer field into stable stage evidence', () => { @@ -269,6 +316,7 @@ describe('Greater Realm private candidate generator', () => { geomorphologyTemperature: candidate.geomorphologyTemperature, geomorphologyMoisture: candidate.geomorphologyMoisture, geomorphologyTotalDelta: candidate.geomorphologyTotalDelta, + geomorphologyTerraceDelta: candidate.geomorphologyTerraceDelta, geomorphologyGlacialDelta: candidate.geomorphologyGlacialDelta, geomorphologyAridDelta: candidate.geomorphologyAridDelta, geomorphologyVolcanicDelta: candidate.geomorphologyVolcanicDelta, diff --git a/tests/greaterRealmCandidatePackage.test.ts b/tests/greaterRealmCandidatePackage.test.ts index 1c5fae19..72ab164b 100644 --- a/tests/greaterRealmCandidatePackage.test.ts +++ b/tests/greaterRealmCandidatePackage.test.ts @@ -78,7 +78,7 @@ let atlasDigest = ''; let manifestDigest = ''; let batchSeedDigest = ''; -type GreaterRealmV4ManifestAuthority = { +type GreaterRealmV5ManifestAuthority = { barrierCrossSections: Array<{ cells: number[] }>; gates: Array<{ firstApproachPath: number[]; @@ -88,6 +88,11 @@ type GreaterRealmV4ManifestAuthority = { }>; }; +function privateAtlasFormatVersion(atlas: Buffer): number { + const magicLength = atlas.readUInt16LE(0); + return atlas.readUInt16LE(2 + magicLength); +} + function privateAtlasFieldNames(atlas: Buffer): readonly string[] { let offset = 0; const magicLength = atlas.readUInt16LE(offset); @@ -249,6 +254,7 @@ describe('Greater Realm owner-only candidate package', () => { let comparisonMetrics: GreaterRealmVerifiedPrivateShortlistMetrics | undefined; try { expect(first.equals(second)).toBe(true); + expect(privateAtlasFormatVersion(first)).toBe(5); expect(createHash('sha256').update(first).digest('hex')).toBe(atlasDigest); const atlasFields = privateAtlasFieldNames(first); expect(atlasFields.filter(name => name === 'geological-barrier-band')) @@ -257,6 +263,7 @@ describe('Greater Realm owner-only candidate package', () => { .toBe(atlasFields.indexOf('barrier') + 1); for (const fieldName of [ 'geomorphology-total-delta', + 'geomorphology-terrace-delta', 'geomorphology-elevation', 'geomorphology-glacial-delta', 'geomorphology-arid-delta', @@ -327,7 +334,7 @@ describe('Greater Realm owner-only candidate package', () => { let derivedSeed: Buffer | undefined; try { expect(GREATER_REALM_GENERATOR_VERSION) - .toBe('greater-realm-v2-natural-continent-pr-a.6'); + .toBe('greater-realm-v2-natural-continent-pr-a.8'); expect(GREATER_REALM_TERRAIN_SEED_NAMESPACE) .toBe('greater-realm-v2-natural-continent-pr-a.3'); expect(GREATER_REALM_GENERATOR_VERSION).not.toBe( @@ -408,22 +415,39 @@ describe('Greater Realm owner-only candidate package', () => { expect(inlandWaterPixels).toBeGreaterThan(100); expect(oceanTones.size).toBeGreaterThanOrEqual(4); - let minimumQ = Number.POSITIVE_INFINITY; - let maximumQ = Number.NEGATIVE_INFINITY; let minimumR = Number.POSITIVE_INFINITY; let maximumR = Number.NEGATIVE_INFINITY; + let minimumProjectedX = Number.POSITIVE_INFINITY; + let maximumProjectedX = Number.NEGATIVE_INFINITY; for (let cell = 0; cell < fixture.candidate.grid.cellCount; cell += 1) { - minimumQ = Math.min(minimumQ, fixture.candidate.grid.q[cell]!); - maximumQ = Math.max(maximumQ, fixture.candidate.grid.q[cell]!); - minimumR = Math.min(minimumR, fixture.candidate.grid.r[cell]!); - maximumR = Math.max(maximumR, fixture.candidate.grid.r[cell]!); + const q = fixture.candidate.grid.q[cell]!; + const r = fixture.candidate.grid.r[cell]!; + const projectedX = q + r / 2; + minimumR = Math.min(minimumR, r); + maximumR = Math.max(maximumR, r); + minimumProjectedX = Math.min(minimumProjectedX, projectedX); + maximumProjectedX = Math.max(maximumProjectedX, projectedX); } - const qSpan = maximumQ - minimumQ + 1; const rSpan = maximumR - minimumR + 1; + const projectedXSpan = maximumProjectedX - minimumProjectedX + 1; const scale = Math.max(1, Math.floor(Math.min( - (1_280 - 80) / (qSpan + rSpan / 2), + (1_280 - 80) / projectedXSpan, (1_024 - 120) / rSpan, ))); + const cellPixelSpan = Math.max(1, scale); + const projectedWidth = Math.round( + (maximumProjectedX - minimumProjectedX) * scale, + ) + cellPixelSpan; + const projectedHeight = Math.round((rSpan - 1) * scale * 0.86) + + cellPixelSpan; + const previewOriginX = Math.max( + 0, + Math.round((1_280 - projectedWidth) / 2), + ); + const previewOriginY = Math.max( + 0, + Math.round((1_024 - 44 - projectedHeight) / 2), + ); let renderedRiverAsLand = false; for ( let cell = fixture.candidate.grid.cellCount - 1; @@ -437,11 +461,12 @@ describe('Greater Realm owner-only candidate package', () => { && fixture.candidate.waterRegime[cell] !== 4 ) ) continue; - const x = 40 + Math.round(( - (fixture.candidate.grid.q[cell]! - minimumQ) - + (fixture.candidate.grid.r[cell]! - minimumR) / 2 + const x = previewOriginX + Math.round(( + fixture.candidate.grid.q[cell]! + + fixture.candidate.grid.r[cell]! / 2 + - minimumProjectedX ) * scale); - const y = 60 + Math.round( + const y = previewOriginY + Math.round( (fixture.candidate.grid.r[cell]! - minimumR) * scale * 0.86, ); const offset = (y * 1_280 + x) * 4; @@ -459,13 +484,45 @@ describe('Greater Realm owner-only candidate package', () => { } }, 45_000); + it('keeps the atmospheric hillshade invariant under a global atlas translation', async () => { + const fixture = requireFixture(); + const originalQ = new Int32Array(fixture.candidate.grid.q); + const originalR = new Int32Array(fixture.candidate.grid.r); + let baseline: Buffer | undefined; + let translated: Buffer | undefined; + try { + baseline = await renderGreaterRealmPrivatePreview( + fixture.candidate, + 'hillshade', + ); + for (let cell = 0; cell < fixture.candidate.grid.cellCount; cell += 1) { + fixture.candidate.grid.q[cell] = originalQ[cell]! + 37; + fixture.candidate.grid.r[cell] = originalR[cell]! - 19; + } + translated = await renderGreaterRealmPrivatePreview( + fixture.candidate, + 'hillshade', + ); + + expect(translated.equals(baseline)).toBe(true); + } finally { + fixture.candidate.grid.q.set(originalQ); + fixture.candidate.grid.r.set(originalR); + originalQ.fill(0); + originalR.fill(0); + baseline?.fill(0); + translated?.fill(0); + } + }, 45_000); + it('binds deterministic 15-by-15 axial bins, topography patches, and toolchain pins', () => { const fixture = requireFixture(); const bytes = fixture.workspace.readFile(candidateRelativePath('manifest.private.json')); try { const parsed = JSON.parse(bytes.toString('utf8')) as { - barrierCrossSections: GreaterRealmV4ManifestAuthority['barrierCrossSections']; - gates: GreaterRealmV4ManifestAuthority['gates']; + formatVersion: number; + barrierCrossSections: GreaterRealmV5ManifestAuthority['barrierCrossSections']; + gates: GreaterRealmV5ManifestAuthority['gates']; geomorphologyVersion: string; toolchainVersions: { architecture: string; @@ -528,6 +585,7 @@ describe('Greater Realm owner-only candidate package', () => { chunkKey: string; partitionVersion: string; geomorphologyVersion: string; + topographyVersion: string; cellCount: number; topographyPatchId: string; topographyPatchDigest: string; @@ -543,8 +601,11 @@ describe('Greater Realm owner-only candidate package', () => { manifestDigest: string; topographyPatchId: string; geomorphologyVersion: string; + encodingVersion: string; + topographyVersion: string; }>; }; + expect(parsed.formatVersion).toBe(5); expect(parsed.toolchainVersions.configuredNodeEngine).toBe('>=22.13 <23'); expect(parsed.toolchainVersions.configuredPackageManager).toBe('npm@10.9.8'); expect(parsed.toolchainVersions.libvips).toBe('8.18.3'); @@ -596,7 +657,7 @@ describe('Greater Realm owner-only candidate package', () => { expect(file.path).toMatch(/^node_modules\//u); expect(file.sha256).toMatch(/^[0-9a-f]{64}$/u); } - expect(parsed.geomorphologyVersion).toBe('greater-realm-geomorphology-v2'); + expect(parsed.geomorphologyVersion).toBe('greater-realm-geomorphology-v3'); const throneCell = fixture.candidate.throneAnchor.findIndex(value => value === 1); expect(throneCell).toBeGreaterThanOrEqual(0); expect(parsed.throneAnchor).toEqual({ @@ -633,8 +694,11 @@ describe('Greater Realm owner-only candidate package', () => { const patch = parsed.topographyPatchManifests[index]!; expect(patch.chunkKey).toBe(chunk.chunkKey); expect(chunk.partitionVersion).toBe('axial-bin-15-v1'); - expect(chunk.geomorphologyVersion).toBe('greater-realm-geomorphology-v2'); - expect(patch.geomorphologyVersion).toBe('greater-realm-geomorphology-v2'); + expect(chunk.geomorphologyVersion).toBe('greater-realm-geomorphology-v3'); + expect(chunk.topographyVersion).toBe('greater-realm-advanced-topography-v2'); + expect(patch.geomorphologyVersion).toBe('greater-realm-geomorphology-v3'); + expect(patch.encodingVersion).toBe('wkgr-topography-fields-v2'); + expect(patch.topographyVersion).toBe('greater-realm-advanced-topography-v2'); expect(patch.topographyPatchId).toBe(chunk.topographyPatchId); expect(patch.manifestDigest).toBe(chunk.topographyPatchDigest); expect(patch.sampleCount).toBe(chunk.cellCount); @@ -642,8 +706,8 @@ describe('Greater Realm owner-only candidate package', () => { expect(patch.sampleWidth).toBeLessThanOrEqual(15); expect(patch.sampleHeight).toBeGreaterThan(0); expect(patch.sampleHeight).toBeLessThanOrEqual(15); - expect(patch.fieldCount).toBe(29); - expect(patch.payloadByteCount).toBe(patch.sampleCount * 84); + expect(patch.fieldCount).toBe(30); + expect(patch.payloadByteCount).toBe(patch.sampleCount * 88); expect(patch.payloadDigest).toMatch(/^[0-9a-f]{64}$/u); } } finally { @@ -795,7 +859,7 @@ describe('Greater Realm owner-only candidate package', () => { it.each<[ string, - (manifest: GreaterRealmV4ManifestAuthority) => void, + (manifest: GreaterRealmV5ManifestAuthority) => void, ]>([ ['barrier cross-section cells', manifest => { manifest.barrierCrossSections[0]!.cells[0] = @@ -817,10 +881,10 @@ describe('Greater Realm owner-only candidate package', () => { manifest.gates[0]!.secondAlternateApproachPath[0] = manifest.gates[0]!.secondAlternateApproachPath[0]! + 1; }], - ])('rejects tampered v4 %s authority with an updated digest', async (_label, mutate) => { + ])('rejects tampered v5 %s authority with an updated digest', async (_label, mutate) => { const relativePath = candidateRelativePath('manifest.private.json'); const original = requireFixture().workspace.readFile(relativePath); - const parsed = JSON.parse(original.toString('utf8')) as GreaterRealmV4ManifestAuthority; + const parsed = JSON.parse(original.toString('utf8')) as GreaterRealmV5ManifestAuthority; original.fill(0); mutate(parsed); const corrupted = Buffer.from(`${JSON.stringify(parsed, null, 2)}\n`, 'utf8'); @@ -1198,19 +1262,20 @@ describe('Greater Realm owner-only candidate package', () => { temperature: Int32Array.of(-13), moisture: Int32Array.of(14), geomorphologyTotalDelta: Int32Array.of(15), + geomorphologyTerraceDelta: Int32Array.of(16), geomorphologyElevation: Int32Array.of(14), - geomorphologyGlacialDelta: Int32Array.of(16), - geomorphologyAridDelta: Int32Array.of(17), - geomorphologyVolcanicDelta: Int32Array.of(18), - geomorphologyCoastalDelta: Int32Array.of(19), + geomorphologyGlacialDelta: Int32Array.of(17), + geomorphologyAridDelta: Int32Array.of(18), + geomorphologyVolcanicDelta: Int32Array.of(19), + geomorphologyCoastalDelta: Int32Array.of(20), geomorphologyGlacialMask: Uint8Array.of(1), geomorphologyAridMask: Uint8Array.of(1), geomorphologyVolcanicMask: Uint8Array.of(1), geomorphologyVolcanicAnchorMask: Uint8Array.of(1), geomorphologyCoastalMask: Uint8Array.of(1), geomorphologyCoastalClass: Uint8Array.of(2), - geomorphologyTemperature: Int32Array.of(-20), - geomorphologyMoisture: Int32Array.of(21), + geomorphologyTemperature: Int32Array.of(-21), + geomorphologyMoisture: Int32Array.of(22), }; const u8 = () => Uint8Array.of(1); const i32 = () => Int32Array.of(1); diff --git a/tests/greaterRealmGeomorphology.test.ts b/tests/greaterRealmGeomorphology.test.ts index d11cfd7c..2b0f6e81 100644 --- a/tests/greaterRealmGeomorphology.test.ts +++ b/tests/greaterRealmGeomorphology.test.ts @@ -1,14 +1,15 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from "vitest"; import { GREATER_REALM_GEOMORPHOLOGY_VERSION, shapeGreaterRealmGeomorphology, -} from '../scripts/atlas/greater-realm-geomorphology'; +} from "../scripts/atlas/greater-realm-geomorphology"; import { greaterRealmHexDistance, indexGreaterRealmAxialGrid, type AxialCoordinate, -} from '../scripts/atlas/greater-realm-terrain'; + type GreaterRealmTerrainSeed, +} from "../scripts/atlas/greater-realm-terrain"; function syntheticFixture(radius = 28) { const coordinates: AxialCoordinate[] = []; @@ -30,12 +31,18 @@ function syntheticFixture(radius = 28) { for (let cell = 0; cell < grid.cellCount; cell += 1) { const coordinate = Object.freeze({ q: grid.q[cell]!, r: grid.r[cell]! }); const radiusFromCenter = greaterRealmHexDistance(coordinate); - const volcanicDistance = greaterRealmHexDistance(coordinate, volcanicCenter); - let height = radiusFromCenter >= 23 - ? -800 - (radiusFromCenter - 23) * 500 - : radiusFromCenter === 22 ? 300 - : radiusFromCenter === 21 ? 1_200 - : 5_500 - radiusFromCenter * 120; + const volcanicDistance = greaterRealmHexDistance( + coordinate, + volcanicCenter, + ); + let height = + radiusFromCenter >= 23 + ? -800 - (radiusFromCenter - 23) * 500 + : radiusFromCenter === 22 + ? 300 + : radiusFromCenter === 21 + ? 1_200 + : 5_500 - radiusFromCenter * 120; if (grid.r[cell]! <= -5 && radiusFromCenter < 23) { height += 4_000 + Math.min(2_000, (-grid.r[cell]! - 5) * 100); } @@ -43,11 +50,14 @@ function syntheticFixture(radius = 28) { elevation[cell] = height; tectonicUplift[cell] = grid.r[cell]! <= -5 ? 4_500 : 1_000; if (volcanicDistance <= 4) tectonicUplift[cell] = 8_000; - rockResistance[cell] = grid.r[cell]! >= 5 ? 3_500 : grid.q[cell]! >= 0 ? 7_500 : 4_000; + rockResistance[cell] = + grid.r[cell]! >= 5 ? 3_500 : grid.q[cell]! >= 0 ? 7_500 : 4_000; volcanicPotential[cell] = Math.max(500, 10_000 - volcanicDistance * 850); if (radiusFromCenter <= 2) legacyReserveCell[cell] = 1; - temperature[cell] = grid.r[cell]! <= -5 ? 0 : grid.r[cell]! >= 5 ? 8_000 : 4_500; - moisture[cell] = grid.r[cell]! >= 5 ? -4_000 : grid.r[cell]! <= -5 ? 1_000 : 0; + temperature[cell] = + grid.r[cell]! <= -5 ? 0 : grid.r[cell]! >= 5 ? 8_000 : 4_500; + moisture[cell] = + grid.r[cell]! >= 5 ? -4_000 : grid.r[cell]! <= -5 ? 1_000 : 0; } return Object.freeze({ grid, @@ -66,18 +76,22 @@ function sum(values: Int32Array): number { return total; } -describe('Greater Realm geomorphic shaping', () => { - it('makes deterministic, bounded, coherent changes without touching the legacy reserve', () => { +describe("Greater Realm geomorphic shaping", () => { + it("makes deterministic, bounded, coherent changes without touching the legacy reserve", () => { const fixture = syntheticFixture(); const originalElevation = new Int32Array(fixture.elevation); const input = { ...fixture, - candidateSeed: new Uint32Array([0x1020_3040, 0x5060_7080, 0x90a0_b0c0, 0xd0e0_f001]), + candidateSeed: new Uint32Array([ + 0x1020_3040, 0x5060_7080, 0x90a0_b0c0, 0xd0e0_f001, + ]), } as const; const first = shapeGreaterRealmGeomorphology(input); const second = shapeGreaterRealmGeomorphology(input); - expect(GREATER_REALM_GEOMORPHOLOGY_VERSION).toBe('greater-realm-geomorphology-v2'); + expect(GREATER_REALM_GEOMORPHOLOGY_VERSION).toBe( + "greater-realm-geomorphology-v3", + ); expect(first).toEqual(second); expect(fixture.elevation).toEqual(originalElevation); expect(first.metrics.changedCellCount).toBeGreaterThan(0); @@ -88,9 +102,26 @@ describe('Greater Realm geomorphic shaping', () => { expect(first.metrics.glacial.changedCellCount).toBeGreaterThan(0); expect(first.metrics.arid.changedCellCount).toBeGreaterThan(0); expect(first.metrics.coastal.changedCellCount).toBeGreaterThan(0); + expect(first.metrics.terraces.changedCellCount).toBeGreaterThan(0); + expect(first.metrics.terraces.plateauCellCount).toBeGreaterThan( + first.metrics.terraces.rampCellCount, + ); + expect(first.metrics.terraces.rampCellCount).toBeGreaterThan(0); + expect( + first.metrics.terraces.realizedPlateauCellCount * 100, + ).toBeGreaterThanOrEqual(first.metrics.terraces.eligibleCellCount * 35); + expect(first.metrics.terraces.realizedRampCellCount).toBeGreaterThan(0); + expect(first.metrics.terraces.spatialRampCellCount).toBeGreaterThan(0); + expect(first.metrics.terraces.fullStepEdgeCount).toBe(0); + expect(first.metrics.terraces.weatheredDetailCellCount).toBeGreaterThan(0); + expect( + Math.abs(first.metrics.terraces.netElevationDelta), + ).toBeLessThanOrEqual(first.metrics.terraces.eligibleCellCount * 300); expect(first.metrics.endogenicUpliftUnits).toBeGreaterThan(0); expect(first.metrics.volcanicAnchorCount).toBeGreaterThan(0); - expect(first.metrics.glacial.minimumSystemCellCount).toBeGreaterThanOrEqual(6); + expect(first.metrics.glacial.minimumSystemCellCount).toBeGreaterThanOrEqual( + 6, + ); expect(first.metrics.arid.minimumSystemCellCount).toBeGreaterThanOrEqual(8); expect(first.metrics.coastalClassCount).toBeGreaterThanOrEqual(2); expect(first.metrics.glacialClimateCompatibilityBasisPoints).toBe(10_000); @@ -98,22 +129,38 @@ describe('Greater Realm geomorphic shaping', () => { expect(first.metrics.volcanicTectonicCompatibilityBasisPoints).toBe(10_000); expect(first.metrics.coastalProximityCompatibilityBasisPoints).toBe(10_000); expect(first.metrics.erodedMaterialUnits).toBe( - first.metrics.depositedMaterialUnits + first.metrics.exportedMaterialUnits, + first.metrics.depositedMaterialUnits + + first.metrics.exportedMaterialUnits, ); expect(sum(first.totalDelta)).toBe( - first.metrics.endogenicUpliftUnits - first.metrics.exportedMaterialUnits, + first.metrics.terraces.netElevationDelta + + first.metrics.endogenicUpliftUnits - + first.metrics.exportedMaterialUnits, + ); + expect( + first.metrics.ridgeUpliftAlignmentBasisPoints, + ).toBeGreaterThanOrEqual(0); + expect(first.metrics.ridgeUpliftAlignmentBasisPoints).toBeLessThanOrEqual( + 10_000, + ); + expect( + first.metrics.riverValleyAlignmentBasisPoints, + ).toBeGreaterThanOrEqual(0); + expect(first.metrics.riverValleyAlignmentBasisPoints).toBeLessThanOrEqual( + 10_000, ); - expect(first.metrics.ridgeUpliftAlignmentBasisPoints).toBeGreaterThanOrEqual(0); - expect(first.metrics.ridgeUpliftAlignmentBasisPoints).toBeLessThanOrEqual(10_000); - expect(first.metrics.riverValleyAlignmentBasisPoints).toBeGreaterThanOrEqual(0); - expect(first.metrics.riverValleyAlignmentBasisPoints).toBeLessThanOrEqual(10_000); for (let cell = 0; cell < fixture.grid.cellCount; cell += 1) { expect(first.totalDelta[cell]).toBe( - first.glacialDelta[cell]! + first.aridDelta[cell]! + first.volcanicDelta[cell]! - + first.coastalDelta[cell]!, + first.terraceDelta[cell]! + + first.glacialDelta[cell]! + + first.aridDelta[cell]! + + first.volcanicDelta[cell]! + + first.coastalDelta[cell]!, + ); + expect(first.elevation[cell]).toBe( + fixture.elevation[cell]! + first.totalDelta[cell]!, ); - expect(first.elevation[cell]).toBe(fixture.elevation[cell]! + first.totalDelta[cell]!); if (fixture.legacyReserveCell[cell] === 1) { expect(first.totalDelta[cell]).toBe(0); expect(first.elevation[cell]).toBe(fixture.elevation[cell]); @@ -121,7 +168,7 @@ describe('Greater Realm geomorphic shaping', () => { } }); - it('removes isolated climate-compatible speckles before shaping', () => { + it("removes isolated climate-compatible speckles before shaping", () => { const fixture = syntheticFixture(); const temperature = new Int32Array(fixture.grid.cellCount); temperature.fill(4_000); @@ -142,28 +189,76 @@ describe('Greater Realm geomorphic shaping', () => { expect(result.metrics.arid.systemCount).toBe(0); }); - it('selects separated volcanic anchors from a domain-wide potential plateau', () => { + it("preserves the complete land/sea sign even on one-unit coastlines", () => { + const fixture = syntheticFixture(8); + const originalElevation = new Int32Array(fixture.grid.cellCount); + for (let cell = 0; cell < fixture.grid.cellCount; cell += 1) { + const distance = greaterRealmHexDistance({ + q: fixture.grid.q[cell]!, + r: fixture.grid.r[cell]!, + }); + fixture.elevation[cell] = + distance <= 4 + ? distance === 4 && fixture.grid.q[cell]! % 2 === 0 + ? 100 + : 1 + : -1; + fixture.legacyReserveCell[cell] = 0; + fixture.tectonicUplift[cell] = 1_000; + fixture.rockResistance[cell] = 4_000; + fixture.volcanicPotential[cell] = 0; + fixture.climate.temperature[cell] = 7_000; + fixture.climate.moisture[cell] = -3_000; + originalElevation[cell] = fixture.elevation[cell]!; + } + + const result = shapeGreaterRealmGeomorphology({ + ...fixture, + candidateSeed: new Uint32Array([0xa, 0xb, 0xc, 0xd]), + }); + + let overlappingCoastalErosion = 0; + for (let cell = 0; cell < fixture.grid.cellCount; cell += 1) { + expect(result.elevation[cell]! > 0).toBe(originalElevation[cell]! > 0); + if (result.aridDelta[cell]! < 0 && result.coastalDelta[cell]! < 0) { + overlappingCoastalErosion += 1; + } + } + expect(overlappingCoastalErosion).toBeGreaterThan(0); + expect(result.metrics.erodedMaterialUnits).toBe( + result.metrics.depositedMaterialUnits + + result.metrics.exportedMaterialUnits, + ); + }); + + it("selects separated volcanic anchors from a domain-wide potential plateau", () => { const fixture = syntheticFixture(); fixture.volcanicPotential.fill(8_500); fixture.tectonicUplift.fill(6_000); const result = shapeGreaterRealmGeomorphology({ ...fixture, - candidateSeed: new Uint32Array([0x0bad_f00d, 0x1020_3040, 0x5060_7080, 0x90a0_b0c0]), + candidateSeed: new Uint32Array([ + 0x0bad_f00d, 0x1020_3040, 0x5060_7080, 0x90a0_b0c0, + ]), }); const anchors = [...result.volcanicAnchorMask] - .map((value, cell) => value === 1 ? cell : -1) - .filter(cell => cell >= 0); + .map((value, cell) => (value === 1 ? cell : -1)) + .filter((cell) => cell >= 0); expect(result.metrics.volcanicAnchorCount).toBe(2); expect(anchors).toHaveLength(2); - expect(result.metrics.volcanicTectonicCompatibilityBasisPoints).toBe(10_000); - expect(greaterRealmHexDistance( - { q: fixture.grid.q[anchors[0]!]!, r: fixture.grid.r[anchors[0]!]! }, - { q: fixture.grid.q[anchors[1]!]!, r: fixture.grid.r[anchors[1]!]! }, - )).toBeGreaterThanOrEqual(14); + expect(result.metrics.volcanicTectonicCompatibilityBasisPoints).toBe( + 10_000, + ); + expect( + greaterRealmHexDistance( + { q: fixture.grid.q[anchors[0]!]!, r: fixture.grid.r[anchors[0]!]! }, + { q: fixture.grid.q[anchors[1]!]!, r: fixture.grid.r[anchors[1]!]! }, + ), + ).toBeGreaterThanOrEqual(14); }); - it('derives its production climate from independent named integer fields', () => { + it("derives its production climate from independent named integer fields", () => { const { climate: _fixtureClimate, ...fixture } = syntheticFixture(); const input = { ...fixture, @@ -179,4 +274,93 @@ describe('Greater Realm geomorphic shaping', () => { expect(new Set(first.temperature).size).toBeGreaterThan(1); expect(new Set(first.moisture).size).toBeGreaterThan(1); }); + + it("zeroizes owned buffers after a late geomorphology failure", () => { + const { climate: _fixtureClimate, ...fixture } = syntheticFixture(); + const originalElevation = new Int32Array(fixture.elevation); + const originalTectonicUplift = new Int32Array(fixture.tectonicUplift); + const originalRockResistance = new Int32Array(fixture.rockResistance); + const originalVolcanicPotential = new Int32Array(fixture.volcanicPotential); + const originalLegacyReserveCell = new Uint8Array(fixture.legacyReserveCell); + const allocations: Array<{ + array: + Int32Array | Uint8Array | Uint16Array | Uint32Array | BigUint64Array; + owner: string; + }> = []; + type TrackedConstructor = typeof Int32Array; + const track = (name: string, constructor: TrackedConstructor): void => { + vi.stubGlobal( + name, + new Proxy(constructor, { + construct(target, argumentsList) { + const array = Reflect.construct(target, argumentsList) as + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | BigUint64Array; + const owner = + (new Error().stack ?? "") + .split("\n") + .find((line) => line.includes("/scripts/atlas/")) ?? ""; + allocations.push({ array, owner }); + return array; + }, + }), + ); + }; + track("Int32Array", Int32Array); + track("Uint8Array", Uint8Array as unknown as TrackedConstructor); + track("Uint16Array", Uint16Array as unknown as TrackedConstructor); + track("Uint32Array", Uint32Array as unknown as TrackedConstructor); + track("BigUint64Array", BigUint64Array as unknown as TrackedConstructor); + + const words = [0x1020_3040, 0x5060_7080, 0x90a0_b0c0, 0xd0e0_f001] as const; + let seedValidationCount = 0; + const lateFailSeed = { + 0: words[0], + 1: words[1], + 2: words[2], + 3: words[3], + length: 4, + *[Symbol.iterator]() { + seedValidationCount += 1; + if (seedValidationCount === 6) { + throw new Error("CONTROLLED_GEOMORPHOLOGY_LATE_FAILURE"); + } + yield* words; + }, + } as unknown as GreaterRealmTerrainSeed; + + try { + expect(() => + shapeGreaterRealmGeomorphology({ + ...fixture, + candidateSeed: lateFailSeed, + }), + ).toThrow("CONTROLLED_GEOMORPHOLOGY_LATE_FAILURE"); + expect(seedValidationCount).toBe(6); + + const directlyOwned = allocations.filter( + ({ owner }) => + owner.includes("/greater-realm-geomorphology.ts:") || + owner.includes("/greater-realm-terraces.ts:"), + ); + expect(directlyOwned.length).toBeGreaterThan(20); + for (const { array } of directlyOwned) { + for (let index = 0; index < array.length; index += 1) { + const value = array[index]; + expect(value === 0 || value === 0n).toBe(true); + } + } + + expect(fixture.elevation).toEqual(originalElevation); + expect(fixture.tectonicUplift).toEqual(originalTectonicUplift); + expect(fixture.rockResistance).toEqual(originalRockResistance); + expect(fixture.volcanicPotential).toEqual(originalVolcanicPotential); + expect(fixture.legacyReserveCell).toEqual(originalLegacyReserveCell); + } finally { + vi.unstubAllGlobals(); + } + }); }); diff --git a/tests/greaterRealmTerraces.test.ts b/tests/greaterRealmTerraces.test.ts new file mode 100644 index 00000000..32a689c3 --- /dev/null +++ b/tests/greaterRealmTerraces.test.ts @@ -0,0 +1,206 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { + GREATER_REALM_TERRACE_VERSION, + shapeGreaterRealmTerraces, +} from '../scripts/atlas/greater-realm-terraces'; +import { + greaterRealmHexDistance, + indexGreaterRealmAxialGrid, + type AxialCoordinate, +} from '../scripts/atlas/greater-realm-terrain'; + +function fixture(radius = 34) { + const coordinates: AxialCoordinate[] = []; + for (let q = -radius; q <= radius; q += 1) { + const minimumR = Math.max(-radius, -q - radius); + const maximumR = Math.min(radius, -q + radius); + for (let r = minimumR; r <= maximumR; r += 1) coordinates.push({ q, r }); + } + const grid = indexGreaterRealmAxialGrid(coordinates); + const elevation = new Int32Array(grid.cellCount); + const legacyReserveCell = new Uint8Array(grid.cellCount); + for (let cell = 0; cell < grid.cellCount; cell += 1) { + const distance = greaterRealmHexDistance({ + q: grid.q[cell]!, + r: grid.r[cell]!, + }); + elevation[cell] = + distance >= 31 + ? -900 - (distance - 31) * 400 + : 18_000 - + distance * 520 + + ((grid.q[cell]! * 71 + grid.r[cell]! * 43) % 420); + if (distance <= 2) legacyReserveCell[cell] = 1; + } + return Object.freeze({ grid, elevation, legacyReserveCell }); +} + +describe('Greater Realm low-frequency terraces', () => { + it('builds broad plateaus, short ramps, and weathered detail deterministically', () => { + const input = { + ...fixture(), + candidateSeed: new Uint32Array([ + 0x1020_3040, 0x5060_7080, 0x90a0_b0c0, 0xd0e0_f001, + ]), + } as const; + const original = new Int32Array(input.elevation); + const first = shapeGreaterRealmTerraces(input); + const second = shapeGreaterRealmTerraces(input); + + expect(GREATER_REALM_TERRACE_VERSION).toBe( + 'greater-realm-low-frequency-terraces-v1', + ); + expect(first).toEqual(second); + expect(input.elevation).toEqual(original); + expect(first.metrics.changedCellCount).toBeGreaterThan(100); + expect(first.metrics.plateauCellCount).toBeGreaterThan( + first.metrics.rampCellCount, + ); + expect(first.metrics.rampCellCount).toBeGreaterThan(0); + expect(first.metrics.realizedPlateauCellCount * 100).toBeGreaterThanOrEqual( + first.metrics.eligibleCellCount * 35, + ); + expect(first.metrics.realizedRampCellCount).toBeGreaterThan(0); + expect(first.metrics.spatialRampCellCount).toBeGreaterThan(0); + expect(first.metrics.fullStepEdgeCount).toBe(0); + expect(first.metrics.maximumNewEdgeIncrease).toBeLessThanOrEqual(1_200); + expect(first.metrics.weatheredDetailCellCount).toBeGreaterThan(0); + expect(first.metrics.maximumAbsoluteCellDelta).toBeLessThanOrEqual(2_200); + expect(Math.abs(first.metrics.netElevationDelta)).toBeLessThanOrEqual( + first.metrics.eligibleCellCount * 300, + ); + }); + + it('turns skipped contour steps into short spatial ramps on a monotonic slope', () => { + const input = { + ...fixture(), + candidateSeed: new Uint32Array([ + 0x1111_1111, 0x2222_2222, 0x3333_3333, 0x4444_4444, + ]), + }; + input.legacyReserveCell.fill(0); + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + input.elevation[cell] = 30_000 + input.grid.q[cell]! * 500; + } + const result = shapeGreaterRealmTerraces(input); + let fullStepEdges = 0; + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + for (let direction = 0; direction < 6; direction += 1) { + const neighbor = input.grid.neighbors[cell * 6 + direction]!; + if (neighbor <= cell) continue; + const sourceEdge = Math.abs( + input.elevation[cell]! - input.elevation[neighbor]!, + ); + const shapedEdge = Math.abs( + result.elevation[cell]! - result.elevation[neighbor]!, + ); + if (sourceEdge < 1_200 && shapedEdge >= 2_000) fullStepEdges += 1; + } + } + + expect(result.metrics.spatialRampCellCount).toBeGreaterThan(0); + expect(result.metrics.fullStepEdgeCount).toBe(0); + expect(result.metrics.maximumNewEdgeIncrease).toBeLessThanOrEqual(1_200); + expect(fullStepEdges).toBe(0); + expect(result.metrics.realizedPlateauCellCount).toBeGreaterThan( + result.metrics.realizedRampCellCount, + ); + }); + + it('keeps the coast sign and legacy reserve exactly locked', () => { + const input = { + ...fixture(), + candidateSeed: new Uint32Array([1, 2, 3, 4]), + } as const; + const result = shapeGreaterRealmTerraces(input); + + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + expect(result.elevation[cell]! > 0).toBe(input.elevation[cell]! > 0); + expect(result.elevation[cell]).toBe( + input.elevation[cell]! + result.delta[cell]!, + ); + if ( + input.legacyReserveCell[cell] === 1 || + input.elevation[cell]! <= 1_800 + ) { + expect(result.delta[cell]).toBe(0); + } + } + }); + + it('bounds every new slope along the first editable Lowlands-reserve ring', () => { + const input = { + ...fixture(), + candidateSeed: new Uint32Array([5, 6, 7, 8]), + } as const; + const result = shapeGreaterRealmTerraces(input); + let seamEdges = 0; + + for (let cell = 0; cell < input.grid.cellCount; cell += 1) { + if (input.legacyReserveCell[cell] !== 1) continue; + for (let direction = 0; direction < 6; direction += 1) { + const neighbor = input.grid.neighbors[cell * 6 + direction]!; + if (neighbor < 0 || input.legacyReserveCell[neighbor] === 1) continue; + seamEdges += 1; + const sourceEdge = Math.abs( + input.elevation[cell]! - input.elevation[neighbor]!, + ); + const shapedEdge = Math.abs( + result.elevation[cell]! - result.elevation[neighbor]!, + ); + expect(shapedEdge).toBeLessThanOrEqual(sourceEdge + 2_200); + } + } + expect(seamEdges).toBeGreaterThan(0); + }); + + it('zeroizes every owned working field after a late arithmetic failure', () => { + const input = { + ...fixture(), + candidateSeed: new Uint32Array([ + 0x1020_3040, 0x5060_7080, 0x90a0_b0c0, 0xd0e0_f001, + ]), + seaLevel: Number.MIN_SAFE_INTEGER, + } as const; + const originalElevation = new Int32Array(input.elevation); + const originalReserve = new Uint8Array(input.legacyReserveCell); + const int32FillSpy = vi.spyOn(Int32Array.prototype, 'fill'); + const uint8FillSpy = vi.spyOn(Uint8Array.prototype, 'fill'); + + try { + expect(() => shapeGreaterRealmTerraces(input)).toThrow( + 'GREATER_REALM_TERRACE_ARITHMETIC_INVALID', + ); + + const int32Receivers = [ + ...new Set( + int32FillSpy.mock.instances as unknown as readonly Int32Array[], + ), + ].filter((values) => values !== input.elevation); + const uint8Receivers = [ + ...new Set( + uint8FillSpy.mock.instances as unknown as readonly Uint8Array[], + ), + ].filter((values) => values !== input.legacyReserveCell); + + expect(int32Receivers.length).toBeGreaterThan(10); + expect(uint8Receivers.length).toBeGreaterThan(2); + expect( + int32Receivers.every((values) => + values.every((value) => value === 0), + ), + ).toBe(true); + expect( + uint8Receivers.every((values) => + values.every((value) => value === 0), + ), + ).toBe(true); + expect(input.elevation).toEqual(originalElevation); + expect(input.legacyReserveCell).toEqual(originalReserve); + } finally { + int32FillSpy.mockRestore(); + uint8FillSpy.mockRestore(); + } + }); +}); diff --git a/tests/greaterRealmTerrain.test.ts b/tests/greaterRealmTerrain.test.ts index 836fc9da..6acf7510 100644 --- a/tests/greaterRealmTerrain.test.ts +++ b/tests/greaterRealmTerrain.test.ts @@ -133,6 +133,35 @@ describe('Greater Realm deterministic terrain core', () => { } }); + it('wipes multiscale working fields on success and the output on failure', () => { + const grid = indexGreaterRealmAxialGrid(hexDisc(2)); + const fillSpy = vi.spyOn(Int32Array.prototype, 'fill'); + try { + const result = createGreaterRealmMultiscaleIntegerField(grid, 77, [ + { channel: 'wipe-macro', amplitude: 900, smoothingPasses: 3 }, + { channel: 'wipe-detail', amplitude: 90, smoothingPasses: 1 }, + ]); + expect([...result].some((value) => value !== 0)).toBe(true); + const successfulWipes = (fillSpy.mock.instances as unknown as Int32Array[]).filter( + (values) => values.length === grid.cellCount && values !== result, + ); + expect(successfulWipes).toHaveLength(6); + expect(successfulWipes.every((values) => values.every((value) => value === 0))).toBe(true); + + fillSpy.mockClear(); + expect(() => createGreaterRealmMultiscaleIntegerField(grid, 77, [ + { channel: 'wipe-invalid', amplitude: -1, smoothingPasses: 0 }, + ])).toThrow('GREATER_REALM_TERRAIN_AMPLITUDE_INVALID'); + const failedWipes = (fillSpy.mock.instances as unknown as Int32Array[]).filter( + (values) => values.length === grid.cellCount, + ); + expect(failedWipes).toHaveLength(1); + expect(failedWipes[0]!.every((value) => value === 0)).toBe(true); + } finally { + fillSpy.mockRestore(); + } + }); + it('fills an enclosed depression and gives every cell a flat-safe path to an outlet', () => { const grid = indexGreaterRealmAxialGrid(hexDisc(2)); const elevation = new Int32Array(grid.cellCount); diff --git a/tests/greaterRealmTierTwoCapacityAuthority.test.ts b/tests/greaterRealmTierTwoCapacityAuthority.test.ts new file mode 100644 index 00000000..d42e06aa --- /dev/null +++ b/tests/greaterRealmTierTwoCapacityAuthority.test.ts @@ -0,0 +1,78 @@ +// @vitest-environment node + +import { createHash } from 'node:crypto'; + +import { describe, expect, it } from 'vitest'; + +import { generateGreaterRealmCandidate } from '../scripts/atlas/greater-realm-candidate-generator'; +import { clearGreaterRealmPrivateCandidateBuffers } from '../scripts/atlas/greater-realm-candidate-package'; + +describe('Greater Realm Tier-II capacity authority', () => { + it('retains one fordable Tier-II spine between both dry strategic frontiers', () => { + const rootSeed = Uint8Array.from(createHash('sha256') + .update('greater-realm-ordinary-parent-a\0', 'utf8') + .digest()); + let candidate: ReturnType | undefined; + try { + candidate = generateGreaterRealmCandidate({ rootSeed, candidateOrdinal: 9 }); + for (const region of [6, 7, 8]) { + const seen = new Uint8Array(candidate.grid.cellCount); + const queue: Uint32Array = new Uint32Array(candidate.grid.cellCount); + let head = 0; + let tail = 0; + for (let cell = 0; cell < candidate.grid.cellCount; cell += 1) { + if ( + candidate.regionId[cell] !== region + || candidate.waterRegime[cell] !== 0 + ) continue; + let touchesOuter = false; + for (let direction = 0; direction < 6; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * 6 + direction]!; + if ( + neighbor >= 0 + && candidate.tierId[neighbor] === 1 + && candidate.waterRegime[neighbor] === 0 + ) { + touchesOuter = true; + break; + } + } + if (!touchesOuter) continue; + seen[cell] = 1; + queue[tail++] = cell; + } + let reachesInner = false; + while (head < tail && !reachesInner) { + const cell: number = queue[head++]!; + for (let direction = 0; direction < 6; direction += 1) { + const neighbor = candidate.grid.neighbors[cell * 6 + direction]!; + if (neighbor < 0) continue; + if ( + candidate.tierId[neighbor] === 3 + && candidate.waterRegime[neighbor] === 0 + ) { + reachesInner = true; + break; + } + if ( + seen[neighbor] === 0 + && candidate.regionId[neighbor] === region + && ( + candidate.waterRegime[neighbor] === 0 + || candidate.waterRegime[neighbor] === 3 + || candidate.waterRegime[neighbor] === 4 + ) + ) { + seen[neighbor] = 1; + queue[tail++] = neighbor; + } + } + } + expect(reachesInner, `Tier-II region ${region}`).toBe(true); + } + } finally { + rootSeed.fill(0); + if (candidate) clearGreaterRealmPrivateCandidateBuffers(candidate); + } + }, 30_000); +});