Skip to content

feat(openworlds): 'building your universe' loading screen on Start (cold-open felt-latency) - #396

Merged
100yenadmin merged 1 commit into
mainfrom
feat/building-universe
May 30, 2026
Merged

feat(openworlds): 'building your universe' loading screen on Start (cold-open felt-latency)#396
100yenadmin merged 1 commit into
mainfrom
feat/building-universe

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 30, 2026

Copy link
Copy Markdown
Member

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:

  • 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 the moment the live viewer repaints (no blank gap);
  • 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. 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 #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 touched

  • viewer/openworlds/building-universe.jsx (NEW) — window.OpenWorldsBuilding persistence facade (sessionStorage-backed, survives the reload, self-heals stale records past the backstop), useBuildingUniverse(liveSession) hook (App-level lifecycle + first-narration handoff + backstop), and the BuildingUniverse overlay component.
  • viewer/openworlds/app.jsx3 surgical, additive changes (see reconcile note below). No existing logic changed.
  • viewer/openworlds/screen-launcher.jsxstartPlay: begin() at the click; clear() on the two pre-reload failure exits.
  • viewer/openworlds/screen-create.jsxbindHero: same begin()/clear() (kind:"forge" tunes the eyebrow).
  • viewer/openworlds/index.html — loads building-universe.jsx before app.jsx. (The server serves any file in the bundle generically and auto-version-stamps .jsxno server change needed.)
  • viewer/openworlds/styles.css — a self-contained .building-universe block (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-hidden helper).
  • 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.

⚠️ app.jsx changes — for reconcile with the parallel streaming agent

A parallel agent is editing app.jsx for incremental narration streaming. My changes are 3 additive hunks, no existing logic touched:

  1. After const liveSession = useLiveSession(state); — add:
    const building = (typeof window.useBuildingUniverse === "function")
      ? window.useBuildingUniverse(liveSession)
      : { active: false, record: null, handoff: false, dismiss: () => {} };
  2. After the existing didAutoRoute effect — add a wasBuilding ref + an effect that, when building.active flips false while screen === "launcher", calls setScreen("table") (belt-and-suspenders handoff; also covers the in-browser already-live case).
  3. The App() return is wrapped in <React.Fragment>…</React.Fragment> and the overlay rendered after the .window </div>:
    {building.active && window.BuildingUniverse && (
      <window.BuildingUniverse record={building.record} handoff={building.handoff} />
    )}

The handoff reads liveSession.chatBeats (first {kind:"narration"}). If the streaming work changes how the first narration first appears in chatBeats, the handoff trigger should be re-pointed at whatever "first narration present" signal streaming settles on — they share the same intent (the #385 cold-open clears on the same beat).

Verification

Smoke-tested in the dev viewer (Preview tool): overlay shows on begin(), survives a hard reload (both play and forge variants — 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

    • Added a "building your universe" loading overlay with animated visuals and elapsed-time counter during character and world creation
    • Overlay automatically navigates to the game table upon creation completion
    • Persists state across page reloads during the creation process
  • Accessibility

    • Added live region announcements for screen readers
    • Integrated reduced-motion and high-contrast display modes
  • Tests

    • Added test suite validating loading overlay behavior and accessibility features

Review Change Stack

…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.
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

The 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.

Changes

Building Universe Loading Overlay

Layer / File(s) Summary
Building Universe core module
viewer/openworlds/building-universe.jsx
Implements window.OpenWorldsBuilding persistence facade with begin/read/clear methods, the useBuildingUniverse(liveSession) hook that detects narration handoff and manages overlay state, the BuildingUniverse component with elapsed timer and rotating content, and lore pools for headline/subline variation.
Overlay visual design and accessibility
viewer/openworlds/styles.css
Styles the full-screen parchment-themed overlay with animated seal, indeterminate sweep progress bar, and rotating lore text. Includes responsive scaling for narrow windows, reduced-motion mode (disables animations while preserving elapsed/text info), and high-contrast mode (simplifies background and edge effects).
App-level overlay wiring and routing
viewer/openworlds/app.jsx, viewer/openworlds/index.html
Consumes the useBuildingUniverse hook in the app root with safe fallback, wraps the return in a Fragment to render the overlay alongside existing layout, and adds an effect to auto-route from launcher to table once the overlay becomes inactive after narration arrives. Script tag loads the new module before app.jsx executes.
Launcher and create screen entry point wiring
viewer/openworlds/screen-launcher.jsx, viewer/openworlds/screen-create.jsx
Both entry points call window.OpenWorldsBuilding.begin() immediately when the user presses play or bind, passing world, kind, and title metadata. Both clear the overlay on network failure or missing viewer address, and both clear it if the session request throws.
Test suite for overlay behavior and accessibility
viewer/tests/test_building_universe.py
Comprehensive test harness using Node vm to load and render the real JSX via Babel-standalone. Tests validate sessionStorage persistence (begin/read/clear), rendering honesty (no fake percentages), accessible text evolution with elapsed times, headline rotation through lore pools, live region stability, handoff-mode messaging, and kind-specific eyebrow content (hero name vs. universe name).

Sequence Diagram

sequenceDiagram
  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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • electricsheephq/WorldOS#385: The new Building Universe overlay with narration-milestone detection directly addresses the cold-open "looks frozen" UX by providing an animated, persisted waiting state and detecting the same liveSession.chatBeats narration milestone referenced in that issue.

Possibly related PRs

  • electricsheephq/WorldOS#359: Both PRs modify the launcher flow in viewer/openworlds/screen-launcher.jsx; #359 routes resume→play through startPlay while this PR wraps that same startPlay call in the building-universe overlay logic.
  • electricsheephq/WorldOS#343: Both PRs modify viewer/openworlds/app.jsx and thread liveSession into the routing flow; this PR's building-universe hook consumes liveSession.chatBeats to detect narration, while #343 creates and passes liveSession into ScreenRouter/ScreenTable.
  • electricsheephq/WorldOS#328: Both PRs modify the play entry flow in viewer/openworlds/screen-launcher.jsx; this PR adds the overlay trigger at play start while #328 refactors the launcher CTA and routing to the live table.

Poem

🐰 A parchment curtain rises slow,
With spinning seals and candlelight's glow,
As worlds are forged and stories brew,
The cosmos whispers: "Let's build you!"
Then when the narration starts to sing,
Off comes the curtain—let the tale begin!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely summarizes the primary change: adding a 'building your universe' loading screen to address cold-open felt latency during Start/Play flows.
Description check ✅ Passed The description is comprehensive and well-structured, covering what changed, why, files touched, verification steps, and licensing—matching the template requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant