RELEASE
planning/2.8.0/slates/CEREMONY_MOMENTS_SLATE.md
sha256 c1f5c5b5c886023d · 26780 bytes ·
original held in the private archive
# CEREMONY MOMENTS SLATE — boot / first-launch / transition / ceremony beats
> Ideation-only scoping pass for the "boot / ceremony moments" item in the 2.8.0 queue.
> Companion in spirit to `FEEDBACK_ANIMATION_SLATE.md` (the 33 shipped per-event animations),
> but focused on **transitions and ceremonies** — the moments the machine changes state —
> rather than per-event feedback. Read against HEAD `6b08b16` (dev).
>
> NO source edits ship from this doc. Owner picks; each pick becomes its own unit.
---
## 1 · INVENTORY — what already exists (so this slate is additive, not duplicative)
### 1.1 Boot sequence (WU-F6 + WU-T3 + WU-B10)
- `runBootSequence(onComplete)` — `js/ui-audio.js:1531`. Three POST flavors via
`_pickBootFlavor()` (`ui-audio.js:1477`):
- **`normal`** — terse 8-line warm boot.
- **`cold`** — first-ever power-on (RETROS BIOS banner + counting memory test), once per
device, gated by MetaStore `robco_booted_before` (`js/state.js:68`).
- **`degraded`** — rare cold-tube flicker variant, `DEGRADED_BOOT_CHANCE = 0.01`
rolled on EVERY boot (owner pref — never first-boot-gated).
- Per-game identity boot line (WU-T3): `PIP-BOY 3000 MK IV — MOJAVE WASTELAND UPLINK`
spliced in before the final line, from `GAME_DEFS[ctx].theme` (`ui-audio.js:1547-1555`).
- Boot drone armed to the first user gesture (`_bootActive` window, WU-B10,
`ui-audio.js:1329`); 120 ms line cadence; `boot-fade-out` exit.
- Test override: `window.__robcoBootFlavor`; Test Console REBOOT replays the real sequence
(`js/test-console.js:129`).
### 1.2 Existing ceremonies (view-once beats)
- **Module Bay hatch** — the ONE shipped view-once ceremony. `releaseBayHatch()`
(`js/ui-core.js:705`), MetaStore `robco_bay_opened`, fires only on a genuine
first user-initiated SETTINGS visit (never at `window.onload` — `ui-core.js:2496`),
honors reduced-motion, Test Console REPLAY HATCH resets it (`test-console.js:191`).
- **Cold POST** (above) is the only other first-ever beat.
- **Cartridge swap** today = `confirmAction('> SWAP PROGRAM CARTRIDGE' … 'SEAT CARTRIDGE')`
→ `onGameContextChange()` → **bare `window.location.reload()`** (`ui-core.js:3366-3416`).
A jarring white flash — the build plan itself calls it "the biggest wasted-ceremony
opportunity."
- **New campaign** today = `wipeTerminal()` double-confirm → plain chat lines
`> TERMINAL WIPED. INITIATING NEW CAMPAIGN...` / `> SELECT GAME CONTEXT:`
(`ui-core.js:7841-7910`). No ceremony at all.
### 1.3 Ambient runtime + presence
- `AmbientRuntime` states COLD_BOOT/READY/ACTIVE/IDLE/STANDBY/SHUTDOWN/OFF (`js/runtime.js`);
A3 experiences: idle phosphor dim, standby breathing vignette, shutdown CRT
collapse-to-a-dot + `#powerOnBtn` recovery (legal SHUTDOWN→OFF→COLD_BOOT walk).
- **Wake sequence** (`exitStandby()`, `ui-core.js:1167`): wake tone → 650 ms → hum ramp +
`> COURIER RETURNED. SYNCHRONIZING TELEMETRY...` + geiger resync, with the two-site
shutdown-race guard. This is a real ceremony already — audio + chat, but **no visual verb**.
- **DO-O Overseer** — scope states listening/thinking/speaking/disabled/offline, idle blips
from `identity.persona.blipBank`. **`identity.overseer.greeting` is authored for all three
games (`state.js:983/1186/1392`) but consumed NOWHERE** — grep hits only the three
declarations. A finished, data-driven moment with no renderer.
- **CHASSIS living core** — 24 behaviors incl. boot ignition, uptime-milestone pulse
(once per crossed hour), recovery stabilize, hold-to-overcharge; all gate-stacked
via `_coreShouldAnimate()`/`.core-still`.
- **Overseer's Log telemetry** (WU-F7, `ui-core.js:160-246`): `bootCount`,
`totalPowerOnMs`, `longestSessionMs`, `firstBoot` — MetaStore device stats, already
tracked, mostly ceremonially unused.
### 1.4 Identity data already scaffolded (DO-K)
Every game carries `identity.ceremony` (`coldStart`, `switchLabel`) and
`identity.motionTexture` (`seat`/`sweep`/`wake`/`fault`/`breathe`) — `state.js:934-944`
(NV), `1139-1149` (FO3), FO4 design-only. **Only `sweep` is consumed** (DO-N `_bezelSweep()`,
`ui-core.js:4201`). `seat`/`wake`/`fault`/`breathe` are authored textures with no verb built.
### 1.5 Motion-verb grammar (Protocol UI-9)
- **SWEEP** shipped — phosphor smear on subsystem change, plain `@keyframes` + toggled
class + forced reflow, auto-neutralized by the global reduced-motion block.
- **SEAT / WAKE / FAULT / BREATHE** — named in the protocol as "land with later units";
FAULT exists only as feedback-animation #32 (lamp flicker), not a formal verb; WAKE
exists only as audio (`playWakeTone`).
### 1.6 Update / reboot flow
Blocking `#updateModal` + `> REBOOT TERMINAL` → `SKIP_WAITING` → reload
(`index.html:4133`, Suite 65). **Nothing marks the first boot AFTER an update** — the user
taps REBOOT and lands in an ordinary warm boot with no "what changed" beat.
### 1.7 Account / sync moments
Google link: popup → `renderAccount()` re-render flips to `OPERATOR VERIFIED`
(`js/ui-account.js`). First cloud push: additive `addDoc` + `#31 HOLOTAPE COMMIT`
(Wave 2, home-only). **No one-time "operator registered" ceremony** anywhere.
### 1.8 Already-planned unit this slate must NOT duplicate
**DO-C — Cartridge-swap game-switch + cold-start ceremony**
(`DESIGN_OVERHAUL_BUILD_PLAN.md` §DO-C, lines 384-430): wraps the `onGameContextChange()`
reload as an eject/seat ceremony, and upgrades the once-per-device cold boot into a
per-game salvage-and-power-on ceremony driven by `identity.ceremony.coldStart`. Mockup-gated,
skippable, reduced-motion-instant, MetaStore-only. **Slate items below either feed DO-C or
deliberately stay out of its lane; two entries are explicit DO-C deferrals so the owner
sees the full moment landscape in one place.**
### 1.9 Adjacent shipped beats (not gaps)
Level-up already has a flagship (#9 VAULT-BOY LEVEL CARD + XP sweep, Wave 1); location
arrival has the LOCATION CONFIRMATION CARD (Suite 204); saves have #31 HOLOTAPE COMMIT;
quests have #23/#24 stamps. The slate proposes *milestone variants*, never re-doing these.
---
## 2 · DESIGN RULES every slate item obeys (noted per idea as tags)
| Tag | Constraint |
| --- | --- |
| `MOBILE` | Works and does not annoy at 360/412 (owner is almost exclusively on a phone). No moment adds taps, blocks input, or meaningfully delays boot. |
| `RM-SAFE` | Plain `@keyframes` only — auto-neutralized by the existing global `prefers-reduced-motion` block to one correct static frame (Protocol UI-9). |
| `NO-WRITE` | Zero campaign-state write. Transient DOM/module vars, or a MetaStore device flag at most (`robco_bay_opened` precedent). |
| `AGNOSTIC` | Per-game flavor only via `identity`/`GAME_DEFS` (Protocol 38). Generic fallback for an unauthored game — never borrowed fiction. |
| `PWA-OK` | Boot integrity / SW / offline untouched; ceremony wraps real work, never replaces or delays it (DO-C guardrail, adopted slate-wide). |
| `EARNED` | Fires at a genuine transition; one-time moments are view-once (MetaStore flag); repeating moments are brief (<2 s) and never gate input. |
**One-time vs repeating** is flagged per item as `ONCE` (view-once, hatch-style),
`ONCE/CAMPAIGN`, `RARE` (probabilistic or milestone), or `REPEAT` (every occurrence, kept tiny).
---
## 3 · THE SLATE
Ranked **MUST-HAVE → NICE → STRETCH**. Effort: S (<½ day), M (~1 day), L (multi-day).
---
### TIER 1 — MUST-HAVE (recommended for 2.8)
#### M1 · CAMPAIGN IGNITION — the new-campaign ceremony `ONCE/CAMPAIGN`
- **Trigger:** end of `wipeTerminal()` — the moment the fresh campaign is seeded and the
game-context prompt appears (`ui-core.js:7905`), and again when the player picks the game.
- **Looks/feels:** instead of two bare chat lines, the glass runs a short (~2.5 s,
skippable-by-tap) "commissioning" sequence: the CRT dips to near-black, a
`> UNIT RE-COMMISSIONED` header types on, then 3–4 calibration lines
(`> REGISTERING NEW OPERATOR……… [OK]`, `> CALIBRATING S.P.E.C.I.A.L. BASELINE… [OK]`,
`> DIRECTIVE REGISTRY: EMPTY — AWAITING ORDERS`), the living core does one ignition
flare (reuse `_coreOneShot()`), and the Overseer posts its per-game
`identity.overseer.greeting` (see M2) as the campaign's first line.
- **Why:** starting a campaign is the single most ceremonial thing a player does and today
it reads as a settings reset. This is the "session zero" beat every RPG opening has.
- **Implementation:** a small `_runCampaignIgnition()` in `ui-core.js` called from the two
`wipeTerminal()` chat-line sites; typed lines reuse the boot-line `setInterval` pattern
(`runBootSequence` precedent); flare reuses `_coreOneShot()`; greeting via
`getIdentity().overseer.greeting` with generic fallback; all chat lines via
`appendToChat(line,'sys',true)` **not** persisted beyond normal chat rules; any dimming a
toggled class + plain `@keyframes`. Skip = any tap/keydown fast-forwards to the end frame.
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort M · Impact HIGH · Risk LOW**
(touches only the two chat-line call sites; the wipe/seed logic is untouched).
#### M2 · DIRECTOR ON THE WIRE — consume the orphaned greeting `ONCE per session`
- **Trigger:** the first time in a session the UPLINK subsystem becomes active with a live
carrier (`_isUplinkConnected()` true) and the transcript's last line isn't already it.
- **Looks/feels:** the Overseer scope runs its existing speaking pulse once and the
authored greeting types on: NV `▸ EVENING, COURIER. The Director is on the wire…`,
FO3 `▸ VAULT DWELLER, THE OVERSEER IS MONITORING…` — the exact strings already in
`identity.overseer.greeting` (`state.js:983/1186/1392`).
- **Why:** the data is authored for all three games and rendered NOWHERE — this is a
finished moment missing only ~15 lines of consumer. Cheapest win on the slate; makes the
presence feel like it noticed you sat down.
- **Implementation:** a session-scoped module flag (`let _greeted = false`) checked in the
existing `_syncBezelNav()`/subsystem-arrival path; render via
`appendToChat(greeting,'sys',true)` un-persisted (the DO-O idle-blip precedent —
device-template ambient content never enters saved history, Protocol UI-10); scope pulse
reuses `_scopePulse()` gated by `_scopeShouldAnimate()`.
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S · Impact MED-HIGH · Risk MINIMAL**
#### M3 · FIRMWARE FLASH — the post-update first boot `REPEAT (once per update)`
- **Trigger:** first boot where `APP_VERSION` ≠ MetaStore `robco_last_seen_version`
(new registered device pref; absent = treat as seen, so existing devices don't all
fire it on the unit that ships this).
- **Looks/feels:** one extra POST line spliced into the boot sequence (the WU-T3 splice
precedent): `> FIRMWARE FLASH DETECTED — U.O.S. v2.7.0 → v2.8.0 ...... [OK]`, plus a
one-shot amber glint on the casing serial plate after boot, and the SERVICE & FAULT
CONSOLE's REV LOG button pulses once (pointing the user at the changelog they just got).
- **Why:** the user taps REBOOT TERMINAL on faith; today nothing ever acknowledges the
update landed. Closes the loop on the whole Protocol-1 update dance and gives the
changelog viewer an earned entrance.
- **Implementation:** read/compare/set the pref inside `runBootSequence()` next to the
existing `robco_booted_before` write; the extra line rides the existing `lines.splice`
mechanism; glint/pulse are toggled classes + plain `@keyframes`. Register the key in
`META_MANIFEST` (Protocol UI-6/4).
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE (MetaStore)` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S · Impact MED · Risk LOW**
#### M4 · LONG-ABSENCE RECALIBRATION — the "welcome back" boot line `REPEAT (rare by nature)`
- **Trigger:** boot where time since the last session flush (Overseer's Log already
persists this — `_readOverseerLog()`, `ui-core.js:173`) exceeds a threshold (e.g. ≥3 days).
- **Looks/feels:** one POST line: `> UNIT IDLE 6 DAYS — RE-CALIBRATING PHOSPHOR ...... [OK]`,
and the warm boot's hum ramp runs marginally slower for that one boot (audio-only nicety,
behind the existing gesture gate). Under ~3 days, nothing — silence is the feature.
- **Why:** the machine remembering you were gone is the cheapest possible "it's alive"
signal, and it's invisible to daily users so it can never wear out.
- **Implementation:** needs a `lastFlushAt` timestamp added to the Overseer-log JSON blob
(same MetaStore key, additive field, zeroes-safe parse already in place); one conditional
`lines.splice` in `runBootSequence()`. Copy is generic; a per-game variant string could
ride `identity` later.
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE (MetaStore)` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S · Impact MED · Risk MINIMAL**
#### M5 · SEAT — formalize the pending motion verb `REPEAT (micro)`
- **Trigger:** any "component physically installs" interaction: Module Bay board reseat,
phosphor tube pick, program-cartridge confirm, Tool Deck open, perk CARD SEAT (#13
retro-fits onto the shared verb).
- **Looks/feels:** the named **SEAT** verb per Protocol UI-9 — a 220 ms settle: the element
drops 2-3 px with a 1-frame overshoot and a faint phosphor compression ring, textured
per game by `identity.motionTexture.seat` (`thunk-and-spin` NV / `mechanical-clunk` FO3 —
data already authored, `state.js:939/1144`). Pairs with the existing `playChipClick()`/
`playBoardThunk()` audio that already fires at these sites.
- **Why:** the protocol names SEAT as pending; half the app's fiction is "hardware you
seat" and the audio half already shipped — this completes the sensory pair and gives
every future unit a reusable install-verb instead of bespoke transitions.
- **Implementation:** one `@keyframes seatSettle` + a `.seat` toggled class + forced-reflow
trigger helper `_motionSeat(el)` (mirror `_bezelSweep()`); texture variants as
`[data-game]`-scoped keyframe overrides (CSS only, no JS branch). Adopt at 3-4 call
sites this unit; document the verb in CLAUDE.md UI-9 as adopted.
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S-M · Impact MED · Risk LOW**
---
### TIER 2 — NICE (strong candidates, second wave)
#### N1 · OPERATOR REGISTRATION — the account-link stamp `ONCE`
- **Trigger:** first-ever successful Google link on this device (the `linkWithPopup`
success path in `cloud.js`), gated by a new view-once MetaStore flag.
- **Looks/feels:** the REG PORT board runs a short "registration" beat — the operator name
types onto the plate character-by-character, then the Wave-2 ink-stamp animation
(`_playInkStamp()` reuse) slams `REGISTERED` across it; the annunciator echoes
`⚑ OPERATOR ON RECORD` if the user isn't on SETTINGS.
- **Why:** account linking is the app's biggest trust moment and today it's a silent
re-render. A one-time ceremony marks it as an achievement, not a form submission.
- **Implementation:** flag check + `_playInkStamp()` + a typing loop in `renderAccount()`'s
signed-in branch; echo via `_echoPush()`. Sign-in flow itself untouched (Protocol 29/30
never re-verified — display-only).
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE (MetaStore)` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S · Impact MED · Risk LOW**
#### N2 · SESSION LOG CLOSED — the shutdown send-off `REPEAT`
- **Trigger:** entering SHUTDOWN via the PWR lamp / `AmbientRuntime.shutdown()` — just
before the A3 collapse-to-a-dot plays.
- **Looks/feels:** a single farewell frame inside the collapse: `> SESSION LOG CLOSED ·
UPTIME 2H 14M · GOODNIGHT, OPERATOR` flashes on the dot's final frame (uptime from the
existing session clock). At Balanced/Minimal tiers (plain cut) it's just the line, no
flourish.
- **Why:** the power-down is the app's most theatrical animation but it ends mute; a
sign-off makes deliberate shutdown feel like closing a book instead of killing a process.
- **Implementation:** one line appended by the shutdown-crt observer's onEnter (it already
owns this window); copy generic, optional per-game sign-off string in `identity` later.
Careful: never delay the actual shutdown transitions (line renders in parallel).
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S · Impact MED · Risk LOW**
#### N3 · WAKE — formalize the second pending verb `REPEAT`
- **Trigger:** the existing standby wake (`exitStandby()`, `ui-core.js:1167`) — the tone +
`COURIER RETURNED` line already fire; this adds the missing visual.
- **Looks/feels:** the named **WAKE** verb: a horizontal carrier-acquire smear (a thin
bright line sweeps once across the glass and "locks", like a CRT finding sync), textured
per game by `identity.motionTexture.wake` (`carrier-acquire` NV / `post-check-chime`
FO3). ~400 ms, overlay layer only (never transforms text — the SWEEP rule).
- **Why:** wake is the one runtime ceremony with audio + chat but no motion; formalizing
it finishes the verb and makes tab-return feel like the machine snapping back to
attention.
- **Implementation:** `@keyframes wakeAcquire` + `.wake` class on `.glass-frame`
(the `_bezelSweep()` add/reflow/remove pattern), triggered inside `exitStandby()`'s
650 ms callback AFTER the `_isShuttingDown()` recheck (respects the shutdown-race
guard exactly as shipped).
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S · Impact MED · Risk LOW**
#### N4 · VETERAN IGNITION — boot-count milestone POST line `RARE`
- **Trigger:** boot where `_readOverseerLog().bootCount` crosses a milestone
(100 / 500 / 1000 / each further 1000).
- **Looks/feels:** one POST insert: `> IGNITION No. 0500 — UNIT RATED: FIELD VETERAN`,
plus the CHASSIS hour-meter drums do one celebratory roll on first CHASSIS visit that
session (reuse the odometer tiles' existing paint).
- **Why:** `bootCount` is already tracked and displayed but never *celebrated*; milestone
scarcity makes it a collectible moment for the long-haul owner. Pairs with the existing
uptime-milestone core pulse without overlapping it (different metric, different surface).
- **Implementation:** milestone check + `lines.splice` in `runBootSequence()`; a
`_pending*`-style module var for the drum roll (the Wave-1 deferred-consumption pattern).
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S · Impact LOW-MED · Risk MINIMAL**
#### N5 · MILESTONE LEVEL CARD — gilded variant of #9 `RARE`
- **Trigger:** `level.up` where the new level % 5 === 0 (or a GAME_DEFS-tunable stride).
- **Looks/feels:** the existing VAULT-BOY LEVEL CARD (#9) renders with a ceremonial
variant class — amber/gold border (`--bezel-wire`), a slower double sweep on the XP bar,
and the living core's #14 tumble fires alongside (both already exist; this composes them).
- **Why:** level-up feedback exists but every level feels identical; milestone levels are
the natural "chapter break" and cost almost nothing since both halves shipped.
- **Implementation:** one modifier class + `@keyframes` on the existing card; the
subscriber that shows the card checks the stride. No new event, no new surface.
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S · Impact MED · Risk MINIMAL**
#### N6 · SUBSYSTEM ARRIVAL ACCENTS — first visit per session `ONCE per subsystem per session`
- **Trigger:** first `selectSubsystem(view)` per subsystem per session (session-scoped
`Set`, cleared on reload).
- **Looks/feels:** SWEEP still plays every switch (unchanged); the FIRST arrival at each
subsystem additionally runs that subsystem's signature one-shot: DATABANK — the
cartography SURVEY-SWEEP does one full accelerated rotation; CHASSIS — the living core
ignition flare; OPERATOR — the VU array does its fill-in cascade; SETTINGS — the bay
boards' LEDs blink on in sequence. All reuse animations that already exist on those
surfaces.
- **Why:** gives each "room" of the machine a sense of powering up as you walk in, exactly
once, so it never becomes navigation friction.
- **Risk note:** the one slate item with real annoyance potential if overdone — must stay
≤400 ms, purely decorative, and per-session (never per-switch). If it reads busy at 360px
in the mockup pass, cut it without regret.
- **Implementation:** a `Set` in `selectSubsystem()`'s path + per-subsystem one-shot class
triggers; zero new keyframes needed for CHASSIS/OPERATOR (reuse), one small one for
SETTINGS.
- **Tags:** `MOBILE(⚠ verify)` `RM-SAFE` `NO-WRITE` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort M · Impact MED · Risk MED**
#### N7 · FAULT — formalize the third pending verb `REPEAT (rare by nature)`
- **Trigger:** a NEW entry landing in the client error ring-buffer (`_recordError`) —
the same signal the FAULT lamp + #32 flicker already read.
- **Looks/feels:** the named **FAULT** verb: a one-shot static-scratch tear across the
glass overlay (per `identity.motionTexture.fault`: `static-scratch-burst` NV /
`buzzer-fault` FO3) — distinct from the CRT FLINCH (#2, HP loss) by being a vertical
hold-jitter rather than a horizontal tear. Brief (≤300 ms), overlay-only.
- **Why:** completes the verb family for the error path; today a fault lights a lamp but
the glass itself never registers distress.
- **Implementation:** `@keyframes faultScratch` + class on `.glass-frame`, triggered from
the existing `_updateFaultLamp` refresh path (one added line). Must never fire during
boot (`_bootActive` guard) so a boot-time error can't stack on the POST.
- **Tags:** `MOBILE` `RM-SAFE` `NO-WRITE` `AGNOSTIC` `PWA-OK` `EARNED` — **Effort S · Impact LOW-MED · Risk LOW**
---
### TIER 3 — STRETCH (defer / fold into planned units / experimental)
#### S1 · CARTRIDGE-SWAP CEREMONY — **explicit DO-C deferral** `REPEAT`
The eject-tape → white-noise beat → target-machine POST wrap around the
`onGameContextChange()` reload. **Already fully specced as DO-C** (build plan §DO-C,
mockup-gated, `identity.ceremony.switchLabel` authored). This slate adds nothing —
listed so the owner sees it in the moment landscape. Build it as DO-C, not from here.
#### S2 · COLD-START SALVAGE CEREMONY — **explicit DO-C deferral** `ONCE`
The once-per-device first boot upgraded to a per-game salvage-and-power-on ceremony
(`identity.ceremony.coldStart`: `mojave-uplink-handshake` / `vaulttec-post` / `mk4-post`).
Also DO-C scope (H-1). The current WU-F6 cold POST remains the shipped baseline until then.
#### S3 · BREATHE — the fourth verb, formalized `AMBIENT`
- A barely-perceptible slow luminance sine on the glass at IDLE (per
`identity.motionTexture.breathe`: `slow-warm-sine` / `steady-hum`). The standby vignette
already breathes; this extends the idiom to IDLE at the `full` immersion tier only.
- **Why stretch:** continuous ambient animation on mobile = battery + subtlety risk; wants
the DO-Q2 motion-demo pass first. Effort S, but verify-heavy.
- **Tags:** `MOBILE(⚠ battery)` `RM-SAFE` `NO-WRITE` `AGNOSTIC` — **Impact LOW-MED · Risk MED**
#### S4 · HOLOTAPE EJECT — full-backup export ceremony `REPEAT`
- On EXPORT FULL BACKUP / Eject Holotape (WU-F3): a tape physically slides out of a slot
at the SAVE ARCHIVE board edge before the share sheet opens (never delaying it —
animation runs in parallel with the async share). Builds on #31's glyph.
- **Why stretch:** lovely but low-frequency surface; #31 already covers the save beat.
- **Tags:** all pass — **Effort M · Impact LOW-MED · Risk LOW**
#### S5 · TIME-OF-DAY GREETING — dynamic salutation `REPEAT (micro)`
- M2's greeting swaps its salutation by real clock (`MORNING/AFTERNOON/EVENING, COURIER`).
Data change: `greeting` becomes a template or 3 variants in `identity.overseer`.
- **Why stretch:** tiny, but touches authored identity strings for all 3 games + the
Suite 157 contract — do it as a follow-up to M2, not inside it.
- **Tags:** all pass — **Effort S · Impact LOW · Risk MINIMAL**
#### S6 · GHOST TRANSMISSION — ultra-rare boot easter egg `RARE (~1/500)`
- A fourth boot flavor rolled like `degraded` but far rarer: the POST is interrupted by
two lines of a garbled, unattributed transmission (`> ?????: —SIGNAL LOST—`) then resumes
normally. Pure flavor, generic copy (game-specific lines could ride `identity.ambient`
later). The owner's degraded-boot precedent (roll every boot, never first-boot-gated)
applies verbatim.
- **Why stretch:** delightful but pure sugar; needs owner taste-check on tone.
- **Tags:** all pass (`__robcoBootFlavor` override extends for testing) — **Effort S · Impact LOW-MED · Risk LOW**
#### S7 · CAMPAIGN ANNIVERSARY STAMP — in-game calendar beat `RARE`
- When the in-game date (game-time helpers, `ui-render.js`) crosses a year since campaign
start: one CHRONICLE entry + a small stamp on the CAMPAIGN CHRONICLE board. Reads
campaign state but writes only via the existing `_logCampaignEvent()` auto-log path.
- **Why stretch:** crosses into campaign-data territory (auto-log write, not pure
flourish) — wants its own scoping; flagged rather than smuggled in as a "moment."
- **Tags:** `NO-WRITE` ✗ (auto-log entry — sanctioned path, but not a pure flourish) — **Effort M · Impact LOW · Risk MED**
---
## 4 · RECOMMENDATION FOR 2.8
Build **M1–M5 as one "Ceremony Moments Wave 1" unit** (they share infrastructure —
boot-line splicing, `identity` reads, one new MetaStore key each for M3, and the
`_bezelSweep()` trigger pattern for M5):
1. **M2 Director on the Wire** — cheapest, highest fiction-per-line; the data already exists.
2. **M1 Campaign Ignition** — the biggest genuine gap (new campaign is currently two bare chat lines).
3. **M3 Firmware Flash** — closes the update-loop blind spot; trivially cheap.
4. **M4 Long-Absence Recalibration** — near-free alongside M3 (same splice mechanism).
5. **M5 SEAT verb** — pays down the Protocol UI-9 debt and gives DO-C its install-verb for free.
Then **N1/N2/N3/N5** as a Wave 2 if Wave 1 lands well (all Effort S). **N6 needs a mockup
taste-check before committing** (the one annoyance-risk item). S1/S2 stay DO-C's; S3–S7 parked.
Estimated Wave 1 cost: ~1.5–2 days incl. both-runner suite coverage + live 360/412/desktop
render-verify; zero new campaign-state fields (Protocol 4 untriggered except M3/M4's
registered MetaStore keys); every animation plain-`@keyframes` reduced-motion-safe.
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).