Returning players are the common case, so surface their saved crews first.
The two glass panels had no margin between them; add spacing on the
Rejoin box so it sits clear of the Muster box below it.
Also tidies the TODO 'Polish' section (renamed from 'UI Polish'; drops the
now-committed Rank-3 bonus 'Gameplay gaps' entry).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the roster banner and the standalone character-sheet panel with
a left-hand column of crew bubbles. Each bubble shows the player's role
icon, name, rank, current hand size, and a 🛞 for the Captain; clicking
one pops out that player's character sheet as a modal.
- New CrewColumn.svelte: bubbles (Pi-Rats first, Deep last) + an
Objectives button that opens a crew-objectives popup.
- CharacterSheet.svelte is now a target-driven modal: description,
likes/hates, and personal objectives for anyone; secret J/Q/K only on
your own sheet; the Duel UI (no "Challenge whom?" — target is fixed)
only when a living Pi-Rat views another living Pi-Rat.
- ScenePhase.svelte → 3-column layout (crew | obstacles | hand/deep);
crew column collapses to a horizontal strip on narrow screens.
- Shared .modal-backdrop/.modal-box/.modal-close moved into components.css;
removed the now-dead roster-banner and captain-badge styles.
Backend unchanged — the state blob already carries hands, roles, ranks,
objectives, and the captain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
During between_scenes there's now just one centered panel with the
voting controls, nomination progress, and Crew Rank Ledger. The separate
"Resting Deep Upkeep" panel (which only said hand refresh would happen
later) is rendered solely during deep_upkeep, where the actual discard/
redraw happens. Moved the rank ledger into the voting panel and added a
.between-grid.single layout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirrors the name modal: earning the Gat objective sets
Player.needs_gat_description, and GatModal.svelte (rendered by Dashboard
across all phases) prompts for a one-of-a-kind Gat description, stored in
Player.gat_description and shown on the character sheet. Like a stolen
Name, the Gat's description travels with it through Gat Tax transfers.
- Player.gat_description / needs_gat_description (+ migration)
- toggle_objective personal_1 sets/clears the prompt
- crud_challenge gat-tax paths move the description with the Gat
- set-gat-description route; CharacterSheet displays it
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the Event Log is collapsed, a freshly-arrived event now flashes as
a toast anchored above the button, then animates down and fades into it.
Suppressed on the initial state load and on rollback reseeds so it only
fires for genuinely new events; clicking the toast opens the log.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Admin page's back button now returns to this browser's own Pi-Rat
dashboard for the game (looked up from the saved sessions), labelled
"← Back to Game". Falls back to the join page only when this browser has
no saved session for the game.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completing Crew Objective 1 (Steal a Ship) no longer anoints the
highest-ranked Pi-Rat as Captain (nor vacates the seat when the ship is
lost). Captaincy is now earned: the crew chooses one via the Captaincy
UI / set_captain and Crew Objective 2. Decoupled captaincy from crew_1
in toggle_objective, dropped the now-unused random import, and updated
test_captaincy_and_hand_sizes accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Deep now states what happens on success and on failure as two
separate fields when calling a Challenge, instead of one free-text
stakes blob. ChallengePanel shows them as "✅ On success" / "❌ On
failure"; the event log lists both.
- Challenge.stakes_success / stakes_failure (+ migration); the old
single `stakes` column is kept (vestigial) so in-scene rollback
snapshots taken before the upgrade still deserialize, and ChallengePanel
falls back to it for any pre-existing challenge.
- create_challenge / route / DeepControlPanel updated; tests adjusted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The post-voting (deep_upkeep) screen now names who won the rank-up vote
and their new rank, instead of just "Voting complete!". Adds
Game.last_rank_up_player_id (+ migration), set when votes are tallied
and cleared when the next scene begins; falls back to a "no one ranked
up" message when there were no votes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After a resting Deep player confirms their hand refresh, replace the
discard/keep drag editor with a confirmation panel showing the freshly
drawn hand and a "Waiting for the rest of the crew" message, instead of
re-rendering the editable drag UI (which made it look like nothing
happened). Also stops nagging "please complete your Hand Refresh" once
they're done. Visible whenever they remain in deep_upkeep waiting on
other resting Deep players.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An admin can now opt into "teaching mode" from the lobby, seeding
themselves as the Rank-3 player who must play the Deep in the first
scene (instead of leaving it to the random starting-rank roll), so they
can run the table for new crews.
- Game.teaching_deep_player_id (+ Alembic migration)
- maybe_finish_assign_techniques honors it before rolling ranks, so both
the normal flow and the dev-mode skip path respect it
- Admin-gated POST .../teaching-mode toggle (pre-rank phases only)
- Lobby checkbox UI, greyed out if another admin already volunteered
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking Revise on basic records, techniques, or J/Q/K assignment now
offers a Cancel button that throws away any edits made since Revise and
restores the previously-saved values. Basic-records cancel is a pure
client-side toggle; techniques/face cancel re-submit a snapshot taken at
Revise time (since Revise immediately unsubmits/unassigns server-side).
Also added Cancel to the recruit-creation basic-records revise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a Pi-Rat completes their second objective and ranks up, they now
get a blocking modal popup (NameModal.svelte, rendered by Dashboard
across all phases) to claim their Pirate Name, rather than an optional
box buried in their character sheet. Removed the old inline prompt-box
and its handler from CharacterSheet.svelte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split multi-statement-on-one-line colon constructs in crud_scene.py
and routes_scene.py onto separate lines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rolling back is now non-destructive: instead of truncating the future,
it restores the target checkpoint's snapshot and points
Game.rollback_head_checkpoint_id at it. Later checkpoints/events are
kept and rendered greyed; rolling forward to one redoes the rollback
(same endpoint). The abandoned future is discarded only when a new
action is taken from a rolled-back state -- maintain_rollback_history
truncates it, clears the head, and bumps rollback_timeline_version,
now the sole trigger for the frontend log reset.
- Game.rollback_head_checkpoint_id (nullable; in SNAPSHOT_EXCLUDE) +
Alembic migration.
- crud_rollback: rollback_to_checkpoint sets the head (None at latest);
_discard_future + head-clear + version-bump moved into the next scene
action's capture.
- EventLog.svelte: greyed future, redo (forward) affordance, a "rolled
back" banner; dropped the now-misleading destructive confirm.
- Tests rewritten for head-pointer semantics (+ redo, action-after-
rollback truncation, scene-end head clear); HTTP integration test
extended to drive the full cycle over the wire.
Verified end-to-end in a browser: rollback greys the future and reverts
live state (captaincy/ranks), redo restores it, no errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full-state JSON snapshots let a scene be rolled back to an earlier
action. All game state lives in game_id-scoped tables, so a checkpoint
is just a serialized dump of the gameplay rows and rollback restores
it -- sidestepping deterministic replay (the deck shuffle is
materialized into state and captured verbatim).
- Checkpoint table + GameEvent.checkpoint_id (tri-state) +
Game.rollback_timeline_version (models.py, Alembic migration).
- crud_rollback.py: serialize/apply/capture/seal-purge/rollback.
- Capture is driven by the broadcast middleware: snapshot per action
while in the `scene` phase, seal+purge otherwise. Confined to the
current scene; older scenes' checkpoints are purged at scene end.
- POST /game/{gid}/player/{pid}/rollback (routes_rollback.py),
server-enforced for Admins and Deep players.
- EventLog.svelte: per-event rollback buttons + timeline reconciliation.
- Remove the orphaned /scene/rollback per-card-play undo (dead code
since d7f8483, never wired up; superseded by full-state rollback).
Phase 1 truncates the future immediately; the greyed/undoable redo is
deferred to Phase 2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Admins can now kick players from the Admin panel so a vanished player can't
stall the table. crud.kick_player removes a player mid-game: it drops their
captaincy, deletes their votes and any Challenge they're part of, and repairs
every other player's pointers to them (pending swap offers are cleared;
delegated Like/Hate questions and recruit technique slots are handed to a
present crewmate, or pool-filled when no one is left). The row is then deleted
— a kicked player's hand simply stops being "in play", so it returns to the
discard automatically. Finally recheck_phase_completion advances the phase if
the kicked player was the last thing it was waiting on.
Auth mirrors set-admin (admin-key on POST /api/game/{gid}/admin/kick). The one
guard is that the last remaining Admin can't be kicked, so the table is never
locked out of its own panel; a vanished creator can still be kicked once a
co-admin exists. The Admin panel gains a per-row Kick button (with a confirm),
shown as "—" for the last admin.
To support re-checking phase completion after a kick, each phase's
"everyone's done -> advance" gate was extracted into an idempotent maybe_*
helper, and recheck_phase_completion dispatches the right one per Game.phase.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dead and awaiting-recruit Pi-Rats can no longer be nominated to rank up
(they'd reset to Rank 1 anyway). submit_rank_vote rejects them, and the
upkeep dropdown only lists living, in-play crewmates via the shared
eligible_vote_nominees helper.
When a voter has no eligible nominees (e.g. a lone survivor whose only
crewmates are the previous Deep and a dead rat) they now skip voting
entirely and can ready up directly, instead of deadlocking — which is
why dead players were previously left nominable. The roster shows such
players as "No vote".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Joining a game now records the session (game ID, player ID, plus
player/rat/crew names) in localStorage via the new lib/sessions.js, and
the dashboard keeps it enriched as state loads (so the rat name tracks
earned Names). The home page lists saved sessions in a "Rejoin a Crew"
menu, most-recent first, alongside creating a new game.
Deleting an entry only touches localStorage, never the backend, and is
undoable: the row greys out with an Undo button and is dropped from
storage immediately, so a page refresh finalizes the removal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each routed page now sets a descriptive document.title so tabs and
browser history entries are distinguishable: the dashboard shows the
Pi-Rat (and human player) name plus crew, the admin panel shows
"Admin · {crew}", the join page is labeled, and home resets to the
app name. New lib/title.js helper composes "{parts} — Rats with Gats".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Admin panel now shows the crew's join link (origin/#/game/{id}/join)
with Open and Copy buttons, so an admin can invite new players without
returning to the lobby. Matches the existing rejoin-link copy pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Card tooltips now show what a card represents *as* an Obstacle (the
rulebook obstacle table entry) in addition to its suit theme, which is
relevant when challenging another Pi-Rat. The obstacle tables are served
from the backend via GET /api/obstacles (single source of truth in
cards.py) and cached client-side in cards.js, with a retry so a transient
first-load failure doesn't silently disable the tooltips.
Enriched everywhere cards appear in the challenge flow: hand/board cards
(Card.svelte), PvP defend buttons and the temporary-obstacle line
(ChallengePanel), and the duel card selector, which also shows an
inline description of what the chosen card becomes as an Obstacle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Players can now unlock and revise any reasonably revisable choice while
character creation is ongoing: basic Rat Records (also in recruit
creation), submitted techniques (until the swap phase begins), J/Q/K
face-card assignments (until everyone is ready), and Like/Hate answers
written for crewmates. New endpoints unsubmit-techniques and
unassign-face-techniques clear the locked-in state server-side so the
phase machine waits for the revision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Enforced server-side in lobby/start and confirm_scene_setup via
crud_base.has_min_players; Dev Mode bypasses it so local single-player
testing still works. Lobby and scene-setup UIs show a player-count
message instead of the start button when below the minimum.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds Player.is_admin (creator starts with it; backfilled by migration).
Admin-key-authenticated POST /admin/set-admin grants/revokes it from the
admin panel; the creator's privileges can't be revoked. Admin-gated
backend routes (dev mode, skip character creation) and frontend controls
(corner menu, lobby start, scene start, end story) now check is_admin
instead of is_creator, and admins get the admin_key in their state blob
so granted players can open the admin panel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CSS refactor: every color, font, radius, and shadow is now defined once
in theme.css; all other styles derive translucent variants with
color-mix(), so re-theming means editing one file. Removed the
duplicate core.css/style.css import chains, dead styles, and the
Bootstrap/Tailwind orphan classes that were silently unstyled
(the main reason the splash page looked broken).
New "Lantern & Brine" look: brass and parchment on a lantern-lit
ink-navy night, Pirata One wordmark with Alegreya SC/Sans body fonts
(self-hosted via fontsource — they were previously referenced but
never loaded), parchment-faced playing cards, and a rebuilt splash
page. Inline color styles in components were replaced with semantic
classes (prompt-box, notice-banner, status chips, etc.), the rulebook's
embedded palette was updated to match, and the leftover Vite favicon
and "frontend" page title were replaced.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New /#/rules route renders the full rulebook transcribed from
"Rats with Gats beta.pdf" (intro, character creation, gameplay,
obstacle charts, challenge resolution, example of play, and setting
info), with an in-page table of contents. A fixed corner link in
App.svelte opens it in a new tab from every page, and the splash
page's previously broken /rules link now points at the new route.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The lobby Copy buttons now flip to a transient "✓ Copied" label instead
of popping a modal alert, matching the admin page's copy buttons.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each player row now has Open/Copy actions for their dashboard link, so
the creator can recover access for players who lose their URL.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>