From 9454b9e5527bc673cfbd62f6c3a985899dea5670 Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 6 Oct 2026 21:33:57 +0200 Subject: [PATCH] docs: brief and plan for varied dive-site landforms Co-Authored-By: Claude Fable 5.1 --- docs/briefs/terrain-varied-landforms.md | 179 ++++++++++++++++++ .../2026-10-06-terrain-varied-landforms.md | 56 ++++++ 2 files changed, 235 insertions(+) create mode 100644 docs/briefs/terrain-varied-landforms.md create mode 100644 docs/superpowers/plans/2026-10-06-terrain-varied-landforms.md diff --git a/docs/briefs/terrain-varied-landforms.md b/docs/briefs/terrain-varied-landforms.md new file mode 100644 index 0000000..f721473 --- /dev/null +++ b/docs/briefs/terrain-varied-landforms.md @@ -0,0 +1,179 @@ +# Proposition — formes de relief variées pour les sites de plongée + +**Statut : exécuté en autonomie le 2026-10-06** sur demande d'Olivier +(« implémente ce genre de terrains variés, terrain statique, ne te concentre que +sur le terrain, pas les effets de rendu ou la végétation »). Les décisions +ouvertes sont listées en §8. + +Rédigée à partir de cinq photos de référence fournies par Olivier, de l'ADR +0012, de `tools/site_terrain_baker.gd` et du brief +`docs/briefs/terrain-site-zone1-gameplay.md`. + +--- + +## 1. Objectif + +Étendre le vocabulaire de formes du script de cuisson pour que les sites +suivants (U-Boot « sous falaise », navire océanographique « dans un canyon », +GDP §15) puissent être authorés avec le même pipeline que le Banc du Vigilant : +canyons à parois raides et strates, crevasses étroites, éperons et chaos de +blocs, buttes rocheuses, falaises sinueuses. Terrain statique, surface unique +(ADR 0010), aucun changement de rendu ni de végétation. + +Terminé quand : + +- chaque forme des cinq références a une primitive nommée, testée unitairement, + dans `tools/site_terrain_baker.gd` ; +- un site de démonstration cuit (`content/sites/terrain_showcase.tres`) montre + les cinq formes côte à côte, se charge en moins de 3 s et passe des tests de + contrat géométriques (profondeur, pente, largeur, matériau) ; +- le Banc du Vigilant est cuit à l'identique (aucun octet de + `zone1_vigilant_terrain.res` ne change) ; +- `bash scripts/quality_gate.sh` passe. + +## 2. Lecture des références + +| Photo | Ce qu'on voit | Forme de relief | Ce qu'un champ de hauteur peut en garder | +|---|---|---|---| +| 1 | Deux masses rocheuses alvéolées encadrant un chenal de sable grossier | éperons rocheux + chenal | deux buttes aux bords irréguliers, chenal plat entre elles, rugosité forte sur la roche | +| 2 | Fente très étroite et profonde entre deux parois quasi verticales | crevasse | sillon de 5–6 m de large, 20 m de profondeur, parois à 80°+ ; pas de surplomb | +| 3 | Pente de mamelons rocheux arrondis | mamelons | buttes à flancs doux sur une pente, rugosité moyenne | +| 4 | Canyon aux parois verticales **stratifiées**, fond de sable plat **ridé**, mesas en haut | canyon à strates | tranchée à fond plat, parois raides quantifiées en gradins horizontaux, rides sur le fond, plateaux sommitaux | +| 5 | Platier : têtes de corail et blocs épars sur sable ridé | chaos de blocs | champ de demi-ellipsoïdes de 2–6 m de rayon, matériau roche, sable ridé entre | + +Hors périmètre, volontairement : lumière, caustiques, couleur de l'eau, +herbier, corail (rendu et végétation). + +## 3. Ce qui existe et ce qui manque + +Le cuiseur additionne des primitives `elevation_delta(x, z)` puis déduit le +matériau de la pente (roche au-dessus de 20°) et applique des `MaterialPatch` +circulaires. Les primitives actuelles (`SlopedPlane`, `Blob`, `Ridge`, +`Groove`, `Step`, `Ripples`, `Roughness`) sont toutes **lisses** (gaussiennes +ou `smoothstep`) et **mono-segment**. Il manque : + +1. des **tracés en polyligne** (canyon, crevasse, falaise sinueuse) ; +2. des **bords irréguliers** (bruit de contour) pour casser l'aspect « gaussien » ; +3. des **formes à sommet plat et flancs raides** (butte, mesa) ; +4. des **champs d'objets discrets** combinés par maximum, pas par somme + (blocs qui ne fusionnent pas en bosse) ; +5. une phase de **modification** après la somme, qui connaît la pente locale, + pour quantifier les parois en **strates** ; +6. un **matériau imposé par une primitive** (un bloc est roche même sur son + sommet plat). + +## 4. Décision : extension du cuiseur + +### 4.1 Contrat + +- `Primitive.elevation_delta(x_m, z_m) -> float` : inchangé, additif. +- `Primitive.material_at(x_m, z_m) -> int` : nouveau, renvoie + `NO_MATERIAL` (-1) par défaut ; une valeur `SonarMaterial.Value` l'emporte sur + la règle de pente, dans l'ordre des primitives, avant les `MaterialPatch`. +- `Modifier.modify(x_m, z_m, elevation_m, gradient) -> float` : nouvelle phase. + `gradient` est la pente locale `Vector2(dE/dx, dE/dz)` calculée par + différences centrées sur le champ courant ; les modificateurs s'enchaînent + dans l'ordre de `Recipe.modifiers`, après la somme et avant le plancher. +- Ordre de cuisson : somme des primitives → modificateurs → plancher + `floor_elevation_m` → matériau par pente → matériau par primitive → + `MaterialPatch`. + +### 4.2 Nouvelles primitives + +| Primitive | Paramètres | Forme | +|---|---|---| +| `Canyon` | `path: PackedVector2Array`, `half_width_m`, `depth_m`, `wall_softness_m`, `edge_seed`, `edge_amplitude_m` | tranchée à fond plat le long d'une polyligne ; parois `smoothstep` sur `wall_softness_m` ; distance au tracé perturbée par un bruit de contour ; se referme sur une demi-largeur à chaque bout | +| `Outcrop` | `centre`, `radius_x_m`, `radius_z_m`, `height_m`, `wall_width_m`, `edge_seed`, `edge_amplitude_m`, `rock_top` | butte elliptique à sommet plat, flanc de largeur `wall_width_m`, contour perturbé ; matériau roche partout où elle dépasse 30 % de sa hauteur si `rock_top` | +| `BoulderField` | `centre`, `radius_m`, `seed`, `count`, `boulder_radius_min_m`, `boulder_radius_max_m`, `height_ratio` | `count` demi-ellipsoïdes tirés par `RandomNumberGenerator` seedé dans le disque, combinés par **maximum** ; roche sur toute leur emprise | +| `Escarpment` | `path: PackedVector2Array`, `drop_m`, `softness_m`, `slope_beyond`, `edge_seed`, `edge_amplitude_m` | `Step` le long d'une polyligne : le terrain descend à **droite** du sens de parcours ; ligne de crête perturbée par un bruit de contour | + +### 4.3 Nouveau modificateur + +| Modificateur | Paramètres | Effet | +|---|---|---| +| `Strata` | `layer_height_m`, `sharpness` (0–1), `minimum_slope_degrees`, `full_slope_degrees`, `centre`, `radius_m` (0 = partout) | quantifie l'élévation en gradins de `layer_height_m` là où la pente dépasse `minimum_slope_degrees` (plein effet à `full_slope_degrees`) ; `sharpness` 0 = aucun effet, 1 = marches franches ; le fond plat et le plateau ne bougent pas | + +Formule : `f = frac(e / h)`, `f' = clamp((f - 0.5) / (1 - s) + 0.5, 0, 1)`, +`e' = lerp(e, floor(e / h) * h + h * f', w)` avec +`w = smoothstep(tan(min), tan(full), |gradient|) * masque`. + +### 4.4 Ce qui ne change pas + +Le contrat de données (`SiteTerrainData`, ADR 0012 §2), l'acteur de monde, la +collision, le sonar, le shader. Les `.res` restent les artefacts dérivés ; la +recette reste le document d'auteur. Le bruit de contour est un +`FastNoiseLite` seedé, donc déterministe. + +## 5. Site de démonstration : `terrain_showcase` + +But : prouver chaque forme en jeu et au sonar, servir de catalogue pour +l'auteur des sites suivants. Pas un site de mission : un seul placement +(balise de mouillage), aucune épave, aucune route. + +| Donnée | Valeur | +|---|---| +| Champ | 513 × 513 cellules, 1,0 m, origine (-256, -256) | +| Zone jouable | 440 × 440 m centrée | +| Plateau de base | 30 m, incliné de 4 m vers le sud | +| Profondeur min / max | 18 m / 80 m | +| Départ | (0, -12, 0), cap nord | + +Plan de masse (nord en haut) : + +```text + +---------------------------------------------+ + | NW : canyon à strates NE : crevasse | + | mesas à -24 m, tranchée fente 6 m de | + | 28 m de large, 24 m de large, 20 m | + | profond, gradins 2,5 m, de profond, | + | fond ridé entre buttes | + | [départ] | + | SW : chaos de blocs SE : éperons | + | 80 blocs r 2–6 m sur deux buttes | + | sable ridé alvéolées, | + | chenal 20 m | + | ====== falaise sinueuse (Escarpment) ===== | + | tombant vers -80 m, mamelons sur la pente | + +---------------------------------------------+ +``` + +## 6. Tests + +- **Unitaires (`run_site_baker_tests.gd`)** : `Canyon` (fond plat à la + profondeur demandée, nul hors tracé, suit le coude d'une polyligne, + fermeture aux bouts) ; `Outcrop` (sommet à `height_m`, roche sur le sommet + si `rock_top`, nul à deux rayons) ; `BoulderField` (déterministe, nul hors + disque, roche sur un bloc, maximum et non somme) ; `Escarpment` (côté droit + plus bas, drop égal à `drop_m`) ; `Strata` (une pente raide devient des + gradins : au moins deux niveaux plats ; un fond plat ne bouge pas) ; + cuisson du Banc du Vigilant identique à l'artefact versionné. +- **Contrat (`run_site_content_tests.gd`, section showcase)** : canyon + (profondeur ≥ 20 m sous la mesa, fond plat à ± 1 m sur 10 m, paroi ≥ 60° + quelque part sur une transversale, ≥ 2 replats de gradin) ; crevasse + (fond ≥ 16 m sous les bords, largeur au fond ≤ 8 m) ; chaos (roche sur un + bloc, sable entre deux blocs, ≥ 40 cellules de roche distinctes… mesuré par + fraction de roche dans le disque) ; éperons (chenal plus bas que les deux + buttes, roche sur chaque butte) ; falaise (drop ≥ 20 m entre deux points de + part et d'autre) ; bornes et profondeurs globales. +- **Intégration (`run_site_terrain_integration.gd`)** : chargement du showcase + sous 3 s, rayon dans le canyon plus bas que sur la mesa, scan de référence + lisant roche et sable. + +## 7. Documentation + +- ADR 0012 : amendement §3 (liste des primitives, phase de modification, + matériau par primitive), sans changer le contrat de données. +- `content/README.md`, `tests/README.md` : le site de démonstration. +- Captures : PNG de prévisualisation du showcase sous `docs/briefs/captures/`. + +## 8. Décisions laissées à Olivier + +1. **Résolution** : le showcase est cuit à 1,0 m par cellule (crevasse de 6 m + lisible). Les sites de mission restent à 1,5 m sauf décision contraire ; une + crevasse de 5 m à 1,5 m par cellule n'a que trois cellules de large. +2. **Strates partout ?** Le modificateur est appliqué au canyon seulement. + L'appliquer à la falaise sud du Banc du Vigilant est un changement de + recette, donc de `.res`, donc une re-cuisson à valider. +3. **Sites de mission suivants** (U-Boot sous falaise, océanographique en + canyon) : les primitives sont prêtes ; le plan de masse de chaque site + reste à proposer et valider séparément. diff --git a/docs/superpowers/plans/2026-10-06-terrain-varied-landforms.md b/docs/superpowers/plans/2026-10-06-terrain-varied-landforms.md new file mode 100644 index 0000000..6b9e59a --- /dev/null +++ b/docs/superpowers/plans/2026-10-06-terrain-varied-landforms.md @@ -0,0 +1,56 @@ +# Formes de relief variées — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Extend the bake tool with polyline canyons, noise-edged outcrops, boulder fields, polyline escarpments and a strata modifier, then bake a `terrain_showcase` site that demonstrates all five reference landforms and is covered by geometric contract tests. + +**Architecture:** `tools/site_terrain_baker.gd` gains a `Modifier` phase (after the primitive sum, with the local gradient) and a per-primitive material override hook. New primitives are nested classes next to the existing ones. A new recipe `tools/sites/terrain_showcase_recipe.gd` and its baked `content/sites/terrain_showcase*.{tres,res}` are the demonstration site. Nothing under `game/` changes. + +**Tech Stack:** Godot 4.7.2 headless, GDScript, `bash scripts/quality_gate.sh`. + +**Spec:** `docs/briefs/terrain-varied-landforms.md`. + +## Global Constraints + +- Godot binary (Git Bash): `export GODOT_BIN="/c/Users/olivi/Downloads/Godot_v4.7.2-stable_win64.exe/Godot_v4.7.2-stable_win64_console.exe"`; ripgrep: `export PATH="$PATH:/c/Users/olivi/AppData/Local/Programs/Microsoft VS Code/resources/app/node_modules/@vscode/ripgrep/bin"`. +- `tools/` depends on `game/domain/` only; nothing under `game/` or `content/` references `res://tools/` (architecture check). +- Determinism: every random element is a seeded `FastNoiseLite` or `RandomNumberGenerator`; the same recipe bakes byte-identical arrays. +- `content/sites/zone1_vigilant_terrain.res` must not change (re-bake and `git diff --stat` empty for it). +- Test runners: `TEST_COUNT` equals the number of `_expect` calls. +- Never hand-edit `.tres`/`.res`. +- Commits end with `Co-Authored-By: Claude Fable 5.1 `. + +## Tasks + +### Task 1: Modifier phase and per-primitive material hook +- `Primitive.material_at(x, z) -> int` returning `NO_MATERIAL := -1`. +- `Modifier.modify(x, z, elevation, gradient: Vector2) -> float`. +- `Recipe.modifiers: Array[Modifier]`. +- `bake()` order: sum → modifiers (gradient by central differences on the current array) → floor → slope material → primitive material → patches. +- Unit tests: a modifier that adds 1 m everywhere shifts the field; a primitive forcing ROCK on a flat plane wins over the slope rule; zone 1 recipe bakes identical to the checked-in `.res`. + +### Task 2: `Canyon` and `Escarpment` (polyline primitives) +- Shared static `distance_to_path(point, path) -> float`, `arc_position(point, path) -> Vector2(along_m, total_m)` and `signed_distance_to_path` (sign from the nearest segment's cross product, positive on the right of travel). +- Edge noise: `FastNoiseLite` seeded, `TYPE_SIMPLEX_SMOOTH`, frequency 0.05, amplitude `edge_amplitude_m`. +- Tests: Canyon depth on the flat floor, zero far away, follows a corner, closes over one half-width at the ends; Escarpment right side lower by `drop_m`, left side unchanged. + +### Task 3: `Outcrop` and `BoulderField` +- Outcrop: `e = (sqrt((dx/rx)² + (dz/rz)²) - 1) * mean_radius + noise`, `delta = height * (1 - smoothstep(-wall_width, 0, e))`, rock where `delta > 0.3 * height` and `rock_top`. +- BoulderField: `RandomNumberGenerator` seeded; disc sampling `r = radius * sqrt(u)`, angle `TAU * v`; per boulder `h = r_b * height_ratio`, profile `h * sqrt(max(0, 1 - (d / r_b)²))`; combine by `maxf`; early reject outside `radius + max_r`; rock where profile > 0. +- Tests: plateau height, rock on top, zero at two radii; boulder field deterministic, zero outside, rock on a boulder, two overlapping boulders do not sum. + +### Task 4: `Strata` modifier +- Formula from the brief §4.3. Tests: a 45° plane with 2 m layers and sharpness 0.9 produces at least two flat runs (|gradient| < 0.1) on a transect; a flat plane is unchanged; sharpness 0 is identity. + +### Task 5: `terrain_showcase` recipe, bake, registry +- Recipe per brief §5; `bake_site_terrain.gd` RECIPES gains `terrain_showcase`; bake; preview PNG under `docs/briefs/captures/`. +- Verify zone 1 re-bake identical. + +### Task 6: Contract and integration tests for the showcase +- `tests/contract/run_site_content_tests.gd`: new `_test_showcase_content()` per brief §6. +- `tests/integration/run_site_terrain_integration.gd`: showcase loads < 3 s, canyon ray lower than mesa ray, scan reads rock and sand. +- Foundation test: `select_dive_site` on the showcase is not required. + +### Task 7: Docs +- ADR 0012 amendment, `content/README.md`, `tests/README.md`, technical plan status line, brief status. +- Full gate green.