Reported three times across three sessions, two days apart, and it
survived a whole UI rebuild: "i cant see whos support i accept".
It was not styling. The move button's label was format!("{c:?}"), so the
player read `RespondToSupport { response: AcceptBond }` — Rust struct
syntax with no name in it. And the command does not carry the
counterparty, so nothing rendering it alone could have said who; it comes
off the view, as whoever played Support at this seat.
Four more seat-panel fields had the same defect, including `support
AcceptBond` — the one the report names. CB-WP-0020 fixed exactly this for
selections and left its four neighbours as they were.
command_label has no catch-all arm, and that earned its keep before any
test ran: GroundChoice::RejectReverse and SupportResponse::BreakRivalry
both failed to compile — two moves that would have shipped as struct
dumps. An offer the view cannot see is said to be unseen rather than given
an invented name.
The finding underneath: the coverage probe that exists to prove every view
field reaches the PLAYER was matching "player: Some(PlayerId(1))" and
"members: [PlayerId(1)". It was certifying the defect as coverage and
would have gone red had anyone fixed it. Second confirmation of
CB-WP-0024's finding, from the sharper side: a probe naming Debug output
does not merely fail to protect, it pins the defect in place.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
edab11c0e4
commit
c2a7e40b67
6 changed files with 495 additions and 16 deletions
|
|
@ -163,18 +163,28 @@ mod coverage {
|
|||
("selections.*.action", "selected Attack"),
|
||||
("selections.*.target", "Attack on P2"),
|
||||
("selections.*.problem", "for problem 7"),
|
||||
("ground_modes.*", "ground mode Gr"),
|
||||
("ground_choices.*.choice", "ProtectProblem"),
|
||||
("ground_choices.*.problem", "problem: 7 }"),
|
||||
("support_responses.*", "support AcceptBond"),
|
||||
("darvo_targets.*.problem", "problem: Some(1)"),
|
||||
("darvo_targets.*.player", "player: Some(PlayerId(1))"),
|
||||
// CB-WP-0034: words, not Debug — and these probes are the reason
|
||||
// it survived so long. They matched `problem: 7 }`,
|
||||
// `player: Some(PlayerId(1))` and `members: [PlayerId(1)`, so the
|
||||
// gate that exists to prove every field reaches the PLAYER was
|
||||
// proving it by matching Rust struct syntax. It certified the
|
||||
// defect as coverage.
|
||||
//
|
||||
// Second confirmation of CB-WP-0024's finding, from the other
|
||||
// side: a probe naming a PRESENTATION does not survive a
|
||||
// rendering change, and one naming Debug output actively pins it.
|
||||
("ground_modes.*", "Ground & Restate"),
|
||||
("ground_choices.*.choice", "protect Problem"),
|
||||
("ground_choices.*.problem", "Problem 7 from Deny"),
|
||||
("support_responses.*", "accepted P3\u{2019}s Support"),
|
||||
("darvo_targets.*.problem", "Problem 1"),
|
||||
("darvo_targets.*.player", "darvo target P2"),
|
||||
("outcome.total", "total 9"),
|
||||
("outcome.threshold", "of 12"),
|
||||
("outcome.group_success", "failure"),
|
||||
("outcome.personal.*", "P1 +3"),
|
||||
("outcome.coalitions.*.members.*", "members: [PlayerId(1)"),
|
||||
("outcome.coalitions.*.score", "score: 4"),
|
||||
("outcome.coalitions.*.members.*", "P2 + P3"),
|
||||
("outcome.coalitions.*.score", "(score 4)"),
|
||||
("outcome.mastery", "mastery +1"),
|
||||
("outcome.winners.*", "winners P1"),
|
||||
];
|
||||
|
|
@ -598,6 +608,151 @@ mod gamelog {
|
|||
}
|
||||
}
|
||||
|
||||
/// CB-WP-0034. The move button must name **who**.
|
||||
///
|
||||
/// Reported three times across three sessions, two days apart, and it
|
||||
/// survived a whole UI rebuild: *"RespondToSupport is unclear as i
|
||||
/// cant see whos support i accept"*, then twice more about bonding.
|
||||
///
|
||||
/// The cause was not styling. The label was `format!("{c:?}")`, so
|
||||
/// the button read `RespondToSupport { response: AcceptBond }` — Rust
|
||||
/// struct syntax with no name in it — and the command **does not
|
||||
/// carry** the counterparty, so nothing rendering the command alone
|
||||
/// could have said who. It comes off the view.
|
||||
#[test]
|
||||
fn a_support_response_names_the_seat_that_offered_it() {
|
||||
use games_ground::view::SelectionView;
|
||||
use games_ground::{Action, GroundCommand, Selection, SupportResponse};
|
||||
let me = PlayerId(0);
|
||||
let them = PlayerId(1);
|
||||
let mut v = crate::testfix::view(Some(me));
|
||||
// P2 played Support at P1, revealed.
|
||||
v.selections.insert(
|
||||
them,
|
||||
SelectionView::Shown(Selection {
|
||||
action: Action::Support,
|
||||
target: Some(me),
|
||||
problem: None,
|
||||
}),
|
||||
);
|
||||
let legal = vec![
|
||||
GroundCommand::RespondToSupport {
|
||||
response: SupportResponse::AcceptBond,
|
||||
},
|
||||
GroundCommand::RespondToSupport {
|
||||
response: SupportResponse::DeclineBond,
|
||||
},
|
||||
];
|
||||
let html = document_with_log(
|
||||
&v,
|
||||
&legal,
|
||||
crate::TEST_ENDPOINTS,
|
||||
Some(me),
|
||||
false,
|
||||
Account::of(&[]),
|
||||
&[],
|
||||
);
|
||||
let text = crate::text_of(&html);
|
||||
assert!(text.contains("accept P2"), "the offer is anonymous: {text}");
|
||||
assert!(
|
||||
text.contains("decline P2"),
|
||||
"the refusal is anonymous: {text}"
|
||||
);
|
||||
// The defect itself, named: no Rust struct syntax on a button.
|
||||
assert!(
|
||||
!text.contains("RespondToSupport"),
|
||||
"the raw command name is still on the page: {text}"
|
||||
);
|
||||
assert!(
|
||||
!text.contains("AcceptBond"),
|
||||
"the raw variant name is still on the page: {text}"
|
||||
);
|
||||
}
|
||||
|
||||
/// When the offer is not visible in this view, the page says so.
|
||||
///
|
||||
/// **It must not invent a name and must not drop the question.** A
|
||||
/// confident wrong seat is worse than an honest gap — that is the
|
||||
/// family ADR-0018 exists for.
|
||||
#[test]
|
||||
fn an_unseen_offer_is_not_given_an_invented_name() {
|
||||
use games_ground::{GroundCommand, SupportResponse};
|
||||
let me = PlayerId(0);
|
||||
let mut v = crate::testfix::view(Some(me));
|
||||
v.selections.clear();
|
||||
let legal = vec![GroundCommand::RespondToSupport {
|
||||
response: SupportResponse::AcceptBond,
|
||||
}];
|
||||
let text = crate::text_of(&document_with_log(
|
||||
&v,
|
||||
&legal,
|
||||
crate::TEST_ENDPOINTS,
|
||||
Some(me),
|
||||
false,
|
||||
Account::of(&[]),
|
||||
&[],
|
||||
));
|
||||
assert!(
|
||||
text.contains("this view does not show which seat"),
|
||||
"it should say the offer is not visible: {text}"
|
||||
);
|
||||
for name in ["P1", "P2", "P3"] {
|
||||
assert!(
|
||||
!text.contains(&format!("accept {name}")),
|
||||
"it named {name} with nothing to go on: {text}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// No move button may carry Rust `Debug` output (CB-WP-0034).
|
||||
///
|
||||
/// The guard against this returning is that `command_label` has no
|
||||
/// catch-all arm, so a new variant stops the build. This asserts the
|
||||
/// visible half over every command the fixture can offer.
|
||||
#[test]
|
||||
fn no_move_button_shows_rust_struct_syntax() {
|
||||
use games_ground::*;
|
||||
let me = PlayerId(0);
|
||||
let legal = vec![
|
||||
GroundCommand::SpendFreedom,
|
||||
GroundCommand::ChooseGroundMode {
|
||||
mode: GroundMode::Ou,
|
||||
choice: Some(GroundChoice::ProtectProblem { problem: 7 }),
|
||||
},
|
||||
GroundCommand::ChooseDarvoTarget {
|
||||
target: DarvoTarget {
|
||||
problem: Some(1),
|
||||
player: Some(PlayerId(1)),
|
||||
},
|
||||
},
|
||||
GroundCommand::RespondToSupport {
|
||||
response: SupportResponse::FlipToBond,
|
||||
},
|
||||
];
|
||||
let text = crate::text_of(&document_with_log(
|
||||
&crate::testfix::view(Some(me)),
|
||||
&legal,
|
||||
crate::TEST_ENDPOINTS,
|
||||
Some(me),
|
||||
false,
|
||||
Account::of(&[]),
|
||||
&[],
|
||||
));
|
||||
// The tells of a `{:?}` label reaching a player.
|
||||
for tell in ["{ ", " }", "Some(", "None", "PlayerId("] {
|
||||
assert!(
|
||||
!text.contains(tell),
|
||||
"Debug output {tell:?} reached the page: {text}"
|
||||
);
|
||||
}
|
||||
// And the words are actually there. `SpendFreedom` is not among
|
||||
// them: it carries a spatial affordance, so it is a drag target
|
||||
// rather than a button -- which is why this asserts on the
|
||||
// commands that DO become buttons.
|
||||
assert!(text.contains("Observe & Uphold"), "{text}");
|
||||
assert!(text.contains("DARVO target: P2, Problem 1"), "{text}");
|
||||
}
|
||||
|
||||
fn note(after: usize, text: &str) -> crate::doc::LogNote {
|
||||
crate::doc::LogNote {
|
||||
after,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue