fuzzyworld

tower / commit

fix(ui): the panel clock follows the running cycle

37a25a7f9859ff69c58a792f26e322d7c2e17851 · Pavel Safronov · 2026-09-08 11:46:56 -0700 · parents 49b0d64d13b0

fix(ui): the panel clock follows the running cycle

The Time slider did not move while the clock ran. The readout was reading
dayPhase off the view every frame and the slider was sitting wherever it had
last been dragged, so the panel had two clocks in it disagreeing and one of
them was wrong.

The slider now follows. Two guards on it: it is left alone while it has
focus, so this cannot fight a drag in progress, and it writes nothing when
the value has not changed — assigning to a range input and rebuilding the
label are not free, and this runs on every frame of the cycle.

Verified: with the cycle running the slider advanced 720 -> 765 -> 810 while
the label and the readout agreed at every step, and setting it by hand
afterwards still works.

Lifted out of the shelved flying-camera work on interior-flight, where it
had been bundled with a much larger change. Placed above renderStanding
rather than between its doc comment and its body, which is where the
original put it — that had left renderStanding's own comment describing the
wrong function.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
 src/ui/app.ts | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/src/ui/app.ts b/src/ui/app.ts
index 2f5b255..c552f85 100644
--- a/src/ui/app.ts
+++ b/src/ui/app.ts
@@ -818,6 +818,29 @@ export class App {
     ].join('\n')
   }
 
+  /**
+   * Put the panel's clock where the world's clock actually is.
+   *
+   * The running cycle advances `dayPhase` inside the view, and the readout was
+   * reading it off there every frame while the slider sat wherever it had last
+   * been dragged — two clocks in the same panel, disagreeing, one of them wrong.
+   *
+   * The slider is left alone while it has focus, so this cannot fight a drag in
+   * progress, and it writes nothing when the value has not changed: `input` on a
+   * range is not free, and the label rebuild would otherwise run every frame.
+   */
+  private syncTime(): void {
+    const v = this.interior
+    if (!v) return
+    const t = $<HTMLInputElement>('f-time')
+    if (document.activeElement === t) return
+    const mins = Math.round((((v.dayPhase % 1) + 1) % 1) * 1440) % 1440
+    if (Number(t.value) === mins) return
+    t.value = String(mins)
+    $('time-value').textContent =
+      `— ${String(Math.floor(mins / 60)).padStart(2, '0')}:${String(mins % 60).padStart(2, '0')}`
+  }
+
   /**
    * Where you are standing and what can be seen from there. The sightline straight
    * up is the one worth printing: it is the crispest far view in Tower (design.md
@@ -826,6 +849,7 @@ export class App {
   private renderStanding(): void {
     const v = this.interior
     if (!v) return
+    this.syncTime()
     const here = v.at
     const opposite = { theta: here.theta + Math.PI, z: here.z }
     const across = sightline(here, opposite)