feat(openworlds): 'building your universe' loading screen on Start (cold-open felt-latency) - #396
Conversation
…old-open felt-latency)
Pressing Start / Resume → Play (screen-launcher) or Bind (the Forge/Creation wizard)
mints a DM provider session and then generates the cold-open — two long waits with a
full page reload (window.location.assign) wedged between them. Before this, the player
saw "nothing happens" then an abrupt read-only flash. This replaces BOTH waits with one
intentional, on-brand (parchment/brass) full-screen loading experience that:
• appears THE INSTANT play/forge is pressed (window.OpenWorldsBuilding.begin),
• SURVIVES the reload — the intent is stamped in sessionStorage (not React state, which
dies on location.assign), so the overlay is already up when the live viewer repaints, and
• hands off to the live table the moment the FIRST DM narration beat lands in
liveSession.chatBeats (the SAME real milestone the in-table #385 cold-open clears on).
Honest, not faked: a forging brass seal + rotating Sword-Coast lore flavor (the world →
the factions → your hero) + a live elapsed readout + an indeterminate sweep that loops and
never claims a percentage. role="status" announces the wait ONCE; the ticking/rotating text
is visible AND in the a11y tree (the #385 frozen-app lesson) but outside the announced region
so a screen reader isn't spammed. Reduced-motion stills the seal/sweep/flicker but keeps the
elapsed clock + rotating text. A 12-min hard backstop (mirrors the cold-open ceiling) plus
clear()-on-mint-failure guarantee the overlay can never wedge.
Files:
• viewer/openworlds/building-universe.jsx (NEW) — OpenWorldsBuilding persistence facade
(sessionStorage-backed, survives reload), useBuildingUniverse(liveSession) hook (App-level
lifecycle + first-narration handoff + backstop), and the BuildingUniverse overlay component.
• viewer/openworlds/app.jsx — THREE surgical additive changes (for the parallel streaming
agent to reconcile): (1) const building = window.useBuildingUniverse(liveSession) right
after const liveSession = useLiveSession(state); (2) a building→table handoff effect right
after the didAutoRoute effect (routes to "table" when the overlay clears from the launcher);
(3) the return is wrapped in <React.Fragment> and the overlay rendered after </div> of
.window: {building.active && window.BuildingUniverse && <window.BuildingUniverse …/>}.
No existing logic changed.
• viewer/openworlds/screen-launcher.jsx — startPlay: begin() at the click; clear() on the two
pre-reload failure exits.
• viewer/openworlds/screen-create.jsx — bindHero: same begin()/clear() (kind:"forge").
• viewer/openworlds/index.html — load building-universe.jsx before app.jsx (server
auto-version-stamps it; no server change needed).
• viewer/openworlds/styles.css — .building-universe block (fixed full-screen parchment overlay,
forging seal, honest sweep, dots, candleglow, handoff flourish, reduced-motion + high-contrast).
• viewer/tests/test_building_universe.py (NEW) — renders the REAL component via the bundled
Babel + a createElement stub (mirrors test_cold_open_progress.py): persistence facade,
no-fake-%, accessible-text-changes-over-time, headline rotation, stable live region, handoff,
forge eyebrow. 9 pass; existing cold-open/recovery/sanitize suites still green.
Verified in the dev viewer (Preview): overlay shows on begin(), survives a hard reload (forge
+ play variants), headline + elapsed visibly advance, reduced-motion stills motion while keeping
the clock, no console errors.
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughThe PR adds a persistent "building your universe" loading overlay to the Open Worlds viewer. When users initiate play or hero creation, a full-screen overlay appears and persists across page reloads via sessionStorage. The overlay detects the real story-start milestone by monitoring liveSession.chatBeats for the first narration entry, then automatically dismisses and routes the user to the live table. The overlay includes animated visual elements, a live elapsed timer, rotating lore text, and accessibility modes for reduced motion and high contrast. ChangesBuilding Universe Loading Overlay
Sequence DiagramsequenceDiagram
participant User
participant App
participant OpenWorldsBuilding as window.OpenWorldsBuilding
participant Hook as useBuildingUniverse
participant Component as BuildingUniverse
participant ChatBeats as liveSession.chatBeats
User->>App: Click play or bind hero
App->>OpenWorldsBuilding: begin(meta)<br/>(world, kind, title)
OpenWorldsBuilding->>OpenWorldsBuilding: Write sessionStorage<br/>+ dispatch event
Note over OpenWorldsBuilding: Persists across<br/>location.assign() reload
App->>Hook: Mount hook on render
Hook->>OpenWorldsBuilding: read() persisted intent
activate Hook
Hook->>Component: Render with record & handoff state
activate Component
Component->>Component: Elapsed timer<br/>+ rotating lore
Note over Component: Visible until<br/>handoff completes
ChatBeats->>Hook: First narration entry
Hook->>OpenWorldsBuilding: clear() persisted flag
Hook->>Component: Update handoff:true
Component->>Component: Brief handoff<br/>visual state
deactivate Component
deactivate Hook
Hook->>App: Unmount overlay
App->>App: Auto-navigate<br/>launcher→table
User->>App: Sees live table
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
What it does
Pressing Start / Resume → Play (
screen-launcher.jsx) or Bind (the Forge/Creation wizard,screen-create.jsx) mints a DM provider session and then generates the cold-open — two long waits with a full page reload (window.location.assign) wedged between them. Before this, the player saw "nothing happens" then an abrupt read-only flash.This replaces both waits with one intentional, on-brand (parchment/brass) full-screen loading experience that:
window.OpenWorldsBuilding.begin);sessionStorage(not React state, which dies onlocation.assign), so the overlay is already up the moment the live viewer repaints (no blank gap);liveSession.chatBeats— the same real milestone the in-table#385cold-open clears on. A brief "Your story begins…" flourish plays, then it routes to the table where that opening beat is already in the chronicle.Honest, not faked (per the owner's bar): a forging brass seal + rotating Sword-Coast lore flavor (the world → the factions → your hero coming together) + a live elapsed readout + an indeterminate sweep that loops and never claims a percentage.
role="status"announces the wait once; the ticking/rotating text is visible and in the a11y tree (the#385frozen-app lesson) but outside the announced region so a screen reader isn't spammed. Reduced-motion stills the seal/sweep/flicker but keeps the elapsed clock + rotating text. A 12-min hard backstop (mirrors the cold-open ceiling) plusclear()-on-mint-failure guarantee the overlay can never wedge.Files touched
viewer/openworlds/building-universe.jsx(NEW) —window.OpenWorldsBuildingpersistence facade (sessionStorage-backed, survives the reload, self-heals stale records past the backstop),useBuildingUniverse(liveSession)hook (App-level lifecycle + first-narration handoff + backstop), and theBuildingUniverseoverlay component.viewer/openworlds/app.jsx— 3 surgical, additive changes (see reconcile note below). No existing logic changed.viewer/openworlds/screen-launcher.jsx—startPlay:begin()at the click;clear()on the two pre-reload failure exits.viewer/openworlds/screen-create.jsx—bindHero: samebegin()/clear()(kind:"forge"tunes the eyebrow).viewer/openworlds/index.html— loadsbuilding-universe.jsxbeforeapp.jsx. (The server serves any file in the bundle generically and auto-version-stamps.jsx— no server change needed.)viewer/openworlds/styles.css— a self-contained.building-universeblock (fixed full-screen parchment overlay z-index 9000, forging seal w/ counter-rotating rings + candleglow, the honest sweep, pulsing dots, the handoff flourish, reduced-motion + high-contrast handling, a scoped.visually-hiddenhelper).viewer/tests/test_building_universe.py(NEW) — renders the real component via the bundled Babel + acreateElementstub (mirrorstest_cold_open_progress.py): persistence facade, no-fake-%, accessible-text-changes-over-time, headline rotation, stable live region, handoff, forge eyebrow. 9 pass; existing cold-open / recovery / sanitize suites still green.A parallel agent is editing
app.jsxfor incremental narration streaming. My changes are 3 additive hunks, no existing logic touched:const liveSession = useLiveSession(state);— add:didAutoRouteeffect — add awasBuildingref + an effect that, whenbuilding.activeflips false whilescreen === "launcher", callssetScreen("table")(belt-and-suspenders handoff; also covers the in-browser already-live case).App()returnis wrapped in<React.Fragment>…</React.Fragment>and the overlay rendered after the.window</div>:The handoff reads
liveSession.chatBeats(first{kind:"narration"}). If the streaming work changes how the first narration first appears inchatBeats, the handoff trigger should be re-pointed at whatever "first narration present" signal streaming settles on — they share the same intent (the#385cold-open clears on the same beat).Verification
Smoke-tested in the dev viewer (Preview tool): overlay shows on
begin(), survives a hard reload (bothplayandforgevariants — eyebrow + rotating headline correct on the fresh page), headline + elapsed visibly advance over time, reduced-motion stills the motion while keeping the elapsed clock, no console errors. Screenshots captured during dev.Do NOT close on merge — verify on the next full-arc playtest (press Start → loading screen → scene, never a blank/frozen wait).
Summary by CodeRabbit
New Features
Accessibility
Tests