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>