CB-WP-0047: all four boards, and every mode named on the page
Some checks failed
ci / check (push) Failing after 3s
Some checks failed
ci / check (push) Failing after 3s
The modes were already implemented; nothing had ever COMPARED them. The scenarios were not implemented at all: edition::deal has taken a scenario_id since it was written and the only caller passed the literal "SCN_01", so 15 of 20 Problem cards had never been dealt by anything. The seam was the whole mechanism and it sat unused, with nothing red because nothing asked. Scenario is now state (serde default SCN_01, so all 26 recordings replay unchanged), selected by preset `scn-03-4p` with `standard-Np` still meaning SCN_01, and by --scenario/SCENARIO= accepting ids, numbers or titles, validated against the edition rather than a pattern. The threshold now comes off the Scenario card, closing F25's hardcoded 5/7/9. The first version of that control was worthless and mutation said so: all four scenarios print 5/7/9, so reverting to the bands left it green. Split threshold_from() so it can be handed a card that disagrees. The header read `scoring CommonProblem` where the Mode card is titled COMMON PROBLEM, PERSONAL EDGE -- the defect CB-WP-0034 deleted from the move buttons, still standing on the line that says what winning means. The coverage probe was matching that Debug output and went red when it was fixed: third instance (CB-WP-0024, CB-WP-0034). Page now carries the premise, the mode's rules text, and the tiebreak. scenario-panel plays 4x3x3. Findings: SCN_01 and SCN_02 are the same board (identical cells, pinned by a characterisation test); SCN_04 is the hard board at 2p (52% vs 67/73%, the only deck needing two Repair); and group success is EXACTLY equal across all three modes in all 36 cells, because greedy never reads state.mode -- filed F27, the two competitive modes are scoring lenses over cooperative play. F28: SHARED GROUND's mastery subtracts penalties from the claimed COUNT where the mode card's shared score is claimed VALUE. Raised, not fixed; scoring is ground-game's to rule on. Also fixes design.py reporting a backticked path as no reproduction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
1d3f1bfe60
commit
d30938b259
19 changed files with 1351 additions and 109 deletions
|
|
@ -1288,6 +1288,7 @@ mod tests {
|
|||
serve: Some(0),
|
||||
trial: None,
|
||||
mode: games_ground::ScoringMode::SharedGround,
|
||||
scenario: "SCN_01".into(),
|
||||
pace: crate::table::Pace::Speed,
|
||||
variant: games_ground::Variant::Baseline,
|
||||
},
|
||||
|
|
@ -1479,6 +1480,7 @@ mod tests {
|
|||
serve: Some(0),
|
||||
trial: None,
|
||||
mode: games_ground::ScoringMode::SharedGround,
|
||||
scenario: "SCN_01".into(),
|
||||
pace: crate::table::Pace::Speed,
|
||||
variant: games_ground::Variant::Baseline,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -140,12 +140,18 @@ fn render_player(id: PlayerId, p: &PlayerView, is_viewer: bool) -> String {
|
|||
pub fn render(view: &GroundView) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!(
|
||||
"\nround {} step {:?} lead {} mode {:?} rules {} deck {} discard [{}]\n",
|
||||
"\nround {} step {:?} lead {} mode {:?} rules {} scenario {} \
|
||||
deck {} discard [{}]\n",
|
||||
view.round,
|
||||
view.step,
|
||||
seat_name(view.lead),
|
||||
view.mode,
|
||||
view.variant.id(),
|
||||
// CB-WP-0047: WHICH of the four boards. The id, not the title,
|
||||
// because this is the machine-facing view — `cb-play inspect` is
|
||||
// read by a maintainer diffing states, where the HTML page is
|
||||
// read by a player and shows the printed title.
|
||||
view.scenario,
|
||||
view.solution_deck_len,
|
||||
cards(&view.solution_discard),
|
||||
));
|
||||
|
|
@ -497,6 +503,7 @@ mod tests {
|
|||
// CB-WP-0044: the inspector must say which rules it is replaying,
|
||||
// for the same reason the page must.
|
||||
("variant", "rules ground-darvo-r0"),
|
||||
("scenario", "scenario SCN_"),
|
||||
("viewer", "(you)"),
|
||||
("solution_deck_len", "deck 11"),
|
||||
("solution_discard.*.suit", "discard [Repair, Change]"),
|
||||
|
|
@ -614,6 +621,7 @@ mod tests {
|
|||
lead: p2,
|
||||
step: RoundStep::Resolve,
|
||||
mode: ScoringMode::BondedCoalitions,
|
||||
scenario: "SCN_01".into(),
|
||||
players,
|
||||
relations: BTreeMap::from([
|
||||
(Pair::new(p1, p2), Relation::Bond),
|
||||
|
|
@ -770,6 +778,7 @@ mod tests {
|
|||
serve: None,
|
||||
trial: None,
|
||||
mode: games_ground::ScoringMode::SharedGround,
|
||||
scenario: "SCN_01".into(),
|
||||
pace: crate::table::Pace::Speed,
|
||||
variant: games_ground::Variant::Baseline,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -62,6 +62,61 @@ enum Mode {
|
|||
},
|
||||
}
|
||||
|
||||
/// A scenario as the player names it, as the edition names it.
|
||||
///
|
||||
/// Accepts `2`, `02`, `scn-02`, `SCN_02` and the title's first word, so
|
||||
/// that `--scenario confidence` works — the ids are the edition's
|
||||
/// vocabulary and *"Broken Confidence"* is the player's.
|
||||
///
|
||||
/// **Validated against the edition, never against a pattern.** `SCN_09`
|
||||
/// looks exactly like an id; the error names what the edition has.
|
||||
fn normalise_scenario(v: &str) -> Result<String, String> {
|
||||
let known = games_ground::edition::scenarios()?;
|
||||
let want = v.trim().to_ascii_lowercase();
|
||||
let digits: String = want.chars().filter(|c| c.is_ascii_digit()).collect();
|
||||
let by_id = |s: &games_ground::edition::ScenarioText| {
|
||||
s.id.to_ascii_lowercase() == want
|
||||
|| (!digits.is_empty()
|
||||
&& s.id
|
||||
.rsplit('_')
|
||||
.next()
|
||||
.is_some_and(|n| n.parse::<u32>().ok() == digits.parse::<u32>().ok()))
|
||||
};
|
||||
if let Some(s) = known.iter().find(|s| by_id(s)) {
|
||||
return Ok(s.id.clone());
|
||||
}
|
||||
// By title, so the four cards can be named by what is printed on them.
|
||||
if let Some(s) = known
|
||||
.iter()
|
||||
.find(|s| s.title.to_ascii_lowercase().contains(&want) && !want.is_empty())
|
||||
{
|
||||
return Ok(s.id.clone());
|
||||
}
|
||||
Err(format!(
|
||||
"unknown --scenario {v:?} (the edition has {})",
|
||||
known
|
||||
.iter()
|
||||
.map(|s| format!("{} {:?}", s.id, s.title))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
))
|
||||
}
|
||||
|
||||
/// `scn-02-4p`, or `standard-4p` for the scenario every recording plays.
|
||||
///
|
||||
/// **`SCN_01` keeps the old preset string.** Twenty-six recorded
|
||||
/// scenarios name `standard-Np`; emitting `scn-01-Np` for the same board
|
||||
/// would have made every one of them unreplayable to no purpose.
|
||||
pub fn scenario_preset(scenario: &str, players: u8) -> String {
|
||||
if scenario == "SCN_01" {
|
||||
return format!("standard-{players}p");
|
||||
}
|
||||
format!(
|
||||
"scn-{}-{players}p",
|
||||
scenario.rsplit('_').next().unwrap_or(scenario)
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_args(argv: &[String]) -> Result<Mode, String> {
|
||||
let mut config = Config::default();
|
||||
let mut source: Option<std::path::PathBuf> = None;
|
||||
|
|
@ -141,6 +196,16 @@ fn parse_args(argv: &[String]) -> Result<Mode, String> {
|
|||
// cheap now where a retrofit would not be.
|
||||
// CB-WP-0038: ground-game names these in
|
||||
// `editions/catalog.yaml`; `--variant` takes that id.
|
||||
// CB-WP-0047: WHICH SCENARIO. `deal` has taken a scenario id
|
||||
// since it was written; the call site passed the literal
|
||||
// "SCN_01", so three of four decks were unreachable from the
|
||||
// driver — the same shape as `--mode` before F14.
|
||||
"--scenario" => {
|
||||
play_flags.push(flag.into());
|
||||
let v = value(i, argv, flag)?;
|
||||
config.scenario = normalise_scenario(&v)?;
|
||||
i += 2;
|
||||
}
|
||||
"--variant" => {
|
||||
play_flags.push(flag.into());
|
||||
config.variant = value(i, argv, flag)?.parse()?;
|
||||
|
|
@ -314,6 +379,7 @@ mod tests {
|
|||
serve: None,
|
||||
trial: None,
|
||||
mode: games_ground::ScoringMode::SharedGround,
|
||||
scenario: "SCN_01".into(),
|
||||
pace: table::Pace::Speed,
|
||||
variant: games_ground::Variant::Baseline,
|
||||
};
|
||||
|
|
@ -361,6 +427,7 @@ mod tests {
|
|||
serve: None,
|
||||
trial: None,
|
||||
mode: games_ground::ScoringMode::SharedGround,
|
||||
scenario: "SCN_01".into(),
|
||||
pace,
|
||||
variant: games_ground::Variant::Baseline,
|
||||
};
|
||||
|
|
@ -430,6 +497,7 @@ mod tests {
|
|||
serve: None,
|
||||
trial: None,
|
||||
mode: games_ground::ScoringMode::SharedGround,
|
||||
scenario: "SCN_01".into(),
|
||||
pace: table::Pace::Speed,
|
||||
variant: games_ground::Variant::Baseline,
|
||||
};
|
||||
|
|
@ -496,6 +564,7 @@ mod tests {
|
|||
serve: None,
|
||||
trial: None,
|
||||
mode: games_ground::ScoringMode::SharedGround,
|
||||
scenario: "SCN_01".into(),
|
||||
pace: table::Pace::Speed,
|
||||
variant: games_ground::Variant::Baseline,
|
||||
};
|
||||
|
|
@ -560,6 +629,7 @@ mod tests {
|
|||
serve: None,
|
||||
trial: None,
|
||||
mode: games_ground::ScoringMode::SharedGround,
|
||||
scenario: "SCN_01".into(),
|
||||
pace: table::Pace::Speed,
|
||||
variant: games_ground::Variant::Baseline,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,6 +47,14 @@ pub struct Config {
|
|||
/// patch, so two of the three shipped modes were unreachable from the
|
||||
/// only way anyone actually plays.
|
||||
pub mode: games_ground::ScoringMode,
|
||||
/// Which of the edition's four Scenarios to deal (CB-WP-0047).
|
||||
///
|
||||
/// **The same shape as `mode` and the same history.** `setup` dealt
|
||||
/// the literal `"SCN_01"`, so three of the four vendored decks — 15
|
||||
/// of the 20 Problem cards — had never reached a table through the
|
||||
/// only way anyone actually plays. Stored as the preset's scenario
|
||||
/// id so the kernel does the validating.
|
||||
pub scenario: String,
|
||||
/// How much ornamentation is performed (CB-WP-0036,
|
||||
/// [`specs/Ornamentation.md`]).
|
||||
///
|
||||
|
|
@ -103,6 +111,7 @@ impl Default for Config {
|
|||
serve: None,
|
||||
trial: None,
|
||||
mode: games_ground::ScoringMode::SharedGround,
|
||||
scenario: "SCN_01".into(),
|
||||
pace: Pace::Speed,
|
||||
variant: games_ground::Variant::Baseline,
|
||||
}
|
||||
|
|
@ -399,7 +408,7 @@ fn run_game<'a, R: BufRead + 'a, W: Write + 'a>(
|
|||
) -> Result<(Summary, crate::hotseat::EndChoice), String> {
|
||||
let setup = Setup {
|
||||
players: config.players,
|
||||
preset: format!("standard-{}p", config.players),
|
||||
preset: crate::scenario_preset(&config.scenario, config.players),
|
||||
patch: Default::default(),
|
||||
};
|
||||
let mut initial = <GroundState as cb_game_runtime::ScenarioGame>::setup(&setup, config.seed)?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue