T08 iter 3: GROUND modes, INVESTIGATE, SOLVE
Fills in resolution steps 1, 4 and 6 (GR-R06): - GR-R05 mode choice: a player who revealed GROUND picks GR/OU/ND after Reveal. Resolution refuses to start while any revealed GROUND lacks a mode, and only that player may choose it. - GR-A10 GROUND-GR: self -2 Stress and Freedom readied. - GR-A01 INVESTIGATE: reveal the chosen hidden non-Denied Problem, then draw one Solution; the draw still happens when nothing is revealable. - GR-A02 SOLVE: spend a Solution of the Problem's suit and claim it; a later resolver the same round spends nothing, per Lead order. - GR-A13 tightened: INVESTIGATE must target a hidden Problem, SOLVE a face-up non-Denied one. Previously any existing Problem was accepted. Deck exhaustion (U4) reshuffles the discard, seeded from the game seed and round so validate stays a pure function of state. The resulting order travels inside DeckReshuffled, so replay never re-derives it. Still pending, each because it needs its own decision command rather than a default: GROUND-OU and GROUND-ND three-way choices (GR-A11/A12) and the DARVO stage machine (GR-D02..D07). No scenario claims coverage of them. Filler picks in existing scenarios moved from GROUND to INVESTIGATE: GROUND now has a real Stress effect, which was polluting the Support and Attack assertions. 11 scenarios pass, 34 rules covered; 17 tests, fmt/clippy green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b58a9139aa
commit
27b7fe4329
10 changed files with 417 additions and 13 deletions
|
|
@ -132,6 +132,35 @@ pub struct GroundState {
|
|||
pub step: RoundStep,
|
||||
/// GR-R02: face-down selections, hidden until Reveal.
|
||||
pub selections: BTreeMap<PlayerId, Selection>,
|
||||
/// GR-R05: GROUND modes chosen after Reveal, before Resolve.
|
||||
pub ground_modes: BTreeMap<PlayerId, GroundMode>,
|
||||
/// GR-S04/U4: retained so a deck reshuffle stays a pure function of
|
||||
/// state, keeping `validate` deterministic without holding RNG state.
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
/// GR-A10..A12: the three GROUND modes.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum GroundMode {
|
||||
/// Ground & Restate (GR-A10).
|
||||
Gr,
|
||||
/// Observe & Uphold (GR-A11).
|
||||
Ou,
|
||||
/// Name & Decide (GR-A12).
|
||||
Nd,
|
||||
}
|
||||
|
||||
impl GroundMode {
|
||||
fn parse(raw: &str) -> Result<Self, String> {
|
||||
match raw {
|
||||
"GR" => Ok(GroundMode::Gr),
|
||||
"OU" => Ok(GroundMode::Ou),
|
||||
"ND" => Ok(GroundMode::Nd),
|
||||
other => Err(format!(
|
||||
"unknown GROUND mode {other:?} (expected GR, OU or ND)"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GR-R01: Select → Reveal → Resolve → End.
|
||||
|
|
@ -202,6 +231,9 @@ pub enum GroundCommand {
|
|||
},
|
||||
/// GR-R03: spend the READY Freedom token to bypass the stress gate.
|
||||
SpendFreedom,
|
||||
/// GR-R05: a player who revealed GROUND chooses its mode after
|
||||
/// seeing all revealed Actions.
|
||||
ChooseGroundMode { mode: GroundMode },
|
||||
/// GR-R04: reveal all selections simultaneously. System-driven.
|
||||
Reveal,
|
||||
/// GR-R06/R07: resolve revealed Actions in fixed step order, Lead
|
||||
|
|
@ -248,6 +280,36 @@ pub enum GroundEvent {
|
|||
attacker: PlayerId,
|
||||
target: PlayerId,
|
||||
},
|
||||
/// GR-R05.
|
||||
GroundModeChosen {
|
||||
player: PlayerId,
|
||||
mode: GroundMode,
|
||||
},
|
||||
/// GR-A01: a hidden Problem was turned face up.
|
||||
ProblemRevealed {
|
||||
problem: u32,
|
||||
},
|
||||
/// GR-A01: one Solution drawn from the deck.
|
||||
SolutionDrawn {
|
||||
player: PlayerId,
|
||||
card: SolutionCard,
|
||||
},
|
||||
/// GR-A02: a matching Solution was spent to claim a Problem.
|
||||
SolutionDiscarded {
|
||||
player: PlayerId,
|
||||
card: SolutionCard,
|
||||
},
|
||||
/// GR-A02.
|
||||
ProblemClaimed {
|
||||
problem: u32,
|
||||
by: PlayerId,
|
||||
},
|
||||
/// GR-A01 under the U4 default: the discard was reshuffled into the
|
||||
/// deck. The resulting order travels in the event, so `fold` stays
|
||||
/// deterministic without replaying the RNG.
|
||||
DeckReshuffled {
|
||||
order: Vec<SolutionCard>,
|
||||
},
|
||||
/// GR-D01: Stress 5 at End with the marker OFF.
|
||||
DarvoTriggered {
|
||||
player: PlayerId,
|
||||
|
|
@ -344,6 +406,21 @@ impl Aggregate for GroundState {
|
|||
if self.step != RoundStep::Reveal || actor != Actor::System {
|
||||
return Err(Rejection::NotAllowedNow);
|
||||
}
|
||||
// GR-R05: modes are chosen before resolution begins.
|
||||
let pending: Vec<PlayerId> = self
|
||||
.selections
|
||||
.iter()
|
||||
.filter(|(seat, sel)| {
|
||||
sel.action == Action::Ground && !self.ground_modes.contains_key(seat)
|
||||
})
|
||||
.map(|(seat, _)| *seat)
|
||||
.collect();
|
||||
if !pending.is_empty() {
|
||||
return Err(Rejection::Game {
|
||||
code: "ground-mode-pending".into(),
|
||||
detail: format!("GR-R05: no GROUND mode chosen for {pending:?}"),
|
||||
});
|
||||
}
|
||||
return Ok(self.resolution_events());
|
||||
}
|
||||
GroundCommand::EndRound => {
|
||||
|
|
@ -403,6 +480,31 @@ impl Aggregate for GroundState {
|
|||
}
|
||||
Ok(vec![GroundEvent::FreedomSpent { player: id }])
|
||||
}
|
||||
// GR-R05: only after Reveal, only for a player who revealed
|
||||
// GROUND, once.
|
||||
GroundCommand::ChooseGroundMode { mode } => {
|
||||
if self.step != RoundStep::Reveal {
|
||||
return Err(Rejection::NotAllowedNow);
|
||||
}
|
||||
let revealed_ground = self
|
||||
.selections
|
||||
.get(&id)
|
||||
.is_some_and(|s| s.action == Action::Ground);
|
||||
if !revealed_ground {
|
||||
return Err(Rejection::Game {
|
||||
code: "no-ground-revealed".into(),
|
||||
detail: "GR-R05: only a player who revealed GROUND chooses a mode".into(),
|
||||
});
|
||||
}
|
||||
if self.ground_modes.contains_key(&id) {
|
||||
return Err(Rejection::DuplicateCommand);
|
||||
}
|
||||
let _ = player;
|
||||
Ok(vec![GroundEvent::GroundModeChosen {
|
||||
player: id,
|
||||
mode: *mode,
|
||||
}])
|
||||
}
|
||||
GroundCommand::Reveal | GroundCommand::Resolve | GroundCommand::EndRound => {
|
||||
unreachable!("system commands are handled above")
|
||||
}
|
||||
|
|
@ -442,6 +544,38 @@ impl Aggregate for GroundState {
|
|||
state.protection = state.protection.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
GroundEvent::GroundModeChosen { player, mode } => {
|
||||
self.ground_modes.insert(*player, *mode);
|
||||
}
|
||||
GroundEvent::ProblemRevealed { problem } => {
|
||||
if let Some(state) = self.problems.get_mut(problem) {
|
||||
state.face_up = true;
|
||||
}
|
||||
}
|
||||
GroundEvent::SolutionDrawn { player, card } => {
|
||||
// The deck draws from its end, matching the GR-S02 deal.
|
||||
self.solution_deck.pop();
|
||||
if let Some(state) = self.players.get_mut(player) {
|
||||
state.hand.push(*card);
|
||||
}
|
||||
}
|
||||
GroundEvent::SolutionDiscarded { player, card } => {
|
||||
if let Some(state) = self.players.get_mut(player) {
|
||||
if let Some(pos) = state.hand.iter().position(|c| c == card) {
|
||||
state.hand.remove(pos);
|
||||
}
|
||||
}
|
||||
self.solution_discard.push(*card);
|
||||
}
|
||||
GroundEvent::ProblemClaimed { problem, by } => {
|
||||
if let Some(state) = self.problems.get_mut(problem) {
|
||||
state.claimed_by = Some(*by);
|
||||
}
|
||||
}
|
||||
GroundEvent::DeckReshuffled { order } => {
|
||||
self.solution_deck = order.clone();
|
||||
self.solution_discard.clear();
|
||||
}
|
||||
GroundEvent::DarvoTriggered { player } => {
|
||||
if let Some(state) = self.players.get_mut(player) {
|
||||
state.darvo = DarvoStage::Deny;
|
||||
|
|
@ -454,6 +588,7 @@ impl Aggregate for GroundState {
|
|||
self.round = *round;
|
||||
self.lead = *next_lead;
|
||||
self.selections.clear();
|
||||
self.ground_modes.clear();
|
||||
for state in self.players.values_mut() {
|
||||
// GR-R03: the gate lift lasts one Select step only.
|
||||
state.freedom_gate_lifted = false;
|
||||
|
|
@ -470,9 +605,10 @@ impl Aggregate for GroundState {
|
|||
impl GroundState {
|
||||
/// GR-R06/R07: resolve in fixed step order, Lead first within a step.
|
||||
///
|
||||
/// Steps 1 (GROUND), 3 (DARVO stages), 4 (INVESTIGATE) and 6 (SOLVE)
|
||||
/// are not yet implemented — their Actions resolve as no-ops and no
|
||||
/// scenario claims coverage of them.
|
||||
/// Step 3 (active DARVO stages) is not yet implemented — the DARVO
|
||||
/// machine lands with GR-D02..D07 and no scenario claims coverage of
|
||||
/// it. GROUND—OU and GROUND—ND (GR-A11/A12) each offer a three-way
|
||||
/// choice that needs its own command and are likewise pending.
|
||||
fn resolution_events(&self) -> Vec<GroundEvent> {
|
||||
let mut events = Vec::new();
|
||||
// A working copy so slot counts and Stress reflect earlier
|
||||
|
|
@ -483,6 +619,30 @@ impl GroundState {
|
|||
// (GR-L05): a Bond formed now is not "pre-existing".
|
||||
let pre_existing = self.relations.clone();
|
||||
|
||||
// Step 1 — GROUND (GR-A10..A12).
|
||||
for actor in self.seat_order() {
|
||||
if self.selections.get(&actor).map(|s| s.action) != Some(Action::Ground) {
|
||||
continue;
|
||||
}
|
||||
match self.ground_modes.get(&actor) {
|
||||
// GR-A10: Ground & Restate — self −2 Stress, Freedom READY.
|
||||
Some(GroundMode::Gr) => {
|
||||
let stress = work.stress_after(actor, -2);
|
||||
events.push(GroundEvent::StressSet {
|
||||
player: actor,
|
||||
stress,
|
||||
});
|
||||
work.fold(events.last().expect("just pushed"));
|
||||
if !work.players[&actor].freedom_ready {
|
||||
events.push(GroundEvent::FreedomReadied { player: actor });
|
||||
work.fold(events.last().expect("just pushed"));
|
||||
}
|
||||
}
|
||||
// GR-A11/A12 need a sub-choice command; pending.
|
||||
Some(GroundMode::Ou) | Some(GroundMode::Nd) | None => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2 — Support (GR-A03/A04/A05).
|
||||
for actor in self.seat_order() {
|
||||
let Some(selection) = self.selections.get(&actor) else {
|
||||
|
|
@ -602,12 +762,94 @@ impl GroundState {
|
|||
}
|
||||
}
|
||||
|
||||
// Step 4 — INVESTIGATE (GR-A01).
|
||||
for actor in self.seat_order() {
|
||||
let Some(selection) = self.selections.get(&actor) else {
|
||||
continue;
|
||||
};
|
||||
if selection.action != Action::Investigate {
|
||||
continue;
|
||||
}
|
||||
// Reveal the chosen Problem if it is still hidden and not
|
||||
// Denied; otherwise the draw happens on its own.
|
||||
if let Some(problem) = selection.problem {
|
||||
let eligible = work
|
||||
.problems
|
||||
.get(&problem)
|
||||
.is_some_and(|p| !p.face_up && !p.denied);
|
||||
if eligible {
|
||||
events.push(GroundEvent::ProblemRevealed { problem });
|
||||
work.fold(events.last().expect("just pushed"));
|
||||
}
|
||||
}
|
||||
work.draw_solution(actor, &mut events);
|
||||
}
|
||||
|
||||
// Step 6 — SOLVE (GR-A02).
|
||||
for actor in self.seat_order() {
|
||||
let Some(selection) = self.selections.get(&actor) else {
|
||||
continue;
|
||||
};
|
||||
if selection.action != Action::Solve {
|
||||
continue;
|
||||
}
|
||||
let Some(problem) = selection.problem else {
|
||||
continue;
|
||||
};
|
||||
let Some(target) = work.problems.get(&problem) else {
|
||||
continue;
|
||||
};
|
||||
// GR-A02: an earlier resolver this round already claimed it,
|
||||
// so no Solution is spent and nothing happens.
|
||||
if target.claimed_by.is_some() || target.denied || !target.face_up {
|
||||
continue;
|
||||
}
|
||||
let required = target.suit;
|
||||
let Some(card) = work.players[&actor]
|
||||
.hand
|
||||
.iter()
|
||||
.find(|c| c.suit == required)
|
||||
.copied()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
events.push(GroundEvent::SolutionDiscarded {
|
||||
player: actor,
|
||||
card,
|
||||
});
|
||||
work.fold(events.last().expect("just pushed"));
|
||||
events.push(GroundEvent::ProblemClaimed { problem, by: actor });
|
||||
work.fold(events.last().expect("just pushed"));
|
||||
}
|
||||
|
||||
events.push(GroundEvent::StepAdvanced {
|
||||
step: RoundStep::Resolve,
|
||||
});
|
||||
events
|
||||
}
|
||||
|
||||
/// GR-A01: draw one Solution, reshuffling the discard first if the
|
||||
/// deck is empty (the U4 default). The reshuffled order travels in
|
||||
/// the event so replay never re-derives it.
|
||||
fn draw_solution(&mut self, player: PlayerId, events: &mut Vec<GroundEvent>) {
|
||||
if self.solution_deck.is_empty() {
|
||||
if self.solution_discard.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut order = self.solution_discard.clone();
|
||||
// Derived from the game seed and round, so the reshuffle is
|
||||
// a pure function of state (GameKernel K5).
|
||||
let mut rng = ChaChaRng::from_seed(Seed(self.seed ^ u64::from(self.round)));
|
||||
rng.shuffle(&mut order);
|
||||
events.push(GroundEvent::DeckReshuffled { order });
|
||||
self.fold(events.last().expect("just pushed"));
|
||||
}
|
||||
if let Some(card) = self.solution_deck.last().copied() {
|
||||
events.push(GroundEvent::SolutionDrawn { player, card });
|
||||
self.fold(events.last().expect("just pushed"));
|
||||
}
|
||||
}
|
||||
|
||||
/// GR-R08: Stress is already clamped on every application (U2), so
|
||||
/// End only triggers DARVO, rotates the Lead, and advances the Round.
|
||||
fn end_round_events(&self) -> Vec<GroundEvent> {
|
||||
|
|
@ -668,8 +910,22 @@ impl GroundState {
|
|||
if action.requires_problem_target() {
|
||||
let problem =
|
||||
problem.ok_or_else(|| bad(format!("GR-A13: {action:?} needs a Problem")))?;
|
||||
if !self.problems.contains_key(&problem) {
|
||||
return Err(bad(format!("GR-A13: no Problem {problem}")));
|
||||
let target = self
|
||||
.problems
|
||||
.get(&problem)
|
||||
.ok_or_else(|| bad(format!("GR-A13: no Problem {problem}")))?;
|
||||
match action {
|
||||
// GR-A13: INVESTIGATE targets a hidden Problem.
|
||||
Action::Investigate if target.face_up => {
|
||||
return Err(bad(format!("GR-A13: Problem {problem} is already face up")));
|
||||
}
|
||||
// GR-A13: SOLVE targets a face-up, non-Denied Problem.
|
||||
Action::Solve if !target.face_up || target.denied => {
|
||||
return Err(bad(format!(
|
||||
"GR-A13: Problem {problem} is not a face-up, non-Denied Problem"
|
||||
)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if problem.is_some() {
|
||||
return Err(bad(format!("GR-A13: {action:?} takes no Problem target")));
|
||||
|
|
@ -770,6 +1026,8 @@ impl ScenarioGame for GroundState {
|
|||
focus: BTreeMap::new(),
|
||||
step: RoundStep::Select,
|
||||
selections: BTreeMap::new(),
|
||||
ground_modes: BTreeMap::new(),
|
||||
seed,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -800,6 +1058,9 @@ impl ScenarioGame for GroundState {
|
|||
}
|
||||
}
|
||||
"spend_freedom" => GroundCommand::SpendFreedom,
|
||||
"choose_ground_mode" => GroundCommand::ChooseGroundMode {
|
||||
mode: GroundMode::parse(&arg_str("mode")?)?,
|
||||
},
|
||||
"reveal" => GroundCommand::Reveal,
|
||||
"resolve" => GroundCommand::Resolve,
|
||||
"end_round" => GroundCommand::EndRound,
|
||||
|
|
@ -841,6 +1102,8 @@ mod tests {
|
|||
focus: BTreeMap::new(),
|
||||
step: RoundStep::Select,
|
||||
selections: BTreeMap::new(),
|
||||
ground_modes: BTreeMap::new(),
|
||||
seed: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
43
scenarios/ground/gr-a01-investigate.yaml
Normal file
43
scenarios/ground/gr-a01-investigate.yaml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
scenario: ground/gr-a01-investigate
|
||||
description: >
|
||||
INVESTIGATE reveals the chosen hidden Problem and draws one Solution
|
||||
(GR-A01). Selecting an already face-up Problem is rejected (GR-A13).
|
||||
covers: [GR-A01, GR-S01]
|
||||
provisional: false
|
||||
seed: 42
|
||||
setup:
|
||||
players: 3
|
||||
preset: standard-3p
|
||||
patch:
|
||||
"lead": 0
|
||||
commands:
|
||||
- actor: P1
|
||||
cmd: select_action
|
||||
args: { action: INVESTIGATE, problem: 2 }
|
||||
# GR-A13: Problem 1 is the Surface Problem, already face up.
|
||||
- actor: P2
|
||||
cmd: select_action
|
||||
args: { action: INVESTIGATE, problem: 1 }
|
||||
- actor: P2
|
||||
cmd: select_action
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: P3
|
||||
cmd: select_action
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: SYSTEM
|
||||
cmd: reveal
|
||||
- actor: SYSTEM
|
||||
cmd: resolve
|
||||
expect:
|
||||
events:
|
||||
- kind: ProblemRevealed
|
||||
problem: 2
|
||||
- kind: SolutionDrawn
|
||||
player: 0
|
||||
state:
|
||||
"problems.2.face_up": true
|
||||
"problems.3.face_up": true
|
||||
# GR-S02 dealt two; INVESTIGATE drew a third. The suit is the
|
||||
# golden value for seed 42 under the GR-S04 shuffle.
|
||||
"players.0.hand.2.suit": Change
|
||||
rejects: [1]
|
||||
43
scenarios/ground/gr-a02-solve.yaml
Normal file
43
scenarios/ground/gr-a02-solve.yaml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
scenario: ground/gr-a02-solve
|
||||
description: >
|
||||
SOLVE discards a Solution of the Problem's suit and claims it
|
||||
(GR-A02). A second solver of the same Problem the same round spends
|
||||
nothing, because an earlier resolver in Lead order already claimed it.
|
||||
covers: [GR-A02, GR-R07, GR-O04]
|
||||
provisional: false
|
||||
seed: 42
|
||||
setup:
|
||||
players: 3
|
||||
preset: standard-3p
|
||||
patch:
|
||||
"lead": 0
|
||||
"players.0.hand": [{ suit: Clarify }]
|
||||
"players.1.hand": [{ suit: Clarify }]
|
||||
commands:
|
||||
- actor: P1
|
||||
cmd: select_action
|
||||
args: { action: SOLVE, problem: 1 }
|
||||
- actor: P2
|
||||
cmd: select_action
|
||||
args: { action: SOLVE, problem: 1 }
|
||||
- actor: P3
|
||||
cmd: select_action
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: SYSTEM
|
||||
cmd: reveal
|
||||
- actor: SYSTEM
|
||||
cmd: resolve
|
||||
expect:
|
||||
events:
|
||||
- kind: SolutionDiscarded
|
||||
player: 0
|
||||
- kind: ProblemClaimed
|
||||
problem: 1
|
||||
by: 0
|
||||
state:
|
||||
# Problem 1 is the Clarify Surface Problem (GR-S01).
|
||||
"problems.1.claimed_by": 0
|
||||
"players.0.hand": []
|
||||
# GR-A02: P2 resolved second, so its Solution is not spent.
|
||||
"players.1.hand": [{ suit: Clarify }]
|
||||
rejects: []
|
||||
|
|
@ -24,7 +24,7 @@ commands:
|
|||
args: { action: SUPPORT, target: P3 }
|
||||
- actor: P3
|
||||
cmd: select_action
|
||||
args: { action: GROUND }
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: SYSTEM
|
||||
cmd: reveal
|
||||
- actor: SYSTEM
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ commands:
|
|||
args: { action: ATTACK, target: P2 }
|
||||
- actor: P2
|
||||
cmd: select_action
|
||||
args: { action: GROUND }
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: P3
|
||||
cmd: select_action
|
||||
args: { action: GROUND }
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: SYSTEM
|
||||
cmd: reveal
|
||||
- actor: SYSTEM
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ commands:
|
|||
args: { action: ATTACK, target: P3 }
|
||||
- actor: P3
|
||||
cmd: select_action
|
||||
args: { action: GROUND }
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: SYSTEM
|
||||
cmd: reveal
|
||||
- actor: SYSTEM
|
||||
|
|
|
|||
55
scenarios/ground/gr-a10-ground-gr.yaml
Normal file
55
scenarios/ground/gr-a10-ground-gr.yaml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
scenario: ground/gr-a10-ground-gr
|
||||
description: >
|
||||
GROUND—GR (Ground & Restate): the mode is chosen after Reveal
|
||||
(GR-R05), and on resolution the player takes −2 Stress and readies
|
||||
their Freedom token (GR-A10, GR-F04). Resolution refuses to start
|
||||
while any revealed GROUND still lacks a mode.
|
||||
covers: [GR-R05, GR-A10]
|
||||
provisional: false
|
||||
seed: 42
|
||||
setup:
|
||||
players: 3
|
||||
preset: standard-3p
|
||||
patch:
|
||||
"lead": 0
|
||||
"players.0.stress": 5
|
||||
"players.0.freedom_ready": false
|
||||
commands:
|
||||
- actor: P1
|
||||
cmd: select_action
|
||||
args: { action: GROUND }
|
||||
- actor: P2
|
||||
cmd: select_action
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: P3
|
||||
cmd: select_action
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: SYSTEM
|
||||
cmd: reveal
|
||||
# GR-R05: no mode chosen yet, so resolution is refused.
|
||||
- actor: SYSTEM
|
||||
cmd: resolve
|
||||
# GR-R05: only a player who revealed GROUND may choose a mode.
|
||||
- actor: P2
|
||||
cmd: choose_ground_mode
|
||||
args: { mode: GR }
|
||||
- actor: P1
|
||||
cmd: choose_ground_mode
|
||||
args: { mode: GR }
|
||||
- actor: SYSTEM
|
||||
cmd: resolve
|
||||
expect:
|
||||
events:
|
||||
- kind: GroundModeChosen
|
||||
player: 0
|
||||
mode: Gr
|
||||
- kind: StressSet
|
||||
player: 0
|
||||
stress: 3
|
||||
- kind: FreedomReadied
|
||||
player: 0
|
||||
state:
|
||||
"players.0.stress": 3
|
||||
"players.0.freedom_ready": true
|
||||
"step": Resolve
|
||||
rejects: [4, 5]
|
||||
|
|
@ -18,10 +18,10 @@ commands:
|
|||
args: { action: ATTACK, target: P2 }
|
||||
- actor: P2
|
||||
cmd: select_action
|
||||
args: { action: GROUND }
|
||||
args: { action: ATTACK, target: P3 }
|
||||
- actor: P3
|
||||
cmd: select_action
|
||||
args: { action: GROUND }
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: SYSTEM
|
||||
cmd: reveal
|
||||
- actor: SYSTEM
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ commands:
|
|||
# GR-R02: one selection per player per round.
|
||||
- actor: P1
|
||||
cmd: select_action
|
||||
args: { action: GROUND }
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
expect:
|
||||
events:
|
||||
- kind: ActionSelected
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ commands:
|
|||
args: { action: SUPPORT, target: P3 }
|
||||
- actor: P3
|
||||
cmd: select_action
|
||||
args: { action: GROUND }
|
||||
args: { action: INVESTIGATE, problem: 3 }
|
||||
- actor: SYSTEM
|
||||
cmd: reveal
|
||||
- actor: SYSTEM
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue