The seeded floor from chat2/05 — FloorNorth/FloorSouth, the PCG32 placement, StampWeight,
StampRadiusFrac/StampCoreFrac/StampEdgeJitter, the centre separation and land gap, the
floor self-check and oracle (h), the centre rings on the tag overlay — is reverted out
whole, as a forward commit. It looked stamped. It is one checkout away at 3b96e06. What
remains is ONE island mechanism: the organic noise-field layer, with chat2/05 stage 2's
shape untouched (freq 16, crest 24 m pre-curve, core 0.25, sharpness 2.5, corners on,
moat + falloff test + outer bound unmoved), now OffshoreSettings.Organic(). The count per
hemisphere is a statistical outcome of the tuning, read off a table, never guaranteed.
The south was measured before anything moved (OffshoreDiagnosis, over task 01's pool at
2048): the premise that the south generates fewer islands is not what the field says.
The NORTH has 1.75x less island-eligible ocean (56 % of its ocean is zone vs 69 %; 233
zone rows per column vs 407) and under half the island candidates (1,183 over-threshold
peaks in zone vs 2,422). Per cell the south is richer too (484 vs 414 candidates per
Mcell). The gate that loses candidates is the moat in the north and the falloff test in
the south — the bulging south coast pushes the 0.72 contour out — but neither binds
enough to suppress it. So the fix is density, and the binding constraint for "a couple
north, consistently" is the north.
The knobs: Density (the main one, the calibration quantile) and SouthWeight (south density
= Density x weight — applied to the quantile, so a south island looks exactly like a north
one; there are just more). Three levels batched on 12 seeds at 4096, SouthWeight 1.25:
0.016 N 2/4.4/10 S 8/13.8/21 (every seed clears N >= 2, S >= 3, but N's minimum IS 2),
0.022 N 3/6.3/12 S 15/19.9/29 (the preset — the first level whose north minimum sits
comfortably above the target), 0.030 N 3/7.8/15 S 19/25.2/33 (the too-many bookend). The
south weight is the developer's stated preference, not a fix; the SouthWeight 1.0 tables
(S 6/10.9/16 at 0.022) are in the batch's scratch/ for the comparison.
The guards, so more density does not buy slop, each a revert by component membership:
specks (< 3e-5 of the map's area, ~500 cells at 4096 — 70-100 per field at these
densities, the noise caps the reference also surfaced), clusters (the smaller of two
islands whose shores are within 0.8 % of the map width — 0-7 per field), blobs (> 6e-4 —
never bit). And the reference's submerged humps, now attributed by HUMP rather than by
bounding box: the first 4096 plate probe showed hollow ghost rings beside islands — a
guard-dropped island's cap went back to seabed but its rim survived inside its
neighbour's bbox keep-region. A hump (one connected raised region) now stays only if it
holds a kept island, and a dropped island's own cap goes even inside a kept hump.
Oracle, all passing: a1 (Phase-1 dump), a3 (task-03 dump), a4 NEW — offshore OFF at 8192
bit-identical to the terrain-curve-v1 tag's own 04 gallery dump over 67,108,864 cells —
j0 shelf inert on land, and per field moat / mainland unmoved / tag-coastline / HMaxSeed
(unchanged, 1.289169) / classify on all 36 table fields and 4 plates. Batch:
BatchRoot(6, "offshore_organic_tune") — exactly 4 plates (three densities on 1063685222,
density_mid on 424242, the table's sparsest south) + count_table.md/.csv + diagnosis.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EY3ZTF6NwzF8ukBHQXSK7
373 lines
21 KiB
Markdown
373 lines
21 KiB
Markdown
# Tools — the offline generator
|
||
|
||
**Holds:** the world generator and its diagnostics. Everything that produces a blueprint, and
|
||
nothing that consumes one at play time.
|
||
|
||
**Boundary — the tools wall:**
|
||
|
||
> ### ⚠⚠ TOOLS MAY NOT REFERENCE `Client/`. THE DEPENDENCY RUNS ONE WAY, OR THE WALL IS NOT A WALL.
|
||
>
|
||
> No UI, no player controllers, no shaders, no rendering code. `Tools/` may use `Core/`.
|
||
>
|
||
> **Two reasons, both from `Design - Tooling - Offline Generation.md`:** a lighter shipped client,
|
||
> and — less obviously — **not handing players the world-generation logic.** Shipping the generator
|
||
> lets it be reverse-engineered into an unfair map preview, with the whole island's layout, every
|
||
> settlement and every route known before anyone sets foot on the beach.
|
||
>
|
||
> **Tools emit data; they never write to a live save.** A generator that could touch live server
|
||
> state is a corruption risk for no benefit.
|
||
|
||
## What is here
|
||
|
||
### The generator — pass 1 (Phase 1)
|
||
|
||
**Ported from the reference's `MapGenerator.GenerateTopography` pass-1 loop** at tag
|
||
`pre-rewrite-reference` (`ab78883`). This is the crown-jewel port: **working code and its tuned
|
||
constants, carried over verbatim — not re-derived from a design summary** (→ D-050,
|
||
`Design - Rewrite - Extraction Manifest.md`).
|
||
|
||
| File | What it is |
|
||
|---|---|
|
||
| `Scripts/TerrainNoise.cs` | ⭐ The FastNoiseLite config, **every fractal property pinned explicitly** |
|
||
| `Scripts/Topography.cs` | ⭐⭐ Pass 1 — the six elements, in the reference's execution order |
|
||
| `Scripts/IslandFalloff.cs` | The whole reference file now: `SmoothAbs`, the **coast shelf**, the **offshore islets** + the reshape helpers |
|
||
| `Scripts/OffshorePass.cs` | ⭐ **Pass 1b** — shelf + the **organic islet layer** (the one island mechanism) + the slop guards, over the finished pass-1 arrays, then `HMaxSeed` is retaken (chat2/05, retuned chat2/06) |
|
||
| `Scripts/OffshoreSettings.cs` | Every islet dial in one object; `Faithful()` (the reference) and `Organic()` (the reshape, tuned: **density** + **south weight** + guards). ⚠ No floor / stamps / count guarantee — chat2/05's `Hybrid()` was reverted out in chat2/06 (git history has it) |
|
||
| `Scripts/OffshoreAnalysis.cs` | Island components, N/S counts, the moat check, the separation-guard geometry, **the hemisphere convention** |
|
||
| `Scripts/OffshoreDiagnosis.cs` | The per-hemisphere **measurement** — valid-zone area, binding gate, noise peaks over threshold (chat2/06 §2) |
|
||
| `Scripts/TagOverlayRenderer.cs` | The island-tag / hemisphere debug overlay |
|
||
| `Scripts/OffshoreIslandsTool.cs` + `Scenes/OffshoreIslandsTool.tscn` | The offshore batch — chat2/06: 4 plates + the count table + the diagnosis (the chat2/05 version is at `3b96e06`) |
|
||
| `Scripts/Pass1Result.cs` | The height field **and the Phase-2 seams** |
|
||
| `Scripts/TerrainGenConfig.cs` | Config + the per-element ablation toggles |
|
||
| `Scripts/HeightField.cs` | Raw `.f32` save/load — **the generation/presentation seam** |
|
||
| `Scripts/TerrainGenTool.cs` | Batch entry point (ladder + seed batch + `INDEX.md`) |
|
||
| `Scenes/TerrainGenTool.tscn` | Run this |
|
||
|
||
### The generator — pass 2a: the redistribution curve (Phase 2, chat2/01)
|
||
|
||
**Ported from `REFERENCE:Tools/Scripts/HeightCurve.cs` + `TerrainDetailPass.cs`** at the same tag.
|
||
The curve itself lives in `Core/` (engine-free, a named C++-candidate seam); `Tools/` carries the
|
||
wiring, the measuring instrument and the batch.
|
||
|
||
| File | What it is |
|
||
|---|---|
|
||
| `Scripts/Shaping.cs` | ⭐⭐ Pass 2a — curve + detail per column, producing **the two height fields** |
|
||
| `Scripts/Pass2Result.cs` | The render field, the classify field, and `FieldsAreAliased` |
|
||
| `Scripts/LandHistogram.cs` | ⭐ The land CDF — **the calibration engine AND the diagnostic** |
|
||
| `Scripts/HistogramRenderer.cs` | The labelled distribution plot (clipped axis, marked) |
|
||
| `Scripts/ShapingOracle.cs` | ⭐⭐ The four automatic correctness checks |
|
||
| `Scripts/CurveBaselineTool.cs` | The chat2/01 batch: calibrate → variants → histograms → oracle |
|
||
| `Scenes/CurveBaselineTool.tscn` | Run this |
|
||
|
||
> ### ⭐⭐ THE TWO-FIELD SPLIT (D-046) STARTS HERE
|
||
>
|
||
> `heightClassify` is **raw, uncurved, un-detailed** — bit-for-bit the pass-1 field.
|
||
> `height` is **curved and detailed**, and is what gets drawn and (later) eroded and carved.
|
||
>
|
||
> Everything that CLASSIFIES the world (biomes, water bodies, the ocean fill) will read the classify
|
||
> field; everything that DRAWS it reads the render field. Nothing consumes the classify field yet —
|
||
> it is established at the curve because **the curve is where the second field is born**, and
|
||
> retrofitting a classify path after three passes have run on one array is how the two silently
|
||
> diverge. `Pass2Result.FieldsAreAliased` is true when the curve is off (both point at one array, as
|
||
> the reference did) — a later pass that writes one while reading the other must read both into
|
||
> locals first.
|
||
|
||
> ### ⭐ THE KNOTS ARE MEASURED, NOT CHOSEN
|
||
>
|
||
> The curve's six input knots are **P60/73/83/88/96/99 of the pooled land height distribution**,
|
||
> which is what makes the band shares 60/13/10/5/8/3/1 exact by construction. The reference shipped
|
||
> the six resulting literals and threw the instrument away — so its knots could never be re-derived,
|
||
> only trusted. `LandHistogram` is that instrument, rebuilt.
|
||
>
|
||
> ⚠ **A knot set is only valid for the distribution it was measured on.** Re-run
|
||
> `CurveBaselineTool` after any change to pass 1.
|
||
|
||
```bash
|
||
xvfb-run -a Godot_v4.7.2-stable_mono_linux.x86_64 \
|
||
--path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/CurveBaselineTool.tscn
|
||
```
|
||
|
||
`ISLA_MAPSIZE` (default 2048) · `ISLA_SEEDS` (6 pinned) · `ISLA_SHOWPIECE_SIZE` (8192) ·
|
||
`ISLA_SHOWPIECE=0` · `ISLA_VARIANTS=0` (calibration-only probe) · `ISLA_PHASE1_SOURCE` ·
|
||
`ISLA_SKIP_RAW=1`
|
||
|
||
**The oracle runs before anything is looked at**, and the tool exits non-zero if any check fails:
|
||
|
||
| | Check |
|
||
|---|---|
|
||
| `a` | curve OFF is bit-identical to Phase 1's pass-1 output |
|
||
| `a′` | pass 1 is bit-identical to Phase 1's `.f32` dump |
|
||
| `b` | the classify field is bit-identical to the raw pre-curve field, curve on or off |
|
||
| `c` | the effective per-seed curve is strictly monotonic (24-corner sweep, throws otherwise) |
|
||
| `d` | realized land shares match 60/13/10/5/8/3/1 |
|
||
|
||
### The map renders — presentation (Phase 1, the look)
|
||
|
||
**Presentation only** (→ `Design - Rendering - Roughness Is Presentation.md`): it changes how height
|
||
is *shown*, never how it is *made*. It **cannot** change the terrain — it is handed a height field
|
||
loaded from a `.f32` dump and has no way to produce one.
|
||
|
||
| File | What it is |
|
||
|---|---|
|
||
| `Scripts/GrayscaleRenderer.cs` | ⭐ Plain grayscale — **for judging the noise itself** |
|
||
| `Scripts/ReliefPalette.cs` | The hypsometric palettes, stops placed on measured percentiles |
|
||
| `Scripts/ReliefRenderer.cs` | The tint (+ optional hillshade) blend |
|
||
| `Scripts/Hillshade.cs` | Horn 3×3 shaded relief; the `ZExaggeration` slope table |
|
||
| `Scripts/LegendRenderer.cs` | ⭐ The elevation legend strip |
|
||
| `Scripts/TinyFont.cs` | A 5×7 bitmap font, so a legend needs no viewport |
|
||
| `Scripts/LookConfig.cs` | The look dials and the named variants |
|
||
| `Scripts/ReviewBatchTool.cs` | ⭐ The review batch — grayscale + hero gradient + labelled relief |
|
||
| `Scripts/ReliefRenderTool.cs` | Taste-gate batch runner |
|
||
| `Scenes/ReviewBatchTool.tscn`, `Scenes/ReliefRenderTool.tscn` | Run these |
|
||
|
||
> ### ⭐ The pretty map is the WIDE GRADIENT, rendered FLAT. Relief is not the hero.
|
||
>
|
||
> A reference relief map gets its look from **wide colour + subtle relief + COHERENT TERRAIN.** We
|
||
> have the first. The third is Phase 2's curve and erosion. On raw pass-1 noise a hillshade has
|
||
> nothing coherent to shade, so it renders fine fractal bumpiness as **visual fuzz that actively
|
||
> hides the elevation the colour is showing.** So:
|
||
>
|
||
> | Look | Role |
|
||
> |---|---|
|
||
> | **`gradient_flat`** | ⭐ **the hero.** Wide Costa-Rica gradient, `HillshadeStrength = 0`, no relief at all |
|
||
> | `subtle_relief` | gentle relief (zex 18, strength 0.30) for comparison — **still fuzzes on raw noise** |
|
||
> | ⚠ `diagnostic_relief` | **A DEV VIEW, NEVER THE PRETTY MAP.** Strong exaggeration to make slope artifacts jump out. Its bumpiness is exaggerated *slope*, not extra terrain |
|
||
> | `atlas`, `dusk` | the task-03 plates, kept reachable |
|
||
>
|
||
> Relief comes into its own once erosion carves ridges and valleys worth lighting.
|
||
|
||
**Plain grayscale** (`GrayscaleRenderer`) normalizes a field to its own `[min,max]`. ⚠ This is the
|
||
one place per-image normalization is *correct* — everywhere else anchors are fixed so images compare.
|
||
Here the point is to see one field at full contrast, so the range used is printed and written into the
|
||
`INDEX.md`, and a shade reads back to a height. **You cannot judge noise through a palette:** a ramp
|
||
bends the value distribution and a hillshade adds shape the data does not have.
|
||
|
||
**The legend** (`LegendRenderer`) draws a colour bar with ticks from the palette's own ramp, so it
|
||
cannot drift from the map beside it. ⚠ **Ticks are RELATIVE height, not metres** — the metres
|
||
conversion is Phase 2's elevation profile, and labelling the bar "m" now would invent a fact. It says
|
||
so on the image. Text comes from `TinyFont`, a 5×7 bitmap font, specifically so a legend does not drag
|
||
in the SubViewport capture path (which awaits render frames and is why `--headless` hangs).
|
||
|
||
```bash
|
||
Godot_v4.7.2-stable_mono_linux.x86_64 --headless \
|
||
--path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/ReliefRenderTool.tscn
|
||
```
|
||
|
||
`ISLA_LOOKS=atlas,relief,dusk` · `ISLA_SOURCE` (batch to read `.f32` from) · `ISLA_MAPSIZE` ·
|
||
`ISLA_SEEDS` · `ISLA_BATCH` · `ISLA_DUMP_RAW=1`
|
||
|
||
**Three looks, deliberately** — a subjective gate drowns in a wall of near-duplicates, and each pair
|
||
isolates one question: `atlas` vs `relief` asks how strong the relief should be (same palette and
|
||
light); `atlas` vs `dusk` asks about palette and sun angle.
|
||
|
||
> ### ⚠ Vertical exaggeration is required, and it is a LOOK dial — not a physical claim.
|
||
>
|
||
> Height is in raw noise units over a 1 m grid, so the true per-pixel gradient is tiny: measured
|
||
> median land slope is **0.22°**. Un-exaggerated, the whole island shades as a flat plane. The raw
|
||
> units are **not metres** — the metres-per-unit conversion is Phase 2's, not this renderer's.
|
||
|
||
**The blend, and why it is not a multiply.** Hillshade on flat ground is `sin(altitude)` ≈ 0.71, so a
|
||
plain `tint × shade` darkens the entire map by 29% before any slope is involved and the hypsometric
|
||
tints are never actually seen. So the shade is normalized by its flat-ground value first — flat
|
||
terrain keeps its true tint, and only *slope* moves the colour — then shadows **multiply** while
|
||
highlights **screen** toward white. That asymmetry is the difference between a colour ramp and a map
|
||
you would frame.
|
||
|
||
**⚠ Relief fades out with depth below sea.** Not only taste: the deep floor is dominated by the
|
||
Trench, a synthetic additive wall whose gradient is ~1.5× the p99 *land* gradient. Shading it
|
||
faithfully draws a bright rim around the whole map and lights up the abyss with base-noise mottle —
|
||
relief on terrain nobody is meant to look at. The fade puts relief where the bathymetry is real (the
|
||
near-shore shelf) and lets the abyss lie flat, which is the cartographic convention anyway.
|
||
|
||
```bash
|
||
Godot_v4.7.2-stable_mono_linux.x86_64 --headless \
|
||
--path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/TerrainGenTool.tscn
|
||
```
|
||
|
||
`ISLA_MAPSIZE` (default 2048) · `ISLA_SEEDS` (comma-separated, positive) · `ISLA_BATCH` ·
|
||
`ISLA_SKIP_RAW=1` · `ISLA_LADDER=0`
|
||
|
||
**The six ported elements, in order** — the order is load-bearing, not an implementation detail:
|
||
|
||
1. **wobbled latitude scalar** — `y/MapSize` plus a ±0.1 low-frequency wobble
|
||
2. **island falloff/mask** — squircle + ellipse, 50/50, then `Pow(·, 2.5f)`
|
||
3. **edge roughness** — `noise(x·2.5, y·2.5)`, modulated by the squircle so it bites only at the rim
|
||
4. **southern sinker** — bottom 25%, **before** the power (its effect is superlinear)
|
||
5. **the Trench** — map-anchored, additive on both axes, **after** the power
|
||
6. **mountain spine** — `SmoothAbs` crest, cubic, southern fade, amplitude `0.6f`
|
||
|
||
> ### ⚠ The latitude scalar is NOT climate temperature.
|
||
>
|
||
> The reference called it `temperature` and let biomes read the same array — a large part of how
|
||
> climate and terrain got fused. Here it is a **stage-1-local latitude field that terrain geometry
|
||
> reads**, and nothing else. Climate is **stage 3** and classifies finished shape (D-049 §2, D-056).
|
||
> Do not alias, store, or rename this into a climate map.
|
||
|
||
**Pass 1b — the coast shelf and the offshore islets (chat2/05).** The reference continued its pass-1
|
||
loop with the submarine **coast shelf** and the **offshore islet** layer (~:621-664); v2 runs them as a
|
||
second sweep over the finished arrays — `OffshorePass` — with the same per-pixel arithmetic in the same
|
||
order, then **retakes `HMaxSeed` after them**, as the reference did (chat2/00 Drift §2, closed). The
|
||
islets exist in two presets: `OffshoreSettings.Faithful()` (the reference, verbatim — the control) and
|
||
`OffshoreSettings.Organic()` (the reshape: small / low / flat / crisp, corners allowed — **the organic
|
||
noise-field layer is the only island mechanism**, tuned for coverage by **density** and a **south
|
||
weight**, with speck / separation / blob guards; chat2/06). ⚠ **No forced count.** chat2/05's seeded
|
||
floor (`Hybrid()`: stamps guaranteeing ≥2 N / ≥4 S) was tried and **reverted out on look** in chat2/06;
|
||
the per-hemisphere counts are a statistical outcome of the tuning, read off the batch's count table.
|
||
|
||
> ### ⚠ Both default OFF — deliberately, and that is a decision to revisit.
|
||
>
|
||
> Every oracle in this phase holds pass 1 against Phase 1's `.f32` dumps. The shelf changes every
|
||
> below-sea cell and the islets ADD LAND, so defaulting either ON stales every regression anchor at
|
||
> once. Batch tools turn them on explicitly. **Flipping the defaults is the act that retires the
|
||
> Phase-1 dumps — do it in a task that re-baselines the oracles.** → `TerrainGenConfig`.
|
||
|
||
> ### ⭐ The island tag — data, set here, read by nothing yet.
|
||
>
|
||
> `Pass1Result.IsOffshoreIsland` / `IslandHemisphere` (carried through `Pass2Result`) mark every cell
|
||
> the islet layer lifted above sea, with its hemisphere. **NORTH = rows `[0, MapSize/2)`, SOUTH =
|
||
> `[MapSize/2, MapSize)`** — y runs south, as the spine's southern fade and the southern sinker
|
||
> already encode. A biome / fertility / placement pass reads it from there and never re-derives
|
||
> island-land from geometry. Null when offshore is off.
|
||
|
||
> ### ⚠ The one honest coupling: islets TURN WATER INTO LAND.
|
||
>
|
||
> Unlike the curve, which moves heights but not the waterline, the islet layer adds above-sea cells —
|
||
> new classification downstream. That is why it runs in the base shape before anything classifies:
|
||
> change an island dial, regenerate, and classification re-runs consistently. A known property, not a
|
||
> surprise.
|
||
|
||
**Not here at all:** erosion, rivers, water bodies, the crater carve, biomes, roads, the mesher.
|
||
|
||
### ⚠ The render writes a `Godot.Image` directly — not the capture path
|
||
|
||
The reference captured maps through a SubViewport + a `TextureRect._Draw`, because it composited
|
||
**vector overlays** (roads, town markers, river polylines) onto the raster. That machinery awaits
|
||
render frames — which is exactly why unattended runs need `xvfb-run` and why `--headless` hangs on
|
||
it.
|
||
|
||
**Phase 1 has no overlays.** A heightmap is a raster, so it is written per-pixel into an `Image` and
|
||
saved: no viewport, no frame awaits, no display. **Do not reintroduce the capture path by habit** —
|
||
it becomes correct the moment something vector needs compositing, and not before.
|
||
|
||
### Diagnostics
|
||
|
||
- `Scenes/UserDirProbe.tscn` — confirms this project's `user://` is its own, not the old
|
||
prototype's. Exits non-zero on a clash.
|
||
- `Scenes/NoiseDefaultsProbe.tscn` — prints this engine's `FastNoiseLite` constructor defaults and
|
||
checks them against the recorded baseline. **Run it after any Godot upgrade.** It cannot change
|
||
the terrain (`TerrainNoise` pins every value); it makes a drifting default *visible* instead of
|
||
silent.
|
||
|
||
---
|
||
|
||
## ⭐ The conventions Phase 1's generator inherits
|
||
|
||
These were **earned, not assumed** — each one came out of the prototype's terrain arc.
|
||
→ `Design - Tooling - Iteration and Batching.md`.
|
||
|
||
### 1. `xvfb-run` for unattended generation — NOT `--headless`
|
||
|
||
> ### ⚠ `--headless` HANGS on a real generation run.
|
||
>
|
||
> The snapshot capture **awaits render frames, and there is no frame loop without a display.**
|
||
> **Measured, not assumed.** A run left on `--headless` does not fail loudly; it sits there.
|
||
|
||
```bash
|
||
xvfb-run -a Godot_v4.7.2-stable_mono_linux.x86_64 --path ~/celerNexus/islaApocalypse-v2 <scene>
|
||
```
|
||
|
||
A windowed run on the desktop also invites being closed mid-run by whoever is using the machine.
|
||
|
||
**The one exception is a job that awaits no frames** — like `UserDirProbe`, which is why that one
|
||
documents `--headless` explicitly:
|
||
|
||
```bash
|
||
Godot_v4.7.2-stable_mono_linux.x86_64 --headless \
|
||
--path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/UserDirProbe.tscn
|
||
```
|
||
|
||
**If a job awaits a frame, it needs `xvfb-run`. When unsure, use `xvfb-run`** — it is correct in
|
||
both cases and costs nothing.
|
||
|
||
### 2. Environment overrides — safe BY CODE, not by care
|
||
|
||
Every path a tool reads or writes resolves through `Core/Scripts/ToolingPaths.cs`, and each has an
|
||
environment override, so a batch **cannot** read over or write to the developer's live files:
|
||
|
||
| Variable | Overrides | Default |
|
||
|---|---|---|
|
||
| `ISLA_CONFIG_PATH` | the generation config file | `user://config.json` |
|
||
| `ISLA_BLUEPRINT_PATH` | blueprints read/written | `user://blueprints` |
|
||
| `ISLA_OUTPUT_DIR` | generation output (batches live under it) | `user://output` |
|
||
|
||
> ⭐ **The point is that it is enforced by CODE, not by care.** A rule that depends on an executor
|
||
> remembering it will eventually meet an executor who does not.
|
||
|
||
There is no second way to obtain these paths. Do not add one.
|
||
|
||
### 3. ⚠ File safety — permanent rules
|
||
|
||
Enforced by `Core/Scripts/FileSafety.cs`, which throws rather than advises.
|
||
|
||
1. **No deletion in the runtime root or anywhere under `batches/`.**
|
||
2. **Intermediates persist** in a `scratch/` subfolder that is **never cleaned.**
|
||
3. **The developer's placed files are never touched.**
|
||
4. **Any deletion is named explicitly in the run report.**
|
||
|
||
> ⚠ **These came from a real incident:** an executor admitted it had been deleting the developer's
|
||
> staged test blueprint. It was owned and fixed **in code.** That is why these are rules and not
|
||
> guidance.
|
||
|
||
### 4. Batch layout
|
||
|
||
```
|
||
batches/<task>_<descriptor>/<seed>_<variant>/
|
||
batches/<task>_<descriptor>/INDEX.md
|
||
batches/<task>_<descriptor>/scratch/ ← persistent; never cleaned
|
||
```
|
||
|
||
> ### ⚠⚠ THE PREFIX IS THE AUTHORING TASK NUMBER. IT IS NOT A COUNTER.
|
||
>
|
||
> `04_review` means *"the batch task 04 authored"*. It does **not** mean "the fifth batch". A task
|
||
> that produces six batches produces six `04_*` folders — not `04_` through `09_`.
|
||
>
|
||
> **This already drifted once.** Tasks 02 and 03 used the prefix as a global running counter and
|
||
> produced `00_smoke` … `05_trophy_10240` across two tasks, so nothing in a folder name said which
|
||
> task made what.
|
||
>
|
||
> **Enforced in code, not remembered:** the task number is an explicit argument to
|
||
> `ToolingPaths.BatchRoot(taskNumber, descriptor)`, which composes the prefix itself and **refuses a
|
||
> descriptor that carries its own** (`ISLA_BATCH=05_foo` → `REFUSED`, exit 2). Tools take it as
|
||
> `ISLA_TASK`.
|
||
|
||
**A/B comparisons are browsed by a human, and a flat directory of same-named PNGs is not
|
||
browsable.** The `INDEX.md` is what makes a batch readable a week later.
|
||
|
||
⚠ **Batches are OUTPUT.** They live under `ISLA_OUTPUT_DIR` (i.e. under `user://`), **not in this
|
||
repo.** The `batches/` folder here carries the convention, not the data. No batches are run this
|
||
phase.
|
||
|
||
### 5. Iteration levers, for when the generator exists
|
||
|
||
- **Skip the road pass for all terrain and water iteration.** In the prototype this cut a run from
|
||
~26 min to ~80 s — a **19× cut**, and the difference between iterating on terrain and not
|
||
iterating on terrain. Blueprints from such a run carry present-but-empty road sections, which is
|
||
**legitimate, not corrupt.**
|
||
- **⭐ Build the oracle before the taste-iteration, not after.** Because the classify path was
|
||
pinned to *uncurved* height, every shaping iteration had a hard automatic correctness check
|
||
(biome and water maps md5-identical). Five rounds of taste-iteration were safe **because
|
||
correctness was not being judged by eye.** *Where a future phase has a subjective gate, ask first
|
||
what the automatic invariant is.*
|
||
- **Config-gate every shaping change**, with the legacy behaviour surviving on the other side. It
|
||
keeps changes comparable as an A/B pair, and it means a rejected change costs a flipped default
|
||
rather than a reverted commit.
|
||
- **The lever is variants per batch, not speed per pass.** Two Phase B tasks ran ~2 hours each,
|
||
which looked like the pipeline getting slower. It was not — a single pass stayed at ~2 minutes.
|
||
The cost was A/B/ablation multiplication. **When a batch is genuinely wide, say so up front.**
|
||
|
||
### 6. Scaling discipline
|
||
|
||
**Nothing uses a raw pixel number.** Every distance, radius, threshold and noise frequency comes
|
||
from `Core/Scripts/GenerationScale.cs`. Read its header before adding any constant — including the
|
||
tightening on **normalized additive noise offsets**, which is a forward-guard for the Phase 1 noise
|
||
port. → `Design - Tooling - Scaling Discipline.md`.
|