fuzzyworld

tower / commit

fix(ui): disable the slat control and remove its UI

cf37225c2c8bc28e1b756be482912489ae8987bf · Pavel Safronov · 2026-09-06 22:02:22 -0700 · parents 5e4052724b9b

fix(ui): disable the slat control and remove its UI

Changing the count did not fully work, for two independent reasons.

The terrain raster is generated from geography.json at canon's 0 slats, so
raising the count drew slat bands as a vector overlay on terrain showing
unbroken land underneath — two layers asserting different worlds. The
corridor is authored the same way: Snowcrown, the rivers and The Narrows all
assume no slats.

And the map's cut angle moves with the count. mapThetaOrigin() returns
slatCentre(n-1) when slats exist and PI when they do not, while the raster is
drawn against PI — so every non-zero count rotates the terrain out of
register with its own place markers, by 120 degrees at 3 slats and up to
137 km of displacement. Same desync D-024 fixed for the zero case; the test
added there only guards zero.

The section is removed and loadSlatCount() is no longer called, so a stored
non-zero count cannot leak back in. Kept: src/canon/slats.ts, the derivation
and the tests covering counts 1 to 16, since none of that is wrong — only
the app's ability to act on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
 README.md     |  9 ++++-----
 decisions.md  | 39 ++++++++++++++++++++++++++++++++++++++-
 index.html    |  7 -------
 src/main.ts   |  7 +++++--
 src/ui/app.ts | 43 +++----------------------------------------
 5 files changed, 50 insertions(+), 55 deletions(-)

diff --git a/README.md b/README.md
index 30e87c6..8443f0b 100644
--- a/README.md
+++ b/README.md
@@ -103,11 +103,10 @@ polygon and **River** and **Canyon** trace an open line (Enter finishes, Esc
 cancels). Drag markers to move them, **shift-click two points** to
 measure a sightline, **f** to fit the view.
 
-**Star slats** under Canon changes the count from 0 to 16. Canon is **0** — the hull
-is sealed, the inner surface is one unbroken 169,646 km², and the only views out are
-at the end caps. The control is kept as an exploration tool: with slats, the total
-width is fixed, so land stays exactly Wisconsin's 140,268 km² at every count from 1
-up and only the per-slat width moves.
+Canon is **0 star slats** — the hull is sealed, the inner surface is one unbroken
+169,646 km², and the only views out are at the end caps. The runtime control for
+this is disabled and hidden (decisions.md D-033): the terrain raster is baked at 0
+slats, so any other count draws bands over land that has none.
 
 Everything autosaves to this browser's localStorage. Two exports:
 
diff --git a/decisions.md b/decisions.md
index 3d32941..901cbba 100644
--- a/decisions.md
+++ b/decisions.md
@@ -46,7 +46,9 @@ costs nothing and it is too good to pass up.
 surface, 169,646 km² — Wisconsin's total area. The second coincidence recorded
 below no longer applies.*
 
-**Slat count is left as a tunable constant.** The width follows from the count:
+**Slat count is left as a tunable constant.** *The runtime control is disabled and
+hidden as of D-033; the derivation below is unchanged and still tested.* The width
+follows from the count:
 
 | slats | each | land strips between |
 |---|---|---|
@@ -982,3 +984,38 @@ put them: closing the editor keeps it closed through every later selection.
 **The general rule this is worth remembering by:** an interface that rearranges
 itself in response to a click costs the user their place. Prefer a sensible initial
 state and then hold still.
+
+---
+
+## D-033 — The slat control is disabled and its UI removed
+
+**Removed at the author's request:** changing the count did not fully work. It did
+not, and for two independent reasons, both worth writing down because this is
+deferred rather than abandoned.
+
+**The raster has no slats in it.** `terrain-color.png` is generated from
+`content/geography.json` at canon's 0 slats. Raising the count drew slat bands as a
+vector overlay on top of terrain that shows unbroken land underneath them — two
+layers asserting different worlds. The corridor is authored the same way: Snowcrown,
+the rivers and The Narrows all assume no slats, so bands above 0 cut straight
+through them.
+
+**And the map's cut angle moves with the count.** `mapThetaOrigin()` returns
+`slatCentre(n-1)` when slats exist and π when they do not (D-013, D-024). The raster
+is drawn against π, so every non-zero count rotates the terrain out of register with
+its own place markers — by 120° at 3 slats, 157° at 8, up to **137 km** of
+displacement. This is the same desync D-024 fixed for the zero case, and the test
+added there only guards the zero case.
+
+**What was done.** The section is gone from the sidebar and `loadSlatCount()` is no
+longer called, so a stored non-zero count from before cannot leak back in. What was
+*kept* is everything that makes the count correct: `src/canon/slats.ts`, the
+derivation, and the tests covering counts 1 to 16 — including that land stays
+exactly Wisconsin's 140,268 km² across the whole range, and that the map cut lands
+on a slat centre at every count.
+
+**What re-enabling would need.** The terrain tool would have to bake slats into the
+raster from the same count the app uses, which means the count becomes an input to
+`npm run terrain` rather than a runtime toggle — and the geography would need
+re-authoring around wherever the bands fall. That is a content decision as much as a
+code one, which is the real reason it is parked.
diff --git a/index.html b/index.html
index b040c28..4a06663 100644
--- a/index.html
+++ b/index.html
@@ -75,13 +75,6 @@
       <button id="f-delete" class="danger">Delete</button>
     </details>
 
-    <details class="sec" id="slats-section">
-      <summary>Star slats <span id="slat-canon" class="pill">canon</span></summary>
-      <label>Count <span id="slat-value" class="dim"></span>
-        <input id="f-slats" type="range" min="0" max="16" step="1" /></label>
-      <p class="hint" id="slat-derived"></p>
-    </details>
-
     <details class="sec" id="data-section">
       <summary>Data</summary>
       <div id="save" class="save"></div>
diff --git a/src/main.ts b/src/main.ts
index c091c91..2b8cf4b 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,9 +1,12 @@
 import './style.css'
 import { App } from './ui/app.js'
-import { loadSlatCount } from './canon/slats.js'
 import { WorldStore } from './world/store.js'
 
-loadSlatCount()
+/**
+ * The slat count stays at its canon value of 0. The runtime control is disabled and
+ * its UI removed — see decisions.md D-033. Restoring a stored non-zero count here
+ * would silently reintroduce the bug for anyone who had already moved the slider.
+ */
 const store = new WorldStore()
 // The committed public world seeds a browser with no local draft yet.
 await store.load(`${import.meta.env.BASE_URL}world.json`)
diff --git a/src/ui/app.ts b/src/ui/app.ts
index 7fd1f06..0bb85e2 100644
--- a/src/ui/app.ts
+++ b/src/ui/app.ts
@@ -5,8 +5,7 @@
  * continuously; export a full backup whenever; export the static public content
  * once to seed `static/world.json` in the repo.
  */
-import { SLAT_COUNT_MAX, SLAT_COUNT_MIN } from '../canon/constants.js'
-import { landStripWidth, slatCount, setSlatCount, slatWidth } from '../canon/slats.js'
+import { landStripWidth, slatCount, slatWidth } from '../canon/slats.js'
 import { type SurfacePoint, landStripIndex, isSlat, surfaceDistance } from '../canon/geometry.js'
 import { sightline } from '../world/instruments.js'
 import {
@@ -77,7 +76,6 @@ export class App {
     this.buildTools()
     this.buildKindSelect()
     this.wireEditor()
-    this.wireCanon()
     this.wireView()
     this.wireData()
     this.applyMode()
@@ -191,7 +189,7 @@ export class App {
   /** Readers get the map, the place list and the sightline probe — nothing else. */
   private applyMode(): void {
     $('mode').textContent = this.writer ? 'writer' : 'reader'
-    for (const id of ['tools-section', 'data-section', 'slats-section']) {
+    for (const id of ['tools-section', 'data-section']) {
       $(id).hidden = !this.writer
     }
     if (!this.writer) {
@@ -386,23 +384,7 @@ export class App {
     field.select()
   }
 
-  // --------------------------------------------------------------------- canon
-
-  /**
-   * Slat count is the one canon parameter the author can move at runtime — D-002
-   * calls it the most tunable number in canon, and seeing eight strips on a map is
-   * exactly the evidence needed to judge it.
-   */
-  private wireCanon(): void {
-    const slider = $<HTMLInputElement>('f-slats')
-    slider.min = String(SLAT_COUNT_MIN)
-    slider.max = String(SLAT_COUNT_MAX)
-    slider.value = String(slatCount())
-    slider.oninput = () => {
-      setSlatCount(Number(slider.value))
-      this.render()
-    }
-  }
+  // ---------------------------------------------------------------------- view
 
   private wireView(): void {
     const box = $<HTMLInputElement>('f-terrain')
@@ -413,24 +395,6 @@ export class App {
     }
   }
 
-  private renderCanon(): void {
-    const n = slatCount()
-    $<HTMLInputElement>('f-slats').value = String(n)
-    $('slat-value').textContent = `— ${n}`
-    // "canon" marks the value, not the section: the slider only shows canon at 0.
-    $('slat-canon').hidden = n !== 0
-    const derived = $('slat-derived')
-    derived.classList.toggle('warn', n === 0)
-    derived.textContent =
-      n === 0
-        ? 'Sealed hull (D-017). One unbroken surface of 169,646 km² — Wisconsin’s '
-          + 'total area — and no view out except at the caps. All weather is Tower '
-          + 'deliberately aiming light.'
-        : `${n} slats, each ${km(slatWidth())} wide, land strips `
-          + `${km(landStripWidth())} wide. Total slat width is fixed, so land stays `
-          + 'exactly Wisconsin’s 140,268 km². Canon is 0.'
-  }
-
   // -------------------------------------------------------------------- editor
 
   private buildKindSelect(): void {
@@ -564,7 +528,6 @@ export class App {
 
     this.renderList()
     this.renderEditor()
-    this.renderCanon()
     this.renderSave()
     this.writeHash()
   }