HardYards/web/world/js/editor_storm.js
type-two d6f04d5e43 editor: one storm picker + a progress line that paints (S15 gates 2.1/2.2, B's halves)
editor_storm.js owns THE storm selection and THE storm list; SCORE IT's
dropdown is a view of it (default = southerly, C's old default, kept on
purpose). The score panel renders scoreSite's progress ticks — flight x/y,
elapsed — and audit.html gets the same line. C's half: subscribe
editor.wind.js per the THREADS proposal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 18:19:44 +10:00

67 lines
2.5 KiB
JavaScript

/**
* editor_storm.js — the editor's ONE storm selection. [Lane B, SPRINT15 gate 2.2]
*
* D scored the wildnight while looking at the southerly: B's SCORE IT and C's
* WIND panel each carried a private `stormKey`, independently defaulted — the
* exact two-harnesses-disagreeing failure this repo keeps paying for, arrived
* inside a single page. This module is the fix: one storm selection, one storm
* LIST, owned here; every panel that shows or uses a storm reads and writes
* THIS and never a private copy.
*
* Deliberately not on A's `EDITOR.on` — emit is private to editor.js, and
* widening A's seam for one dropdown is more contract than the problem needs.
* B owns this file; C's panel subscribes (THREADS 2026-07-20, gate 2.2
* proposal). The default is the southerly — C's old default, kept on purpose:
* the WIND panel is live the moment the page opens and needs a storm that
* shows something, while SCORE IT is an explicit click behind a visible
* dropdown. (A funnel authored under the gentle storm looks like it does
* nothing — C's own comment.)
*
* No I/O here: this holds the NAME, not the storm def. Fetching defs stays
* with the panels (loadStorm), which already cache.
*/
/** Keep in step with data/storms/ — THE list; panels must not carry their own. */
export const STORM_KEYS = [
'storm_01_gentle',
'storm_02_wildnight',
'storm_02b_icenight',
'storm_03_southerly',
'storm_03b_earlybuster',
];
export const DEFAULT_STORM = 'storm_03_southerly';
let key = DEFAULT_STORM;
const subs = new Set();
export const stormSel = {
/** The currently selected storm key. */
get key() { return key; },
/**
* Change the selection. No-op on the same key; throws on a stranger — a
* typo'd storm silently becoming the default is setHardware's lesson again.
* `source` is a free label ('score', 'wind', …) so a subscriber can skip
* reacting to its own write.
*/
set(next, source = null) {
if (next === key) return;
if (!STORM_KEYS.includes(next)) {
throw new Error(`unknown storm "${next}" — the editor knows: ${STORM_KEYS.join(', ')}`);
}
key = next;
for (const fn of [...subs]) {
try { fn(key, source); } catch (err) { console.error('[storm] subscriber threw:', err); }
}
},
/** Subscribe to changes; returns an unsubscribe function. */
onChange(fn) { subs.add(fn); return () => subs.delete(fn); },
STORM_KEYS,
};
// For consoles and selftests — same object, not a copy.
globalThis.EDITOR_STORM = stormSel;