No description
Find a file
beezm 03fe75b378 rivers/01: re-baseline — bare defaults reproduce terrain-shape-v1 (calibration pinned family-off); retire stale oracles; namespace batch root by chat
The bare TerrainGenConfig defaults did NOT reproduce the terrain the developer
locked, so "run the default generator" was not "the terrain in the gallery" —
the single most expensive fact in the codebase, and the reason a fresh chat
would spend an afternoon chasing differences that were CONFIGURATION, not
regression. This is the deliberate task that ends that, before any river work.

A1 — the defaults ARE the locked shape now. Five fields actually move:
SpeckRevert false->true, MinLandComponentFrac 3e-5->2.5e-7 (120x smaller; the
config default would have eaten real islands, not specks), SouthStretch 0->2,
FragmentAmp 0->0.5, Erosion false->true. Seven more were already correct via
SouthernStretch.Default* / CoastalFragment.Default* and are now pinned as
literals, because TerrainShapeV1 used to do that pinning and this default set
inherits the job. CoastShelf stays OFF — the locked shape has no shelf, and
evaluating it (D-041) is its own later task once water renders. Offshore stays
Off permanently (D-063): islands are organic-only, made by the stretch +
fragmentation and identified by the region layer, never placed.

A2 — the preserve mechanism. The curve knots are percentiles of the FAMILY-OFF
land distribution; flipping the defaults would have moved the pool, the knots,
and with them the render field of every batch including terrain-shape-v1
itself. So the pool is pinned family-off (TerrainGenConfig.WithFamilyOff /
CalibrationPool) rather than the knots being baked: calibration stays live, its
INPUT distribution is held still. The pin was a no-op by construction — it sets
the values the defaults carried the instant before the flip — and re-measuring
after confirms it: pool, all six knots, per-seed spread, shaped max,
monotonicity spikeMax and all seven band shares identical.

  Applied wider than "in CalibrateCurve": OffshoreIslandsTool,
  RegionLabelingTool and SouthernStretchTool generate their own family-off
  field for the Phase-1 anchor, so the pool pin alone would NOT have covered
  them and their a1 would have failed for a configuration reason. TerrainGenTool
  too — it AUTHORED 02_pass1_port and must stay able to regenerate its own
  anchor.

  Recorded as a judged-and-parked property: knots measured family-off, applied
  family-on. Deliberate, not an oversight. Same disposition as the mid-slope
  feather.

A4 — no oracle may pass against a superseded baseline. Six anchors retired
(01/03/04/06/08/09) with their checks and ISLA_T0x_SOURCE defaults; three kept
(chat1/02_pass1_port as the family-off pass-1 guard, chat2/10 and chat2/11 as
the shape and erosion acceptance anchors). Two invariants were RE-POINTED
rather than lost — the southern stretch's north-lock and the coastal-fragment
interior-lock now compare against SAME-RUN fields, which is scale-free and
cannot be invalidated by a moved dump. The retired dumps are kept, not deleted,
and marked superseded in their INDEX.md.

  A missing anchor is now LOUD. The old pattern skipped silently, so a moved
  anchor did not make its oracle fail — it made it not RUN, and a batch with a
  skipped check prints an all-PASS table that reads like a clean one. That is
  the INVERSE of the hazard the re-baseline guards against, and the migration
  below is exactly the event that would have triggered it, on nine anchors at
  once. ShapingOracle.LoadAnchor now separates the two cases: absent -> throw;
  present at another size -> loud INCONCLUSIVE, which is a fail, never a pass.

  TerrainShapeV1 inverted from PRESET to GUARD and moved to its own file.
  Apply() is gone — stamping the values on top of the defaults would MASK a
  drift instead of catching it. Its constants are now the assertion target, and
  Assert() refuses a run whose defaults have drifted off the locked shape.

B/C — batches are namespaced by chat: batches/<chat>/NN_slug/. Task numbers
restart at 00 per chat, so a flat root collided the moment a second chat
existed — four colliding prefixes across 25 batches, separable only by slug.
ToolingPaths.ChatSlug is REQUIRED (throws if unset) and defaults per tool to
its authoring chat, so re-running reproduces a batch in place while ISLA_CHAT
redirects — which is also what stops an acceptance run from overwriting the
very anchor it checks against. Writes go through BatchRoot; historical READS
compose against BatchesRoot and so carry the prefix in their own source string
("chat1/02_pass1_port"). The 25 existing batches were migrated moves-only.

ACCEPTANCE — 16 of 16 byte-identical, 0 failed. All 8 gallery seeds at 8192
from the bare defaults are byte-identical to chat2/10_frag4_seed_gallery
(= terrain-shape-v1, a59e52f); all 8 erosion fields byte-identical to
chat2/11_erosion (= ea291ea). Every gallery table row and every erosion
statistic reproduces its recorded value exactly. DrainageTool's a11 passes
bit-identical over 67,108,864 cells, and its analysis reproduces batch 12
exactly — so the whole chain rivers depends on (shape -> erosion -> drainage)
is unchanged. All 12 edited tools re-run clean; both new guards negative-tested.

The baseline moved in DEFAULTS, not in TERRAIN.

-> XX_Human/output/rivers/01_rebaseline_and_batch_namespace.report.md

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhkXBQh2tDmcWKpXYcj8vj
2026-08-23 04:45:10 -04:00
Client Phase 0: clean project skeleton for the v2 rewrite 2026-08-19 20:21:12 -04:00
Core rivers/01: re-baseline — bare defaults reproduce terrain-shape-v1 (calibration pinned family-off); retire stale oracles; namespace batch root by chat 2026-08-23 04:45:10 -04:00
Server Phase 0: clean project skeleton for the v2 rewrite 2026-08-19 20:21:12 -04:00
Tools rivers/01: re-baseline — bare defaults reproduce terrain-shape-v1 (calibration pinned family-off); retire stale oracles; namespace batch root by chat 2026-08-23 04:45:10 -04:00
.gitattributes Phase 0: clean project skeleton for the v2 rewrite 2026-08-19 20:21:12 -04:00
.gitignore Phase 0: clean project skeleton for the v2 rewrite 2026-08-19 20:21:12 -04:00
islaApocalypse-v2.csproj Phase 0: clean project skeleton for the v2 rewrite 2026-08-19 20:21:12 -04:00
islaApocalypse-v2.sln Phase 0: clean project skeleton for the v2 rewrite 2026-08-19 20:21:12 -04:00
project.godot Move the user:// isolation warning out of project.godot 2026-08-19 21:31:12 -04:00
README.md Phase 2a: the faithful redistribution curve, re-measured against v2's own output 2026-08-20 01:38:10 -04:00

Isla Apocalypse — v2

The rewrite (D-049). A post-apocalyptic survival voxel game: one island, generated once offline, loaded by everyone.

Status: Phase 2 — shaping. Phase 0 laid the contracts and the tooling rails; Phase 1 ported the crown-jewel noise and pass 1 (the island's raw shape); chat2/01 added pass 2a — the height redistribution curve and shelf detail, re-calibrated against this repo's own output, with the two-height-field split and an automatic oracle.

Still no erosion, rivers, water bodies, crater carve, coast shelf, offshore islets, biomes, roads or mesher.


⚠ Two repos. Never one.

Repo Role
~/celerNexus/islaApocalypse-v2/ (this) The rewrite. Every line of new code is written here.
~/celerNexus/islaApocalypse/ READ-ONLY. The pre-rewrite prototype, archived at ab78883, tag pre-rewrite-reference. Ported FROM; never written to.
~/celerNexus/islaApocalypse-vault-v0.1/ The design vault — the source of truth for WHAT and WHY. The vault leads.

The old codebase is a spent quarry (D-050): mined, never tracked, never synced, never canon'd. Writing rewrite code into it would corrupt the very thing being ported from.

⚠ Runtime data isolation — do not weaken

This project pins its user:// directory away from the old prototype's, which holds the preserved reference seeds, blueprints and batches:

user:// → ~/.local/share/islaApocalypse-v2/          ← this project
          ~/.local/share/godot/app_userdata/islaApocalypse/   ← ⛔ the OLD prototype. Never touch.

Pinned two ways in project.godot — a distinct application/config/name and use_custom_user_dir + custom_user_dir_name. Both must stay.

⚠ Do not document this with a comment in project.godot — it will not survive.

Godot rewrites project.godot on import and STRIPS ALL COMMENTS, replacing the header with its own boilerplate. (Measured: a warning block written there was gone after the next --import. The settings survived; only the explanation was deleted.) So the warning lives here, and the guarantee is enforced by the probe below — which exits non-zero on a clash and therefore works in a pre-flight check, not just by eye.

After any change to the [application] block, re-run the probe:

Godot_v4.7.2-stable_mono_linux.x86_64 --headless \
  --path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/UserDirProbe.tscn

Layout

Core/     math + data only, ENGINE-FREE   — depends on nothing above it
Server/   authoritative logic             — may use Core          (empty: not yet its phase)
Client/   rendering                       — may use Core          (empty: not yet its phase)
Tools/    the offline generator           — ⚠ may NOT use Client

Each layer has a README.md stating its role and its boundary. Read the one for the layer you are about to write in — they carry the reasons, not just the rules.

The contracts this phase establishes

  • ★★ The column model (D-053) — the world as a 2D grid of columns, each a stack of (material, thickness) runs. Air is the top run; there is no air-vs-solid height branch.Core/Scripts/Column.cs
  • The material schemas (D-054) — terrain and building, two schemas bridged by a transformation seam that is deliberately empty. Identity is a registry key, never an ordinal. Mesh style is per-origin, never a material property. → Core/Scripts/MaterialRegistry.cs
  • The scaling discipline — nothing uses a raw pixel number. → Core/Scripts/GenerationScale.cs
  • The vertical yardstick — ONE metres-per-raw-unit number, not three constants and twenty literals as the prototype had. → Core/Scripts/WorldScale.cs
  • The elevation profile — the 7-band redistribution curve, identity at and below sea, its knots measured rather than chosen. → Core/Scripts/HeightCurve.cs
  • The two-height-field split (D-046) — heightClassify is raw and is the oracle; height is curved and is what gets drawn. → Tools/Scripts/Pass2Result.cs
  • Tooling safety — every path env-overridable, deletion refused by code. → Core/Scripts/ToolingPaths.cs, Core/Scripts/FileSafety.cs

Build

dotnet build
Godot_v4.7.2-stable_mono_linux.x86_64 --headless --import --path .

Godot 4.7.2 (mono) · .NET 8 (net8.0) · Godot.NET.Sdk/4.7.2

The reference repo is Godot 4.7.1. This repo deliberately targets 4.7.2 — clean rewrite, current tooling. 4.7.1 is the port source, not a constraint on new code.

<RollForward>Major</RollForward> in the .csproj is load-bearing.

The target is net8.0 but this machine carries only a .NET 10 runtime. Without that property the generated runtimeconfig.json rolls forward by MINOR only and every run fails with "You must install .NET 8". This bites at run time, not build time — the build succeeds either way — so re-check the emitted runtimeconfig.json after any .csproj change.

What this phase is NOT

No mesher, no stratigraphy, no biomes, no water, no rivers, no erosion. The pipeline is 2D-maps-first (D-056): stages 15 are flat maps, gotten completely right before a single 3D vertex exists. Building the mesher before the data is right is the specific trap this rewrite exists to undo.

And the curve is a BASELINE, not a verdict. It is ported faithfully and calibrated honestly so that later reshaping has a control to be judged against. Whether this elevation profile — 60 % of land in the bottom 14 m, 4 % above the plateau — is the one the world wants is an open question the histograms exist to inform, not one this port has answered.