fuzzyworld

tower / cascadia / files / src / canon / slats.ts

Branch cascadia · default branch · all branches

slats.ts

2.6 KB · history · blame

1/**
2 * Slat count, the one canon parameter adjustable at runtime. See design.md §1.4.
3 *
4 * D-002 fixes the *total* slat width from the land-area target, so the count only
5 * decides how that width is divided up: Tower's land stays exactly Wisconsin's land
6 * area at any count from 1 to 16. At 0 there are no slats at all — the land becomes
7 * the entire inner surface and nobody in Tower can see a star. That is a different
8 * world, and the app says so rather than silently rounding the coincidence away.
9 */
10import {
11 CIRCUMFERENCE_M, INNER_AREA_M2, LAND_AREA_M2, RADIUS_M,
12 SLAT_COUNT_DEFAULT, SLAT_COUNT_MAX, SLAT_COUNT_MIN, SLAT_TOTAL_WIDTH_M,
13} from './constants.js'
14
15const STORAGE_KEY = 'tower.slatCount'
16const TAU = 2 * Math.PI
17
18let count = SLAT_COUNT_DEFAULT
19const listeners = new Set<() => void>()
20
21/** Restore the author's chosen count. Safe where localStorage is unavailable. */
22export function loadSlatCount(): void {
23 try {
24 const raw = localStorage.getItem(STORAGE_KEY)
25 if (raw !== null) count = clamp(Number(raw))
26 } catch {
27 // Private window or blocked site data: keep the canon default.
28 }
29}
30
31const clamp = (n: number): number =>
32 Math.min(SLAT_COUNT_MAX, Math.max(SLAT_COUNT_MIN, Math.round(Number.isFinite(n) ? n : SLAT_COUNT_DEFAULT)))
33
34export const slatCount = (): number => count
35
36export function setSlatCount(n: number): void {
37 const next = clamp(n)
38 if (next === count) return
39 count = next
40 try {
41 localStorage.setItem(STORAGE_KEY, String(count))
42 } catch {
43 // Not persisting is survivable; the session still works.
44 }
45 for (const fn of listeners) fn()
46}
47
48export function onSlatCountChange(fn: () => void): () => void {
49 listeners.add(fn)
50 return () => listeners.delete(fn)
51}
52
53/** Width of one slat. Zero when there are none. */
54export const slatWidth = (): number => (count === 0 ? 0 : SLAT_TOTAL_WIDTH_M / count)
55export const slatAngularWidth = (): number => slatWidth() / RADIUS_M
56/** Angle from one slat to the next. Zero when there are none. */
57export const slatSpacing = (): number => (count === 0 ? 0 : TAU / count)
58
59/** Width of one land strip. With no slats the strip is the whole circumference. */
60export const landStripWidth = (): number =>
61 count === 0 ? CIRCUMFERENCE_M : (CIRCUMFERENCE_M - SLAT_TOTAL_WIDTH_M) / count
62
63/** Total habitable land. Constant at Wisconsin's land area for any count ≥ 1. */
64export const landArea = (): number => (count === 0 ? INNER_AREA_M2 : LAND_AREA_M2)
65
66/** How many distinct land strips exist. With no slats, one continuous surface. */
67export const landStripCount = (): number => (count === 0 ? 1 : count)