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
118
crates/cb-render-html/src/testfix.rs
Normal file
118
crates/cb-render-html/src/testfix.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
//! A populated `GroundView`, built for the coverage gate.
|
||||
//!
|
||||
//! Mid-DARVO, mid-GROUND, scored — **not** a fresh deal. Most of the
|
||||
//! fields this gate exists to cover are empty at deal time, and a coverage
|
||||
//! test run against a state where the fields are absent is the same lie in
|
||||
//! a different costume (CB-WP-0011 T01).
|
||||
//!
|
||||
//! Built directly rather than projected from a `GroundState`. The
|
||||
//! projection's own hiding rules are already asserted by
|
||||
//! `games_ground::view` and by `cb-play`'s
|
||||
//! `the_inspector_never_widens_the_projection`; re-asserting them here
|
||||
//! would be a duplicated fact that drifts. What *this* crate must not do
|
||||
//! is invent a hand it was never given, and that is what the renderer test
|
||||
//! checks — with `hand: None` supplied deliberately.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use cb_kernel::PlayerId;
|
||||
use games_ground::view::{GroundView, OutcomeView, PlayerView, ProblemView, SelectionView};
|
||||
use games_ground::{
|
||||
Action, Coalition, DarvoStage, DarvoTarget, GroundChoice, GroundMode, Pair, Relation,
|
||||
RoundStep, ScoringMode, Selection, SolutionCard, Suit, SupportResponse,
|
||||
};
|
||||
|
||||
pub fn seats() -> (PlayerId, PlayerId, PlayerId) {
|
||||
(PlayerId(0), PlayerId(1), PlayerId(2))
|
||||
}
|
||||
|
||||
/// The fixture, seen by `viewer` (`None` for a spectator).
|
||||
pub fn view(viewer: Option<PlayerId>) -> GroundView {
|
||||
let (p1, p2, p3) = seats();
|
||||
let card = |suit| SolutionCard { suit };
|
||||
|
||||
let mut players = BTreeMap::new();
|
||||
for (id, stress, protection, darvo, ready, lifted, blame) in [
|
||||
(p1, 5u8, 2u8, DarvoStage::Reverse, true, true, vec![p3]),
|
||||
(p2, 1, 0, DarvoStage::Off, false, false, vec![]),
|
||||
(p3, 0, 0, DarvoStage::Deny, true, false, vec![]),
|
||||
] {
|
||||
let own = viewer == Some(id);
|
||||
players.insert(
|
||||
id,
|
||||
PlayerView {
|
||||
stress,
|
||||
freedom_ready: ready,
|
||||
freedom_gate_lifted: lifted,
|
||||
darvo,
|
||||
protection,
|
||||
blame_from: blame,
|
||||
hand: own.then(|| vec![card(Suit::Clarify), card(Suit::Boundary)]),
|
||||
hand_size: 2,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let mut problems = BTreeMap::new();
|
||||
problems.insert(1, ProblemView::FaceDown);
|
||||
problems.insert(
|
||||
7,
|
||||
ProblemView::FaceUp {
|
||||
suit: Suit::Change,
|
||||
value: 6,
|
||||
denied: true,
|
||||
claimed_by: Some(p2),
|
||||
protected_this_round: true,
|
||||
},
|
||||
);
|
||||
|
||||
GroundView {
|
||||
viewer,
|
||||
round: 3,
|
||||
lead: p2,
|
||||
step: RoundStep::Select,
|
||||
mode: ScoringMode::BondedCoalitions,
|
||||
players,
|
||||
relations: BTreeMap::from([
|
||||
(Pair::new(p1, p2), Relation::Rivalry),
|
||||
(Pair::new(p2, p3), Relation::Bond),
|
||||
]),
|
||||
problems,
|
||||
focus: BTreeMap::from([(p1, p3)]),
|
||||
selections: BTreeMap::from([
|
||||
(
|
||||
p1,
|
||||
SelectionView::Shown(Selection {
|
||||
action: Action::Attack,
|
||||
target: Some(p2),
|
||||
problem: Some(7),
|
||||
}),
|
||||
),
|
||||
(p2, SelectionView::Hidden),
|
||||
]),
|
||||
ground_modes: BTreeMap::from([(p3, GroundMode::Gr)]),
|
||||
ground_choices: BTreeMap::from([(p3, GroundChoice::ProtectProblem { problem: 7 })]),
|
||||
support_responses: BTreeMap::from([(p2, SupportResponse::AcceptBond)]),
|
||||
darvo_targets: BTreeMap::from([(
|
||||
p3,
|
||||
DarvoTarget {
|
||||
problem: Some(1),
|
||||
player: Some(p2),
|
||||
},
|
||||
)]),
|
||||
solution_deck_len: 17,
|
||||
solution_discard: vec![card(Suit::Repair), card(Suit::Change)],
|
||||
outcome: Some(OutcomeView {
|
||||
total: 9,
|
||||
threshold: 12,
|
||||
group_success: false,
|
||||
personal: BTreeMap::from([(p1, 3), (p2, -1), (p3, 0)]),
|
||||
coalitions: vec![Coalition {
|
||||
members: vec![p2, p3],
|
||||
score: 4,
|
||||
}],
|
||||
mastery: Some(1),
|
||||
winners: vec![p1],
|
||||
}),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue