Standardized YAML frontmatter on all 115 sprint briefing files across sprints 1-26 with title, description, type, status, sprint number, and team fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13 KiB
title, description, type, status, sprint, team
| title | description | type | status | sprint | team |
|---|---|---|---|---|---|
| Sprint 24 — Client Briefing | Character select screen, triangle activation response, news ticker display | sprint | archived | 24 | client |
Sprint 24: Signal — Client Tasks
Goal: Wire the storyteller's activation event into player-visible consequences, thread character archetype through the full session lifecycle, and deliver the first unscripted end-to-end v0.1 playthrough — from main menu to triangle activation.
Branch: client
Agents: Stig (UI/rendering), Hoshe (QA)
This is the capstone sprint for v0.1. Client work this sprint wires the player-facing signal that makes the simulation legible as a story: the character select screen, the triangle activation response, and the news ticker. All three must land before #593 (playthrough proof) can be filed as done. Sprint 25 is playtest. There is no Sprint 26 before v0.1 ships.
New Tickets
| # | Title | Blocked by |
|---|---|---|
| #588 | Character archetype selection — client: character select screen before session start | #587 (server: archetype in StartupMessage) |
| #590 | Triangle activation consumer — client: react to triangle_crisis_events (monologue chime + urgent overlay) | #589 (server: tell escalation emitted) |
| #592 | News ticker — client: scrolling ticker HUD element in The Last Shift zone | #591 (server: ticker in snapshot) |
Use tooling/db/ticket show <id> for full details.
Key Decisions
decisions/scope.md— D-027 (vertical slice success criteria — the 4 tests this sprint's work must satisfy), D-039 (wow moments — #1 Arrival: opening monologue; #5 News Ticker Gut-Punch: same ticker, opposite monologue reactions)decisions/architecture.md— D-020 (ObserverSnapshot is the only data crossing IPC; StartupMessage is the client→server init message; PROTOCOL_VERSION gates wire compatibility), D-042 (UI microcopy inclient/data/ui-strings.yamlvia UIStrings autoload)decisions/content.md— D-032 (separate monologue pools per character — client does not select the pool; the character string in StartupMessage drives server-side selection), D-074 (audio aesthetic — monologue chime = insert-tech: synthetic, precise, no reverb)decisions/perception.md— D-067 (recognition chime fires at onset of cognitive delay —sfx_monologue_chime_urgent.oggis the correct asset for triangle activation), D-016 (internal monologue as perception bridge — client only displays what server sends; no client-side monologue logic)
Notes
#588 — Character archetype select screen
What exists: client/ui/main_menu.gd (130 lines) — "New Game" button triggers SessionManager.new_game() which creates a save directory and seeds GameState.world_seed. It then loads GAME_SCENE directly, with no character selection step. client/scripts/autoloads/session_manager.gd — new_game() returns a game_id but does not record which archetype was chosen. client/scripts/autoloads/game_state.gd — check whether a character_archetype field already exists (likely not — add it). CharacterArchetype is a server-side enum; the client needs to record the chosen value as a string ("smuggler" or "detective") and include it in StartupMessage sent over IPC.
What to deliver:
-
GameState.character_archetype: String— new field, default"detective". Persisted alongsideworld_seedin the save directory (user://saves/<game-id>/character.txtor extend the existing seed file format). -
Character select scene — insert a step between "New Game" and loading
main.tscn. This can be a new scene (client/scenes/character_select.tscn) or a modal panel withinmain_menu.tscn. Show two options: Smuggler and Detective. Each option shows the character name, a one-line role description, and a two-line tone description (see below). On selection, setGameState.character_archetype, then proceed tomain.tscn.Smuggler card:
- Name:
Smuggler - Role:
Freight logistics worker — Sova Transit - Tone:
Insider access. Social camouflage. The ring is your daily life.
Detective card:
- Name:
Detective - Role:
Commission investigator — External assignment - Tone:
Institutional authority. Analytical lattice. You were sent here.
These strings belong in
client/data/ui-strings.yaml(D-042), not hardcoded in GDScript. - Name:
-
Protocol.encode_startup_message()update — addcharacter_archetypeto the StartupMessage dict before it is serialized. The server'sStartupMessagestruct now haspub character_archetype: CharacterArchetype(#587). Map client string"smuggler"→ server enum variantSmuggler. In MessagePack/GDScript, this is just a string field added to the dict:{ "world_seed": ..., "character_archetype": "Smuggler" }. -
Protocol.PROTOCOL_VERSION = 19— bump to match server #587. The client must send the new version on handshake. This is a hard coordination point with Dudley — client and server PRs must land together or in the same merge window. A version mismatch will crash the connection on the handshake check.
UI constraints: The character select screen must feel intentional, not an afterthought. Two full-width cards, dark background, character name in the sprint's color palette (consistent with main menu). No portraits (art is deferred). Cards are selectable via keyboard (left/right arrows) and mouse click. The selection is confirmed with Enter or a "Begin" button. ESC cancels back to the main menu without creating a save directory.
Non-obvious gotcha: SessionManager.new_game() currently creates the save directory before any game scene loads. The character select step happens after new_game() creates the directory but before the game scene loads. GameState.character_archetype must be set before SimBridge sends the StartupMessage — which happens when main.tscn is ready and SimBridge._ready() connects to the server. Verify the ordering: new_game() → character select panel → user picks archetype → GameState.character_archetype set → main.tscn loads → SimBridge._ready() fires → StartupMessage includes archetype.
Blocked by: #587 (server must define character_archetype field in StartupMessage before client serialization is finalized).
#590 — Triangle activation consumer
What exists: client/scripts/snapshot_event_router.gd — routes snapshot fields to registered handlers. client/scripts/main.gd — registers handlers on _router. client/scripts/autoloads/sim_bridge.gd — _on_snapshot_received() decodes and emits snapshot. client/scripts/protocol/protocol.gd — decode_snapshot() returns a dict from the MessagePack bytes. The server snapshot wire type (ObserverSnapshotWire) has a triangle_crisis_events: Vec<TriangleCrisisEventWire> field (see server/src/bridge/types.rs line ~181). This field is present in the MessagePack output. The client currently ignores it entirely — there is no decode path for triangle_crisis_events in protocol.gd and no handler registered in main.gd.
What to deliver:
-
Decode
triangle_crisis_eventsinprotocol.gddecode_snapshot(). The field is an array of dicts, each with at minimum{ "triangle_id": int }. Add it to the returned snapshot dict as"triangle_crisis_events": Array. -
Handle activation in
main.gd— register a handler that readstriangle_crisis_eventsfrom the snapshot. When the array is non-empty (at least one event), fire the urgent monologue chime:AudioManager.play_one_shot(AudioManager.CHIME_RECOGNITION, AudioManager.BUS_UI_SOUNDS)— wait, check the constant name. The correct asset issfx_monologue_chime_urgent.ogg(D-038, D-067 "sharper variant for contradiction/anomaly").AudioManagerhasconst CHIME_RECOGNITION := "sfx_monologue_chime"— addconst CHIME_ACTIVATION := "sfx_monologue_chime_urgent"if it doesn't exist, then callAudioManager.play_one_shot(CHIME_ACTIVATION, BUS_UI_SOUNDS). -
Deduplication — the triangle activation is a one-shot event (v0.1 fires once per session per D-072/D-089). The client must not fire the chime on every subsequent tick that includes the event in the array. Track activated triangle IDs in a local
Setinmain.gd. Iftriangle_idis already in the set, skip. Add to set on first encounter. -
No overlay UI — the monologue chime is the client-side signal. The copy team (#597) authors the proximity monologue lines that fire when the player observes the activated NPC's
tell_state: RoutineDeviation. The client does not need to render a special overlay or notification — the tell state on the entity and the subsequent proximity monologue are the visible consequence. Keep client reaction to: chime + deduplication tracking only.
Why no overlay: D-039 wow moment #2 ("The Character's Eye") is about the monologue noticing something the player didn't. Adding a UI overlay would make it a notification, not a character observation. The feel is: you're wandering near Kael, suddenly you hear the chime — then the next monologue line is your character's internal voice noticing something is off. The server sends the tell_state: RoutineDeviation on the NPC entity; the client's entity renderer already renders this as visible entity data that can trigger observe_npc monologue.
Blocked by: #589 (server must send non-empty triangle_crisis_events for client to handle).
#592 — News ticker HUD
What exists: client/ui/hud.gd and hud.tscn — main HUD container. client/ui/time_display.gd — insert-style time display already wired via _router.register_always(time_display.update_from_state) in main.gd. No ticker node or script exists. Server snapshot will carry current_ticker: Optional<{ id, text, category }> when player is in the bar zone (#591).
What to deliver:
-
client/ui/news_ticker.gd+news_ticker.tscn— a horizontal scrolling text bar. Design: narrow strip (24–32px tall), anchored top of screen or bottom above the dialogue box, full width. Background: dark semi-transparent (Color(0.05, 0.05, 0.07, 0.75)). Text: scrolls left at a constant rate (~60px/sec). Text content: thetextfield fromcurrent_ticker. Whencurrent_tickerisnull(player is not in bar zone), the ticker hides itself (visible = false). -
Wire in
main.gd— add@onready var news_ticker = $UILayer/NewsTickerand register:_router.register_always(news_ticker.update_from_state). Implementnews_ticker.update_from_state(snapshot: Dictionary): readsnapshot.get("current_ticker"), update text if changed, show/hide based on null. -
Insert overlay compatibility — the ticker lives on
UILayer(z-layer 7 per D-049). When the insert overlay is open (GameState.insert_active = true), the ticker should NOT be hidden — the news terminal is a real-world object the player can see while their insert is open. Do not callset_insert_activeon the ticker. -
Scrolling behavior — the headline scrolls in from the right and exits left. When it exits, it restarts from the right with the same text (the server rotates the headline every 200 ticks; client just loops whatever it currently has). No crossfade, no fade-in. Pure marquee.
UI location: Confirm with the sprint visual check that the ticker does not occlude the time display (top-right insert) or the monologue display (top-center). If there is a conflict, anchor the ticker at the bottom-center above the dialogue box, 4px margin above.
Blocked by: #591 (server must send current_ticker field in snapshot before client has real data to render; before that, the ticker renders nothing and stays hidden).
Dependency Chain
#587 (server: archetype in StartupMessage)
└→ #588 (character select screen) ← start after #587 is merged
└→ PROTOCOL_VERSION 17→19 bump (coordinate with server)
#589 (server: tell escalation)
└→ #590 (triangle activation consumer) ← start after #589 is merged
#591 (server: ticker in snapshot)
└→ #592 (news ticker HUD) ← start after #591 is merged
#588 + #590 + #592 → #593 (playthrough proof — server ticket)
All three client tickets are blocked on their respective server tickets. Start with protocol.gd decode additions speculatively (no server data yet — verify against server/src/bridge/types.rs for field names), then wire the handlers once server branches are merged to main.
PR Workflow
tea pr create --repo jpmschweitzer/settled-reach --login schweitz \
--title "feat(client): character archetype select screen" \
--description "body" --base main --head client
Sprint Completion (Client Criteria)
- From main menu, "New Game" → character select screen appears. Both cards render. Keyboard and mouse selection work. ESC cancels without creating a save directory.
- Selected archetype is persisted in
GameState.character_archetypeand sent inStartupMessage. Server receives correct archetype (verify via debug consolestatus— it should report the active archetype if Dudley adds it to the status response). PROTOCOL_VERSION = 19— client matches server. Connection handshake succeeds.- When
triangle_crisis_eventsis non-empty in snapshot,sfx_monologue_chime_urgent.oggfires once. Does not re-fire on subsequent ticks. - News ticker visible and scrolling in The Last Shift zone. Hidden in all other zones. Text matches the server-sent headline.
make test-clientgreen on client branch. No regressions in existing test suite.