CB-WP-0012-T04: cb-render-html — stage 1 draws, and the browser is the toolkit
Delivers ADR-0007 Decision 1: visualization, drag-to-propose and hot-seat
play, at a measured marginal AM-4a cost of zero.
games-ground shipped: 23 third-party crates
cb-render-html: 23 third-party crates
new crates introduced: 0
Measured, not asserted — the survey's own lesson. AM-4a is unmoved at
246,250; own source is 7,636 -> 9,652.
What shipped:
crates/cb-render-html doc.rs (HTML/SVG emission, incl. the relationship
graph), input.rs (pointer facts -> commands),
serve.rs (Guard, Request, loopback bind)
tools/cb-play hotseat.rs + `--serve PORT`
Per ADR-0007 Decision 2 there is NO cb-render-api and NO cb-render-null.
The renderer targets the existing Project trait; the port waits for
stage 2's wgpu implementation to be its second use.
The six controls, all live, all mutation-checked (8 mutations, each red
for its stated reason):
1-3 token / Origin+Sec-Fetch-Site / explicit 127.0.0.1 bind
4 a token-less request is refused, in the unit AND over a real socket
5 JS may not construct commands — the page reports pointer facts, Rust
resolves them against the legal list the aggregate already offered,
and a test asserts the emitted script contains no game vocabulary
6 the coverage gate crosses the language boundary: it walks the
serialized view for leaf paths and requires each token to appear in
the PARSED emitted document, with a test that the parse really is a
parse (script/style contents must not count as rendered)
The gate fired on its author again, on its first run: ground_choices.*.
choice, ground_choices.*.problem and players.*.blame_from were in neither
list. The last is the one worth keeping — an EMPTY vector is a leaf path
of its own, and it now renders as an explicit absence.
Also, a mutation that did not go red: removing the Sec-Fetch-Site arm
alone left the cross-site test green, because the Origin check caught it
independently. Both had to be removed before the control bit. Recorded
because a control that passes for a reason you did not intend has not
been demonstrated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
883b608f36
commit
84d688688d
14 changed files with 2058 additions and 2 deletions
|
|
@ -9,6 +9,7 @@
|
|||
//! success — a session that ends without an outcome is a failure, and
|
||||
//! saying otherwise is how a stalled game passes for a played one.
|
||||
|
||||
mod hotseat;
|
||||
mod inspect;
|
||||
mod table;
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ use table::Config;
|
|||
|
||||
const USAGE: &str = "\
|
||||
usage: cb-play [--seed N] [--players N] [--seat N]... [--bot greedy|random]
|
||||
[--replay DIR] [--all-bots] [--record FILE]
|
||||
[--replay DIR] [--all-bots] [--record FILE] [--serve PORT]
|
||||
cb-play --inspect PATH [--as SEAT|spectator]
|
||||
|
||||
play:
|
||||
|
|
@ -27,6 +28,10 @@ play:
|
|||
--bot KIND policy for every other seat: greedy (default) or random
|
||||
--replay DIR write a .cbreplay bundle of the finished game to DIR
|
||||
--record FILE write the finished game as a scenario YAML
|
||||
--serve PORT play human seats in a browser on 127.0.0.1:PORT instead
|
||||
of on the terminal; 0 lets the OS pick. Prints a URL
|
||||
carrying a per-process token — without it the page is
|
||||
refused (ADR-0007).
|
||||
|
||||
inspect a recorded game — a .cbreplay bundle directory or a scenario YAML:
|
||||
--inspect PATH render the table after every recorded step
|
||||
|
|
@ -104,6 +109,15 @@ fn parse_args(argv: &[String]) -> Result<Mode, String> {
|
|||
config.replay_dir = Some(value(i, argv, flag)?.into());
|
||||
i += 2;
|
||||
}
|
||||
"--serve" => {
|
||||
play_flags.push(flag.into());
|
||||
config.serve = Some(
|
||||
value(i, argv, flag)?
|
||||
.parse()
|
||||
.map_err(|e| format!("--serve: {e}"))?,
|
||||
);
|
||||
i += 2;
|
||||
}
|
||||
"--all-bots" => {
|
||||
all_bots = true;
|
||||
play_flags.push(flag.into());
|
||||
|
|
@ -245,6 +259,7 @@ mod tests {
|
|||
bot: "greedy".into(),
|
||||
replay_dir: None,
|
||||
record: None,
|
||||
serve: None,
|
||||
};
|
||||
let script = "0\n".repeat(400);
|
||||
let mut out: Vec<u8> = Vec::new();
|
||||
|
|
@ -283,6 +298,7 @@ mod tests {
|
|||
bot: "greedy".into(),
|
||||
replay_dir: None,
|
||||
record: None,
|
||||
serve: None,
|
||||
};
|
||||
let mut out: Vec<u8> = Vec::new();
|
||||
table::play(&config, "0\n".repeat(200).as_bytes(), &mut out).expect("game");
|
||||
|
|
@ -344,6 +360,7 @@ mod tests {
|
|||
bot: "random".into(),
|
||||
replay_dir: None,
|
||||
record: None,
|
||||
serve: None,
|
||||
};
|
||||
let mut out: Vec<u8> = Vec::new();
|
||||
let summary = table::play(&config, "".as_bytes(), &mut out).expect("bot game");
|
||||
|
|
@ -403,6 +420,7 @@ mod tests {
|
|||
bot: "greedy".into(),
|
||||
replay_dir: Some(dir.clone()),
|
||||
record: Some(dir.join("session.yaml")),
|
||||
serve: None,
|
||||
};
|
||||
let mut out: Vec<u8> = Vec::new();
|
||||
let summary = table::play(&config, "".as_bytes(), &mut out).expect("game");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue