Stop auto-assigning the Captain when a ship is stolen

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>
This commit is contained in:
2026-06-14 09:30:46 -07:00
parent 39dc07d6b6
commit ceaf88ef22
3 changed files with 13 additions and 22 deletions

View File

@@ -14,7 +14,7 @@
- [x] When Deep issues a challenge, stakes should be two fields, success and failure
- [ ] Captain should not be autoassigned. That's something the crew has to earn.
- [x] Captain should not be autoassigned. That's something the crew has to earn.
- [ ] The back button on the admin page should go back to the active game, not the join screen

View File

@@ -1,5 +1,4 @@
import json
import random
from typing import Tuple, Dict, Any
from sqlmodel import Session
from .models import Game, Player, Obstacle
@@ -310,19 +309,11 @@ def toggle_objective(db: Session, game_id: str, target_id: str, obj_type: str, s
if not game:
return
if obj_type == "crew_1":
# Stealing a ship no longer auto-anoints a Captain — the crew has to
# earn and choose one themselves (Crew Objective 2 + the Captaincy UI).
game.completed_crew_1 = status
db.add(game)
db.commit()
if status and not game.captain_player_id:
# "Once the crew controls a ship, the highest-Ranked Pi-Rat becomes the de facto Captain."
pi_rats = [p for p in game.players if p.role != "deep" and not p.is_dead and not p.is_ghost]
if pi_rats:
max_rank = max(p.rank for p in pi_rats)
candidates = [p for p in pi_rats if p.rank == max_rank]
set_captain(db, game, random.choice(candidates).id, "Highest-ranked Pi-Rat aboard the stolen ship")
elif not status and game.captain_player_id:
# The ship is lost: the Captain's position goes with it.
set_captain(db, game, None, "The ship was lost")
elif obj_type == "crew_2":
game.completed_crew_2 = status
db.add(game)

View File

@@ -357,20 +357,20 @@ def test_captaincy_and_hand_sizes(session):
session.commit()
assert crud.calculate_max_hand_size(p3, game.players, game.captain_player_id) == 4
# Completing Crew Objective 1 (Steal a Ship) makes the highest-ranked Pi-Rat the Captain
# Stealing a ship does NOT auto-assign a Captain — the crew must earn and
# choose one. Completing Crew Objective 1 leaves the captaincy vacant.
crud.toggle_objective(session, game.id, p1.id, "crew_1", True)
session.refresh(game)
assert game.captain_player_id in (p2.id, p3.id)
captain = crud.get_player(session, game.captain_player_id)
assert crud.is_player_captain(captain, game)
# Captain privilege: +1 hand size
assert crud.calculate_max_hand_size(captain, game.players, game.captain_player_id) == 5
# Losing the ship vacates the Captaincy
crud.toggle_objective(session, game.id, p1.id, "crew_1", False)
session.refresh(game)
assert game.captain_player_id is None
# The crew chooses a Captain explicitly (via the Captaincy UI / set_captain).
crud.set_captain(session, game, p2.id)
session.refresh(game)
assert game.captain_player_id == p2.id
assert crud.is_player_captain(p2, game)
# Captain privilege: +1 hand size
assert crud.calculate_max_hand_size(p2, game.players, game.captain_player_id) == 5
def test_captain_tax_on_failed_challenge(session):
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
# p3 (rank 2) becomes captain