Editor: redesign di rilievi, caldere e strade (F0-F6) #282

Closed
opened 2026-08-08 10:10:53 +02:00 by panda · 0 comments
Collaborator

Gli strumenti altimetrici dell'editor autorano heightfield geometricamente incoerenti: montagne a "torta nuziale", caldere ottenute sovrapponendo stamp circolari, strade dipinte sopra pareti mai raccordate. Il pianeta verrà ridisegnato; questa epica impedisce che il prossimo abbia gli stessi difetti.

Design review completa e Invariants Check: nel documento di mandato dell'owner, riassunti qui.

Diagnosi verificata sul branch

Tutti e nove i punti confermati riga per riga:

  1. Landform.Apply cuce per costruzione — riceve readZ, lo passa a ThrowIfNull e non lo usa mai; scrive BaseZ + (PeakZ-BaseZ)·falloff su tutto il disco.
  2. Terraced = la torta nuzialefloor(z/TerraceStep)·TerraceStep, default 25, e i riser misurati nel vulcano sono 25-26. MaxGradient per Terraced ritorna height/radius: il commento nel codice ammette che i riser sono verticali.
  3. Tre percorsi stradali — freehand e harness passano da Trace.Bake; Route (A→B) stampa con Brush.Square e non ha spalle affatto.
  4. Freehand non transazionale — dipinge durante il drag, cuoce al rilascio; se il bake fallisce il terreno dipinto resta.
  5. Spalle fisse ≠ raccordoTrace.Bake tocca solo ±Shoulder (12) e clampa; all'anello esterno resta un salto. 175 Z a gradiente 2 richiedono 88 celle.
  6. Semantiche di larghezza discordantiBrush.Square(size) è totale, Trace.Width è raggio Chebyshev: con _brushSize=1 la route dipinge 1 cella, il trace ne cuoce 3.
  7. Switchback — a parità di distanza vince la quota più alta, silenziosamente.
  8. NaturalizzazioneNaturalise.Erode usa 4 vicini, il movimento 8; ZSmoother riscrive solo le celle pennellate.
  9. Undo assente — dichiarato slice futura in ChunkedEditModel.

Divergenze rispetto al mandato

  • La scala verticale del client è 16, non 4. IsoProjection.ZScale = 16f, viva, usata da 9 file, con il commento che registra la decisione del 2026-08-04 ("Raised from UO's squashed 4 to 16 … mountains should loom"); ZScale3D = IsoProjection.ZScale. Il z*4 è UO classico. Chiuso dall'owner: si va a 16. EditorCamera.ZScale è invece davvero un campo morto (mai letto da Basis/Project) e va rimosso.
  • Il terrain di pendenza si chiama mountain_rock, non mountain. Ha maxSlopeZ 2, cioè esattamente SustainableRisePerStep: policy materiale e regola di pendenza si rinforzano.
  • Il bordo del crop non è una cucitura e va escluso dalle misure.

Modello dati

Due tipi, non uno — è ciò che rende raggiungibile il budget di 64 MiB:

  • TerrainEditPlan transitorio e denso (Before/After Z + terrain, bitmask ulong[], diagnostica, CanCommit, RejectionReason), uno vivo per volta, mostra EstimatedUndoBytes prima del commit;
  • TerrainPatch persistente e sparso (solo celle cambiate) dentro una EditTransaction per gesto, che ripristina Z e terrain insieme.

Denso al limite di 2 M celle costerebbe 12 MB a transazione (5 nella history); sparso ~3,6 MB (una ventina).

Algoritmi

  • Ridge — campo distanza da spline: target = lerp(crestZ(t), existingZ(cell), smoothstep(|d|/width(t))). A |d| ≥ width il target è il terreno esistente: cucitura zero per costruzione. Rami uniti con smooth-max, mai max duro. Picco = massimo locale di crestZ, sella = minimo: nessuna forma sovrapposta.
  • Caldera — una sola z = f(cell) in frame ellittico ruotato, raggi modulati per angolo da rumore a bassa frequenza; profilo continuo esterno → raccordo → versante → cresta → parete interna → fondo, dove il raccordo è il primo tratto e tende a existingZ. Brecce = settori angolari che abbassano la cresta, e basta.
  • Spalle adattive — Dijkstra multi-sorgente a 8 direzioni dalla carreggiata; a distanza d la banda è roadZ ± d·g; una cella già in banda tiene la Z naturale e chiude il raccordo; oltre MaxEarthworkRadius con frontiere ancora aperte → rifiuto con la distanza minima richiesta. Postcondizione verificata sul risultato, non assunta.
  • Conflitti — due sorgenti con |roadZ₁ − roadZ₂| > 2·d·g sulla stessa cella → conflitto esplicito, mai la quota più alta.
  • Materiale — maschera calcolata dopo la Z da pendenza ed elevazione, soglia modulata da rumore world-space a bassa frequenza (che produce chiazze connesse; la scacchiera nasce solo da rumore per-cella), terrain protetti esclusi, SlopeTerrainId tipizzato senza switch sul bioma.

Fasi

  • F0 — baseline e fixture (cc978d0)
  • F1 — harness e usabilità 3D: camera/view/overlay/screenshot senza exit, chrome nascondibile, goto, camera persistita, rimozione ZScale morto
  • F2 — validatore discreto a 8 direzioni + overlay slope/walk/reach/seam
  • F3 — transazioni e undo/redo — gate duro: nessun commit di Ridge/Caldera prima
  • F4 — Landform corretto + CliffTerraces + Ridge + policy materiale
  • F5 — Caldera
  • F6 — pipeline stradale unica

Mai F4-F6 in un unico commit.

F0 — fatto

content/worlds/caldera-fixture: crop 512×512 da X 7288..7799, Y 4032..4543, 792 KB, sha256 1747e28d…, apribile da editor e client. Provenienza, tabella coordinate mondo→locale dei KO noti e baseline in PROVENANCE.md, con i numeri pinnati da CalderaFixtureTests: modificare la fixture rompe la build invece di spostare silenziosamente la baseline.

Baseline sulla Z effettiva (BaseZ + rilievo), bordo del crop escluso:

edges (8-connessi) 1 037 342
piatti (Δz = 0) 860 359 — 82,9 %
sostenibili come rotta (Δz ≤ 2) 1 012 526 — 97,6 %
gradino singolo (2 < Δz ≤ 5) 7 465 — 0,7 %
parete (Δz > 5) 17 351 — 1,7 %
passo massimo ortogonale / diagonale 175 / 175
range Z 0 .. 180

La patologia non è "è quasi tutto ripido": è che l'82,9% degli edge non ha alcun dislivello e le transizioni fra i ripiani sono pareti fino a 175 Z in una cella. Solo il 14,7% è pendenza davvero percorribile. È la firma di floor(z/25)·25.

Definition of Done

Quella del mandato §14, integralmente.

Gli strumenti altimetrici dell'editor autorano heightfield geometricamente incoerenti: montagne a "torta nuziale", caldere ottenute sovrapponendo stamp circolari, strade dipinte sopra pareti mai raccordate. Il pianeta verrà ridisegnato; questa epica impedisce che il prossimo abbia gli stessi difetti. Design review completa e Invariants Check: nel documento di mandato dell'owner, riassunti qui. ## Diagnosi verificata sul branch Tutti e nove i punti confermati riga per riga: 1. **`Landform.Apply` cuce per costruzione** — riceve `readZ`, lo passa a `ThrowIfNull` e non lo usa mai; scrive `BaseZ + (PeakZ-BaseZ)·falloff` su tutto il disco. 2. **`Terraced` = la torta nuziale** — `floor(z/TerraceStep)·TerraceStep`, default 25, e i riser misurati nel vulcano sono 25-26. `MaxGradient` per `Terraced` ritorna `height/radius`: il commento nel codice ammette che i riser sono verticali. 3. **Tre percorsi stradali** — freehand e harness passano da `Trace.Bake`; `Route (A→B)` stampa con `Brush.Square` e **non ha spalle affatto**. 4. **Freehand non transazionale** — dipinge durante il drag, cuoce al rilascio; se il bake fallisce il terreno dipinto resta. 5. **Spalle fisse ≠ raccordo** — `Trace.Bake` tocca solo `±Shoulder` (12) e clampa; all'anello esterno resta un salto. 175 Z a gradiente 2 richiedono 88 celle. 6. **Semantiche di larghezza discordanti** — `Brush.Square(size)` è totale, `Trace.Width` è raggio Chebyshev: con `_brushSize=1` la route dipinge 1 cella, il trace ne cuoce 3. 7. **Switchback** — a parità di distanza vince la quota più alta, silenziosamente. 8. **Naturalizzazione** — `Naturalise.Erode` usa 4 vicini, il movimento 8; `ZSmoother` riscrive solo le celle pennellate. 9. **Undo assente** — dichiarato slice futura in `ChunkedEditModel`. ### Divergenze rispetto al mandato - **La scala verticale del client è 16, non 4.** `IsoProjection.ZScale = 16f`, viva, usata da 9 file, con il commento che registra la decisione del 2026-08-04 (*"Raised from UO's squashed 4 to 16 … mountains should loom"*); `ZScale3D = IsoProjection.ZScale`. Il `z*4` è UO classico. **Chiuso dall'owner: si va a 16.** `EditorCamera.ZScale` è invece davvero un campo morto (mai letto da `Basis`/`Project`) e va rimosso. - Il terrain di pendenza si chiama **`mountain_rock`**, non `mountain`. Ha `maxSlopeZ 2`, cioè esattamente `SustainableRisePerStep`: policy materiale e regola di pendenza si rinforzano. - Il **bordo del crop non è una cucitura** e va escluso dalle misure. ## Modello dati Due tipi, non uno — è ciò che rende raggiungibile il budget di 64 MiB: - `TerrainEditPlan` **transitorio e denso** (Before/After Z + terrain, bitmask `ulong[]`, diagnostica, `CanCommit`, `RejectionReason`), uno vivo per volta, mostra `EstimatedUndoBytes` prima del commit; - `TerrainPatch` **persistente e sparso** (solo celle cambiate) dentro una `EditTransaction` per gesto, che ripristina Z e terrain insieme. Denso al limite di 2 M celle costerebbe 12 MB a transazione (5 nella history); sparso ~3,6 MB (una ventina). ## Algoritmi - **Ridge** — campo distanza da spline: `target = lerp(crestZ(t), existingZ(cell), smoothstep(|d|/width(t)))`. A `|d| ≥ width` il target **è** il terreno esistente: cucitura zero per costruzione. Rami uniti con smooth-max, mai `max` duro. Picco = massimo locale di `crestZ`, sella = minimo: nessuna forma sovrapposta. - **Caldera** — una sola `z = f(cell)` in frame ellittico ruotato, raggi modulati per angolo da rumore a bassa frequenza; profilo continuo `esterno → raccordo → versante → cresta → parete interna → fondo`, dove il raccordo **è** il primo tratto e tende a `existingZ`. Brecce = settori angolari che abbassano la cresta, e basta. - **Spalle adattive** — Dijkstra multi-sorgente a 8 direzioni dalla carreggiata; a distanza `d` la banda è `roadZ ± d·g`; una cella già in banda tiene la Z naturale e **chiude** il raccordo; oltre `MaxEarthworkRadius` con frontiere ancora aperte → rifiuto con la distanza minima richiesta. Postcondizione verificata sul risultato, non assunta. - **Conflitti** — due sorgenti con `|roadZ₁ − roadZ₂| > 2·d·g` sulla stessa cella → conflitto esplicito, mai la quota più alta. - **Materiale** — maschera calcolata **dopo** la Z da pendenza ed elevazione, soglia modulata da rumore world-space a bassa frequenza (che produce chiazze connesse; la scacchiera nasce solo da rumore per-cella), terrain protetti esclusi, `SlopeTerrainId` tipizzato senza switch sul bioma. ## Fasi - [x] **F0 — baseline e fixture** (`cc978d0`) - [ ] **F1 — harness e usabilità 3D**: camera/view/overlay/screenshot senza exit, chrome nascondibile, goto, camera persistita, rimozione `ZScale` morto - [ ] **F2 — validatore discreto a 8 direzioni + overlay** slope/walk/reach/seam - [ ] **F3 — transazioni e undo/redo** — gate duro: nessun commit di Ridge/Caldera prima - [ ] **F4 — `Landform` corretto + `CliffTerraces` + `Ridge` + policy materiale** - [ ] **F5 — `Caldera`** - [ ] **F6 — pipeline stradale unica** Mai F4-F6 in un unico commit. ## F0 — fatto `content/worlds/caldera-fixture`: crop 512×512 da `X 7288..7799, Y 4032..4543`, 792 KB, sha256 `1747e28d…`, apribile da editor e client. Provenienza, tabella coordinate mondo→locale dei KO noti e baseline in `PROVENANCE.md`, con i numeri **pinnati da `CalderaFixtureTests`**: modificare la fixture rompe la build invece di spostare silenziosamente la baseline. Baseline sulla Z **effettiva** (`BaseZ` + rilievo), bordo del crop escluso: | | | | --- | --- | | edges (8-connessi) | 1 037 342 | | piatti (Δz = 0) | 860 359 — **82,9 %** | | sostenibili come rotta (Δz ≤ 2) | 1 012 526 — 97,6 % | | gradino singolo (2 < Δz ≤ 5) | 7 465 — 0,7 % | | **parete (Δz > 5)** | **17 351 — 1,7 %** | | passo massimo ortogonale / diagonale | **175 / 175** | | range Z | 0 .. 180 | La patologia non è "è quasi tutto ripido": è che l'82,9% degli edge **non ha alcun dislivello** e le transizioni fra i ripiani sono **pareti fino a 175 Z in una cella**. Solo il 14,7% è pendenza davvero percorribile. È la firma di `floor(z/25)·25`. ## Definition of Done Quella del mandato §14, integralmente.
panda closed this issue 2026-08-08 15:41:58 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo#282
No description provided.