GENERATED VIEW · PRIVATE ARCHIVE · DO NOT SERVE
RobCo Industries · Archive Museum

PHASE3_OPERATOR_PLAN.md



RELEASE

planning/2.8.0/plans/PHASE3_OPERATOR_PLAN.md

sha256 836ab629a35208fe · 20899 bytes · original held in the private archive

# Phase 3 · Piece 1 — DRESS the OPERATOR / STAT screen into the NV "machine" language **Branch:** `dev` · **HEAD:** `e1e0ceb` · **APP_VERSION:** `2.7.0` **Flow:** plan → **Fable mockup** → owner approve → build (same as the Settings tab / campaign-configs pieces). **Scope:** PLAN ONLY. No code edits, no gate. Implementation + the mockup are later passes. --- ## 0. The head start — this is NOT greenfield The **NV machine mockup already sketches OPERATOR** end-to-end: - `planning/mockups/nv-machine-mockup.html` renders a dressed OPERATOR view (`?view=operator`), shot as `nv-machine-412-operator-full.png` / `nv-machine-desktop.png`. - `nv-machine-rationale.md` §4 ("OPERATOR fully dressed") already proposes the dressing vocabulary: **VITAL TELEMETRY** (segmented HP bar, XP meter, caps odometer, karma lamp, rads exposure meter), **ATTRIBUTE TUNING GAUGES** (7 analog needle gauges + ± steppers), **skeletal-harness BIO-SCAN**, **13-channel skill VU array**, **STATUS EFFECT compound lamps**, **FACTION reputation needle board**. So this plan's job is **(a)** formalize that sketch into a build-ready, element-complete spec, **(b)** reconcile it with the owner's three north-star dressings, and **(c)** fill the gaps the §4 sketch skipped (see §2 — PERKS+TRAITS, the LOCATION/DATE/TIME cluster, LEVEL UP, WEIGHT/AP/EQUIPPED, the two skill sub-panels, KARMA CENTER, the RUN BIO-SCAN button, radAwayAlert). The Fable mockup should **extend** `nv-machine-mockup.html`'s operator view (Protocol 22 — reuse, don't fork), not start fresh. --- ## 1. The consistency floor (what OPERATOR must match) Owner directive: the merged **Comm-Link (DO-O Director Uplink oscilloscope) + SETTINGS tab (campaign-config hardware boards + Module Bay)** is the LOWEST design floor. OPERATOR must read as the **same machine**. The concrete shared vocabulary (from `campaign-configs-notes.md`, `module-bay-rationale.md`, `nv-machine-rationale.md`): - **Amber-on-phosphor palette:** green (`#14fdce`) = local to the machine; **amber (`--bezel-wire`)** = "over the wire" (Director / remote telemetry). Rads/exposure + any Director-sourced readout lean amber; owned character stats stay green. Hazards use `--robco-danger` red (the INTERLOCK precedent). - **Collapsible board DNA:** every board is a `<details class="panel">` whose **closed state is a one-line LED status row** (0i — never information-free). OPERATOR already IS a stack of `<details>` panels — this is a natural fit. - **Slot/board fiction:** each block carries a diegetic id + a real plain-English sub-label riding along (Protocol 25 guardrail) — the campaign-config `P-DECK`/`INTERLOCK` and Module Bay `SLOT 0N` precedent. OPERATOR's fiction = a **biometric telemetry bus** (each board gets a `BUS` id, mirroring the bay's part numbers). - **Copper edge-connectors** use `background-repeat: round` (Suite 160 stray-pin rule). - **All new motion is plain `animation`** (never `transition:` alone), auto-neutralized by the existing global `prefers-reduced-motion` block — no bespoke carve-out. Motion verbs already named: SWEEP (nav), SEAT (seat/overshoot). OPERATOR adds gauge-needle overshoot (SEAT) + HP drain-lag as plain animations. - **Centering rule:** incomplete flex rows (gauge grid, limb nodes, skill channels) center under `flex-wrap + justify-content:center`. - **Per-game via `GAME_DEFS[ctx].identity`** (Protocol 38) — see §5. --- ## 2. Element-by-element dressing spec (the meat) Below is EVERY control/readout actually on `data-tab="stat"` today (verified against `index.html` @ `e1e0ceb`), grouped into dressed boards. **The dressed form is a visual wrapper around the exact same element ids** — see §3 for why that's non-negotiable. ### Board 1 — VITAL TELEMETRY *(from BIO-METRICS panel, L422–775, instrument half)* Fiction: `BUS-01 · VITAL TELEMETRY`. The glanceable core; default-open. | Element (id) | Handler | Dressed form | Stays functional | | --- | --- | --- | --- | | HP cur/max (`stat_hp_cur`,`stat_hp_max`) + bar (`hp_bar_fill`/`hp_bar_container`) | `oninput=updateMath` | **Segmented phosphor HP gauge** with drain-lag animation; the two number inputs stay as small editable fields beneath/beside the bar | ✅ both inputs editable, bar driven by `updateMath()` | | LVL/XP (`stat_lvl`,`stat_xp`) + XP bar (`xp_bar_fill`) | `stat_lvl oninput=onLvlInputChanged`, `stat_xp oninput=updateMath` | **Level readout tile + XP progress meter** (blue accent kept) | ✅ both editable | | LEVEL UP (`btnLevelUp`) | `onclick=nativeLevelUp`, `disabled` toggled by `updateMath` | **Physical "COMMIT LEVEL" key** with lit/disabled states honored (ungated-by-XP behavior preserved) | ✅ same handler + disabled logic | | CAPS (`c_caps`) | `oninput=updateMath` | **Mechanical caps odometer** skin; the number input stays directly editable (odometer is the display layer) | ✅ editable | | KARMA slider (`stat_karma`) + label (`karma_label`) | `oninput=updateKarmaUI+updateMath` | **Lit karma alignment lamp/gauge** driven by the slider; slider stays the control | ✅ range input editable | | MAX AP (`display_ap`) | computed by `updateMath` | **AP readout tile** | ✅ read-only display, unchanged | | WEIGHT (`display_weight`) | computed by `updateMath` | **Load gauge / readout** | ✅ display unchanged | | EQUIPPED (`equippedDisplay`) | rendered by `_updateContextPanels` | **"LOADOUT" readout strip** | ✅ display unchanged | | RadAway alert (`radAwayAlert`) | shown/hidden by `updateMath` | **Amber warning lamp** | ✅ same show/hide | ### Board 2 — ATTRIBUTE TUNING GAUGES *(SPECIAL grid, L426–546)* ⭐ north-star Fiction: `BUS-02 · S.P.E.C.I.A.L. TUNING`. The owner's "SPECIAL as tuning dials." - Seven **analog needle gauges** (etched S/P/E/C/I/A/L letters), needle position = value 1–10, **SEAT overshoot** animation on change (plain `animation`, reduced-motion-safe). - Each gauge carries **± stepper buttons** (≥28px) that nudge the underlying `s_x` input by 1 and call the existing clamp path. - **The raw number input (`s_s`..`s_l`) stays present and editable** (small, directly typeable) — the gauge/steppers are an *added* affordance, never a replacement (no-regression). Recommend keeping the number visible; see Open Q3 for "reveal-on-tap" alternative. - Clamp 1–10 preserved: steppers/inputs route through the existing `capStatMax(el)` (oninput) + `commitStat(el)` (change) — `commitStat` reads `el.id.slice(2)`, writes `state[k]`, clamps `Math.max(1,Math.min(10,v))`, calls `updateMath()`+`saveState()`. Steppers must set `el.value` then call `commitStat(el)` so the exact same path runs. ### Board 3 — BIO-SCAN / SKELETAL HARNESS *(from BIO-SCAN & LIMB STATUS, L777–863)* ⭐ north-star Fiction: `BUS-03 · SKELETAL HARNESS PROBE`. The owner's "body-diagram bio-scan." | Element | Handler | Dressed form | Stays functional | | --- | --- | --- | --- | | RADIATION (`stat_rads`) | `oninput=updateMath` | **Amber exposure meter** + editable input | ✅ editable | | HEAD/L-ARM/R-ARM/L-LEG/R-LEG (`btn_l_hd`/`la`/`ra`/`ll`/`rl`) | `onclick=toggleLimb('xx')` | **Phosphor body silhouette with 5 tappable limb nodes** (≥28px) + a **mirrored readout list** (`[████] OK` green / `[▒░░░] CRPL` red-blinking); a fault propagates to the bezel telemetry LCD (`_vitalsTier()` already reads these limb fields) | ✅ same `toggleLimb`, same OK/CRIPPLED state, same sounds | | RUN BIO-SCAN ADVISORY (button) | `onclick=renderBioScan` | **"RUN ADVISORY" board key** (native, no AI) | ✅ same handler/modal | Body-diagram nodes are a **second projection** of the same `state.la/ra/ll/rl/hd` — route the node taps AND the (kept) text-button fallback through the one `toggleLimb()` so the diagram and the list can never disagree (the Module-Bay bay↔schematic single-apply precedent). ### Board 4 — CHRONO / POSITION FIX *(LOCATION + DATE + TIME cluster, L644–745)* ⚠ §4-sketch gap Fiction: `BUS-04 · POSITION & MISSION CLOCK`. **Not in the mockup §4 — needs a home.** | Element | Handler | Dressed form | Stays functional | | --- | --- | --- | --- | | LOCATION (`stat_loc` + `locationOptions` datalist) | `oninput=updateMath`, `onchange=onLocationChange` | **Position-fix readout** with the editable autocomplete field kept | ✅ editable + datalist | | DATE (`cal_month`/`cal_day`/`cal_year`) + TIME (`time_hour`/`time_min`) + hidden (`time_day`/`stat_ticks`) | `oninput=onTimeInputChanged` | **Mission-clock instrument** (date wheels + 24h clock face); all number inputs stay editable | ✅ editable, ticks logic unchanged | | Computed date (`gameDateDisplay`) + in-game time (`gameTimeDisplay`) | computed in `loadUI` | **Diegetic readout lines** | ✅ display unchanged | **Open Q2:** is CHRONO/POSITION an OPERATOR board or does it belong on DATABANK with world-state? It's on STAT today → recommend keep on OPERATOR (least churn, it's Courier-local telemetry). ### Board 5 — SKILL MATRIX *(L1094–1120)* Fiction: `BUS-05 · SKILL VU ARRAY`. - `skillsGrid` (built by `renderSkills()` → `sk_${key}` number inputs, `oninput=saveState`) → **13-channel VU array** (FNV's real 13 skills via `getSkillKeys()`), each channel a labeled meter with the **number input kept editable** (0–100 clamp preserved). Tag/high skills optionally marked ◆ (cosmetic). - `SKILL BOOKS` sub-panel (`skillBooksPanel`/`skillBooksDisplay`) + `SKILL MAGAZINES` sub-panel (`magazinesPanel`, FNV-only, `display:none` on FO3) → keep as nested collapsible sub-boards, restyled to match. **Dressing must not touch `renderSkills()`'s output ids** (it re-renders `#skillsGrid.innerHTML`) — the board frame wraps `#skillsGrid`, the channel skin is applied by `renderSkills()` markup (a controlled edit to that function's template, still emitting `sk_${key}` inputs). ### Board 6 — PERK & TRAIT LOADOUT *(PERKS panel, L1172–1227)* ⚠ §4-sketch gap Fiction: `BUS-06 · PERK LOADOUT`. **Not in the mockup §4 at all — must be dressed.** - `perksList` (rendered) → **perk chip/card rack**; add-perk form (`newPerkName`/`newPerkRank`/`newPerkLevel`, `onclick=addPerk`) kept. - `TRAITS` sub-panel (`traitsSection`, FNV-only, `display:none` on FO3; `traitFilter` `oninput=renderTraits`, `traitsDisplay`) → nested collapsible sub-board, restyled. ### Board 7 — STATUS EFFECTS *(L1230–1294)* Fiction: `BUS-07 · COMPOUND LAMPS`. - `statusList` (rendered) → **lit/dark compound indicator lamps** (BUFF green / DEBUFF red / NEUTRAL amber). - Add-status form (`newStatusName` select, `newStatusTicks`, `newStatusType` select, `onclick=addStatusEffect`) kept as the editor row. ### Board 8 — FACTION STANDING *(FNV, factionPanel L1332–1337)* Fiction: `BUS-08 · REPUTATION`. - `factionContainer` (built by `renderFactionRep()` in `ui-render.js`) → **INFAMY ◂▸ FAME reputation needle board** with a center detent. Dressing lives in `renderFactionRep()`'s markup (keep its buttons/handlers, esp. the ±5 faction adjust). FNV-only via `factionPanel`. ### Board 9 — KARMA CENTER *(FO3-only, karmaPanel L1340–1347, `display:none`)* ⚠ §4-sketch gap Fiction: `BUS-09 · KARMA ALIGNMENT` (FO3 machine). `karmaCenterDisplay` built by `renderKarmaCenter()`. Stays hidden on FNV (`GAME_DEFS.FNV.usesKarmaCenter=false`). Dress the alignment gauge in `renderKarmaCenter()`'s markup so the FO3 machine inherits it later — game-agnostic, but authored now so the abstraction is proven (the DO-K FO4-entry precedent). --- ## 3. The load-bearing constraint — the id-preservation contract (Protocol 22) `loadUI()` (ui-core.js L4554+) and `updateMath()` do **direct `document.getElementById()`** on a fixed id set every render — if any id disappears, boot throws. The dressing **must keep every one of these ids** (wrap/restyle around them, never rename): `s_s s_p s_e s_c s_i s_a s_l` · `stat_hp_cur stat_hp_max hp_bar_fill hp_bar_container` · `stat_lvl stat_xp xp_bar_fill xp_bar_container btnLevelUp` · `c_caps` · `stat_karma karma_label` · `stat_loc locationOptions` · `cal_month cal_day cal_year time_hour time_min time_day stat_ticks gameDateDisplay gameTimeDisplay` · `display_weight display_ap equippedDisplay radAwayAlert` · `stat_rads` · `btn_l_hd btn_l_la btn_l_ra btn_l_ll btn_l_rl` · `skillsGrid sk_<key> skillBooksPanel skillBooksDisplay magazinesPanel magazinesDisplay` · `perksList newPerkName newPerkRank newPerkLevel traitsSection traitFilter traitsDisplay` · `statusList newStatusName newStatusTicks newStatusType` · `factionContainer factionPanel` · `karmaPanel karmaCenterDisplay`. Handlers stay too (`commitStat`/`capStatMax`/`toggleLimb`/`nativeLevelUp`/`onLvlInputChanged`/`onTimeInputChanged`/`onLocationChange`/`updateKarmaUI`/`addPerk`/`addStatusEffect`/`renderBioScan`/`renderSkills`/`renderFactionRep`/`renderKarmaCenter`). Where a render function emits markup (`renderSkills`, `renderFactionRep`, `renderKarmaCenter`), the channel/needle skin is a controlled edit to *that function's template*, still emitting the same ids/handlers. **No handler is forked; no state field changes** (this is the Module Bay / campaign-config reskin precedent, Protocol 25). Keeping the boards as `<details class="panel">` also preserves their `robco_panel_state` open/closed persistence for free. --- ## 4. Mobile approach (360 / 412 first, desktop second) - **Structure = collapsible boards** (unchanged): OPERATOR stays a vertical stack of `<details>` boards, each with a **0i one-line LED status row when closed** (e.g. `VITAL TELEMETRY · HP 100% · LVL 1` / `S.P.E.C.I.A.L. · 5·5·5·5·5·5·5` / `HARNESS · ALL OK` / `RADS 0`). Default-open the two hero boards (VITAL TELEMETRY, ATTRIBUTE TUNING); collapse the rest so the phone view is a short scannable stack (the settings-tab/module-bay 0i precedent). - **Gauges/needles** are pure-CSS/SVG, sized in relative units; the SPECIAL gauge grid + limb nodes + skill channels **wrap and center** (incomplete last row centers) — zero horizontal overflow at 360 (`scrollWidth === innerWidth`). - **Tap targets ≥28px** (limb nodes, ± steppers, LEVEL UP, board summaries 44px); **inputs ≥16px** (SPECIAL/HP/skill number fields, LOCATION, date/time). The kept number inputs must not shrink below 16px — the dressing adds visual chrome, it doesn't miniaturize the editable fields below the mobile baseline (Protocol 17). - **Reduced-motion:** needle overshoot (SEAT), HP drain-lag, limb-fault blink, VU sweep all as plain `animation` → the global `prefers-reduced-motion` block collapses each to its resting frame automatically. - **Desktop (≥1000px):** boards can sit two-up in the existing subsystem viewport under the same glass, with the permanent Director column on the right (as every subsystem). No new grid system — reuse the shell. - **PWA intact:** pure markup/CSS/(SVG or canvas) + the existing handlers layered over the working shell; no SW/scope change, no new document. A cache bump on the eventual build commit is the only `sw.js` touch. --- ## 5. Game-agnostic / data-driven story (Protocol 38) The dressing structure must generalize to FO3/FO4 with only `GAME_DEFS[ctx].identity` data, no feature-code branches: - **Palette/flavor** ride the existing `--bezel-wire` `[data-game]` token + `identity.material`/`cursor` (green-local vs amber-wire is NV's read; FO3 authors `green3` + its own values). No `ctx === 'FNV'` color branch. - **Skill channels** come from `getSkillKeys()` (13 for FNV, FO3 differs, FO4 = `[]` degrades safely) — the VU array is count-driven, never a hardcoded 13. - **Faction board** from `getFactionRegistry()`; **KARMA CENTER** shown per `GAME_DEFS[ctx].usesKarmaCenter`; **traits/magazines** per `hasTraits`/`hasMagazines` (already the gating today). - Any per-game **board titles / BUS labels / status wording** that need flavor go on `identity` (mirroring `identity.overseer`) with a generic literal fallback for an unauthored game — never another game's borrowed fiction. **Recommend adding an `identity.operator` sub-block** (board labels / harness caption / clock caption) if the mockup shows game-specific copy; if the copy is generic, no new facet is needed. (Open Q6.) --- ## 6. Fable mockup brief **Base:** extend `nv-machine-mockup.html`'s existing `?view=operator` (don't fork) so the mockup reuses the established casing/bezel/Overseer chrome and only elaborates the OPERATOR boards — including the **gaps §0 lists** (CHRONO/POSITION, PERK & TRAIT LOADOUT, LEVEL UP key, WEIGHT/AP/EQUIPPED, skill sub-panels, KARMA CENTER placeholder). Fake all wiring; MetaStore/device-pref only; zero campaign write. **Screens & states to render (all at 360 / 412 / desktop; verify `scrollWidth===innerWidth`):** 1. **OPERATOR — healthy default:** all 9 boards, hero boards open, rest showing 0i status rows. The reference for the full stack. 2. **OPERATOR — damaged/telemetry-reacting:** low HP (segmented bar drained + drain-lag frame), a crippled limb (body node red-blinking + mirrored list `[▒░░░] CRPL` + bezel LCD `⚠ 1 LIMB FAULT`), high rads (amber exposure meter near max + RadAway lamp lit), one active DEBUFF status lamp — proves the instruments respond to state. 3. **ATTRIBUTE TUNING close-up:** one SPECIAL gauge mid-SEAT (needle overshoot), ± steppers + the kept number field visible, 1–10 clamp shown at an extreme (e.g. L=10). 4. **360 collapsed view:** every board closed to its one-line LED status row — proves the 0i short-stack phone experience. 5. **Desktop:** two-up boards under the glass + permanent Director column + bezel/telemetry LCD (the `nv-machine-desktop.png` framing). **Optional (nice-to-have):** an FO3-context frame (`?view=operator` with a FO3 skin) showing KARMA CENTER visible + no magazines/traits — to visually prove the game-agnostic generalization before FO3 builds. **Deliverables:** the extended `nv-machine-mockup.html` (or a dedicated `operator-*.html` if cleaner) + PNGs at 360/412/desktop for states 1–5 + an `operator-notes.md` (like `campaign-configs-notes.md`) recording the board↔real-control mapping, the id-preservation contract, tap-target/overflow verification, and build invariants. --- ## 7. Build touchpoints (for the later implementation pass — not this task) - **Protocol 22/25:** reskin only — every id/handler/state semantic preserved; render-function template edits (`renderSkills`/`renderFactionRep`/`renderKarmaCenter`) keep their emitted ids. Owner-approved redesign guardrails: same location/tab, no added taps (steppers are *fewer* taps than typing for ±1), real labels ride as sub-labels, a plain fallback preserved. - **Protocol 10/17:** render-check at 360/412/desktop, zero overflow, ≥28px/≥16px, reduced-motion-safe. - **Protocol 38:** game-agnostic (identity-driven); the abstraction proven by KARMA CENTER (FO3) authored now. - **Protocol 20/13/15/2a:** static guards for the new markup contract + regression tests in both runners at parity; cache bump (Protocol 1) on the build commit. - **Protocol 4/UI-6:** only if a new device pref is introduced (e.g. a "gauge vs numeric" display-mode toggle — Open Q3). Default is no new pref. --- ## 8. Open questions (owner decisions) 1. **Board granularity.** Keep ~1:1 with today's 9 panels (recommended — preserves per-panel persistence + scope), or consolidate (e.g. fold SPECIAL into VITAL TELEMETRY as one instrument cluster)? 2. **CHRONO/POSITION home.** Keep the LOCATION/DATE/TIME cluster on OPERATOR as a mission-clock board (recommended), or move it to DATABANK as world-state? 3. **SPECIAL gauge + raw input.** Keep the number input always-visible beside each dial (recommended — safest no-regression), or reveal-on-tap for a cleaner face (adds a tap; would want a device-pref default)? 4. **CAPS odometer** — decorative odometer skin over the kept editable input (recommended), confirm you don't want it read-only. 5. **Scope of THIS slice.** Fully dress the three north-star elements (SPECIAL dials, vital telemetry, body-diagram) AND apply the board-frame/0i-status shell to ALL 9 OPERATOR boards so nothing looks half-dressed — with PERKS/STATUS/FACTION/KARMA getting their lamp/needle treatments in the same slice? Or hero-three-first, light-frame-the-rest, deep-dress the others in a follow-up? (Recommend: full board-shell for all + deep-dress the hero three + STATUS lamps + FACTION needles this slice; PERKS/CHRONO can be board-framed with lighter internal dressing if scope tightens.) 6. **`identity.operator` facet.** Add a per-game operator flavor sub-block (board/BUS labels, harness/clock captions) now, or keep OPERATOR copy generic (no new facet) until FO3 needs it? 7. **Motion budget.** Confirm needle-overshoot (SEAT) + HP drain-lag + limb-fault blink are wanted (all reduced-motion-safe), or keep OPERATOR motion minimal (static gauges)?
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).