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 - [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 - [ ] 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 json
import random
from typing import Tuple, Dict, Any from typing import Tuple, Dict, Any
from sqlmodel import Session from sqlmodel import Session
from .models import Game, Player, Obstacle 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: if not game:
return return
if obj_type == "crew_1": 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 game.completed_crew_1 = status
db.add(game) db.add(game)
db.commit() 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": elif obj_type == "crew_2":
game.completed_crew_2 = status game.completed_crew_2 = status
db.add(game) db.add(game)

View File

@@ -357,20 +357,20 @@ def test_captaincy_and_hand_sizes(session):
session.commit() session.commit()
assert crud.calculate_max_hand_size(p3, game.players, game.captain_player_id) == 4 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) crud.toggle_objective(session, game.id, p1.id, "crew_1", True)
session.refresh(game) 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 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): def test_captain_tax_on_failed_challenge(session):
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2) game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
# p3 (rank 2) becomes captain # p3 (rank 2) becomes captain