CB-WP-0020: the table you can read
Some checks failed
ci / check (push) Failing after 3s

Six of seven perceptual defects fixed; item 1 already passed.

T01, at the maintainer's instruction: a legal target restyles its
EXISTING border rather than drawing a new box. outline + outline-offset
drew a second rectangle, which an SVG viewport clips (the missing top and
left edges) and which made a seat's highlight card-sized. A border
already in the layout cannot move the layout.

T02: the ghost was a textContent copy of the card, which is why the line
break collapsed and it read as a second card, and why showing the
explanation destroyed the label. It is now a pill, the explanation is
appended beside the label, and the left-behind element is dimmed and
dashed. The stub grew innerHTML so a test can assert BOTH are present --
it could previously only see that something was displayed.

T03: NOT reproduced and recorded as not reproduced. The likeliest cause
is which element the browser reports -- for touch and pen the pointer is
captured to the pointerdown target, making every drop look like a
drop-on-itself, which is the other half of the report. elementFromPoint
is correct under both explanations. Separately the refusal was written in
element ids on the one surface a player reads when something goes wrong;
it now speaks the game's words and a test forbids id leakage.

T04: seat selections rendered as Debug. The coverage gate then failed my
first fix for dropping a field when target and problem were both set --
the aggregate does not produce that shape and the gate was right not to
care.

T05: the headline reads from group_success. 'Play again' is real, and its
first version was useless: run_game bound a fresh listener per game, so a
second game moved to a new port and left the tab pointing at a dead one.
One listener per session now, and the test asserts the second game is a
DIFFERENT deal.

Chaos d8=8 fired the first override at the new rate and drew S, changing
nothing -- one half of window 2's retirement condition.

CB-WP-0019 settled at $38.54/117 against $34.80/107. Eight for eight,
and the first under 20%.

make all exits 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-03 20:20:38 +02:00
parent 5e06a7d01e
commit bf24affa84
8 changed files with 584 additions and 55 deletions

View file

@ -130,6 +130,49 @@ pub fn describe(command: &GroundCommand, seat: cb_kernel::PlayerId) -> String {
}
}
/// Say why a drop meant nothing, in words a player can act on.
///
/// Deliberately does **not** consult the rules to explain *why* a move is
/// illegal — that would be a second implementation of them. It names what
/// was dropped on what, and leaves the reason to the log.
fn refusal(fact: &PointerFact, seat: cb_kernel::PlayerId) -> String {
let name = |id: &str| -> String {
if let Some(a) = id.strip_prefix("action-") {
let mut c = a.chars();
return match c.next() {
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
None => id.to_string(),
};
}
if let Some(n) = id.strip_prefix("seat-") {
return n
.parse::<u8>()
.map(|n| format!("P{}", n + 1))
.unwrap_or_else(|_| id.to_string());
}
if let Some(n) = id.strip_prefix("problem-") {
return format!("problem {n}");
}
match id {
"table" => "the table".to_string(),
_ if id.starts_with("freedom-") => "your freedom token".to_string(),
_ => id.to_string(),
}
};
if fact.down == fact.up {
return format!("{} needs to be dropped on something", name(&fact.down));
}
let you = format!("P{}", seat.0 + 1);
if fact.up == format!("seat-{}", seat.0) {
return format!("{} cannot be aimed at {you} right now", name(&fact.down));
}
format!(
"{} on {} is not a move you can make right now",
name(&fact.down),
name(&fact.up)
)
}
/// Resolve a pointer fact to an index into the legal list.
///
/// Returns `Err` rather than a default when nothing matches: a drag that
@ -169,10 +212,12 @@ pub fn resolve(
"{} -> {} is offered by more than one legal command; the page is ambiguous",
fact.down, fact.up
)),
(None, _) => Err(format!(
"{} -> {} is not a legal move here",
fact.down, fact.up
)),
// CB-WP-0020 T03: written in the game's words, not the DOM's.
// This read `action-attack -> action-attack is not a legal move
// here`, which is the vocabulary of element ids — the same defect
// the log had before CB-WP-0018, on the surface a player actually
// reads when something goes wrong.
(None, _) => Err(refusal(fact, seat)),
}
}