fix: the game was unplayable, and the note redirect was refused
Some checks failed
ci / check (push) Failing after 3s
Some checks failed
ci / check (push) Failing after 3s
Tier S (fixes inside a boundary; chaos d8=7, no override). Four observations from play, three of them caused by CB-WP-0028 -- and make all was green for all of them. THE NOTE BUG, WHICH I GOT WRONG TWICE. The first fix put the token in the form's action, and that worked. But the 303 afterwards pointed at bare `/` with no token, so the note WAS SAVED and then the browser followed a redirect control 1 refuses. The player sees "no session token" for a note that already landed. A redirect is a request the browser makes on your behalf and is subject to every control the others are. I had tested the POST and stopped there -- the same mistake as the first fix, one step further along. Guard::page_path() now carries the token, and the test asserts the redirect target is ADMITTED rather than merely non-empty. WHY DRAGGING BROKE, WHICH WAS NOT THE DRAG. The gesture logic was fine: the JS harness posts correctly against the served page, and all 14 drop targets are present. The table was 620px tall, which pushed the action cards a full screen below the Problems -- and you cannot drag between two things that are never on screen together. Now 440, with a test asserting the declared height stays under 460 and saying why. That is a proxy for a browser layout, not the property itself, and the test says so. Seats sat ON the ellipse: they were placed at 0.83 of the table radius, which is inside it. Now outside, asserted numerically at 2 through 6 seats against the ellipse equation rather than eyeballed. And the `table` drop target was a separate CARD among the move buttons, which is exactly why a player looking at a picture of a table could not find anywhere to drop. The drawn ellipse is the drop zone now, and a test asserts there is EXACTLY ONE table target and that it is the drawn one -- two elements claiming to be the table is worse than none. The gap this exposes is the one CB-EV-0026 named a day earlier: every test asserted the DOM was correct, and it was. Nothing asserted the page was usable, and the drag test passes on a page you cannot physically drag on. What is added here are proxies a browser-less test can check. make all: exit 0. 61 render tests, 26 cb-play. Verified over real HTTP rather than by inspection: note POST 303, the redirect carries the token, following it returns 200, the table declares 440, one drop zone, seats at (380,421)/(113,112)/(647,112) against a table of rx=200 ry=118, and both notes reached the trial log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ab364a0319
commit
8eadb6c963
4 changed files with 175 additions and 46 deletions
|
|
@ -721,6 +721,18 @@ mod overhead_table {
|
|||
use cb_kernel::PlayerId;
|
||||
use games_ground::GroundState;
|
||||
|
||||
/// Seat circle centres, read out of the emitted SVG.
|
||||
fn seat_centres(html: &str) -> Vec<(f64, f64)> {
|
||||
html.match_indices("<circle cx=\"")
|
||||
.filter_map(|(i, _)| {
|
||||
let rest = &html[i + 12..];
|
||||
let (x, rest) = rest.split_once("\" cy=\"")?;
|
||||
let (y, _) = rest.split_once('"')?;
|
||||
Some((x.parse().ok()?, y.parse().ok()?))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn view_of(players: u8) -> games_ground::view::GroundView {
|
||||
GroundState::setup(
|
||||
&Setup {
|
||||
|
|
@ -743,15 +755,7 @@ mod overhead_table {
|
|||
let v = view_of(players);
|
||||
let html = crate::doc::document(&v, &[], "/command?t=x", Some(PlayerId(0)), false);
|
||||
|
||||
let seats: Vec<(f64, f64)> = html
|
||||
.match_indices("<circle cx=\"")
|
||||
.filter_map(|(i, _)| {
|
||||
let rest = &html[i + 12..];
|
||||
let (x, rest) = rest.split_once("\" cy=\"")?;
|
||||
let (y, _) = rest.split_once('"')?;
|
||||
Some((x.parse().ok()?, y.parse().ok()?))
|
||||
})
|
||||
.collect();
|
||||
let seats = seat_centres(&html);
|
||||
assert_eq!(
|
||||
seats.len(),
|
||||
players as usize,
|
||||
|
|
@ -761,7 +765,7 @@ mod overhead_table {
|
|||
for b in &seats[i + 1..] {
|
||||
let d = ((a.0 - b.0).powi(2) + (a.1 - b.1).powi(2)).sqrt();
|
||||
assert!(
|
||||
d > 70.0,
|
||||
d > 64.0,
|
||||
"{players}p: two seats are {d:.0}px apart and the circles are r=34 — \
|
||||
they overlap"
|
||||
);
|
||||
|
|
@ -800,6 +804,77 @@ mod overhead_table {
|
|||
);
|
||||
}
|
||||
|
||||
/// **Observation 1: the seats were ON the table, not around it.**
|
||||
/// Every seat circle must lie outside the ellipse.
|
||||
#[test]
|
||||
fn the_seats_sit_outside_the_table_not_on_it() {
|
||||
for players in 2..=6u8 {
|
||||
let v = view_of(players);
|
||||
let html = crate::doc::document(&v, &[], "/command?t=x", Some(PlayerId(0)), false);
|
||||
let (cx, cy, rx, ry) = (380.0f64, 215.0f64, 200.0f64, 118.0f64);
|
||||
for (x, y) in seat_centres(&html) {
|
||||
// Outside an ellipse: (dx/rx)^2 + (dy/ry)^2 > 1, with the
|
||||
// seat's own radius kept clear of the rim.
|
||||
let d = ((x - cx) / (rx + 30.0)).powi(2) + ((y - cy) / (ry + 30.0)).powi(2);
|
||||
assert!(
|
||||
d > 1.0,
|
||||
"{players}p: a seat at ({x:.0},{y:.0}) is on or inside the table"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// **Observations 3 and 4: the table you can see is the table you drop
|
||||
/// on, and the game must be playable.** A `table` drop target that is
|
||||
/// not the drawn table is why a player could not find where to drop.
|
||||
#[test]
|
||||
fn the_drawn_table_is_the_drop_target_and_there_is_only_one() {
|
||||
let v = view_of(3);
|
||||
let html = crate::doc::document(&v, &[], "/command?t=x", Some(PlayerId(0)), false);
|
||||
assert_eq!(
|
||||
html.matches("data-drop=\"table\"").count(),
|
||||
1,
|
||||
"two elements claim to be the table; a player cannot tell which to use"
|
||||
);
|
||||
let table = html
|
||||
.split("aria-label=\"the table, seen from above\"")
|
||||
.nth(1)
|
||||
.and_then(|s| s.split("</svg>").next())
|
||||
.expect("one table svg");
|
||||
assert!(
|
||||
table.contains("data-drop=\"table\""),
|
||||
"the drop target is not on the drawn table"
|
||||
);
|
||||
assert!(
|
||||
table.contains("<ellipse"),
|
||||
"the drop target should be the surface itself"
|
||||
);
|
||||
}
|
||||
|
||||
/// **The reason the game became unplayable.** The table was 620px
|
||||
/// tall, so the action cards sat a screen below the Problems — and you
|
||||
/// cannot drag between two things never on screen together.
|
||||
///
|
||||
/// Asserted on the declared height, which is the only thing a test
|
||||
/// without a browser can see, and that limit is said out loud rather
|
||||
/// than implied.
|
||||
#[test]
|
||||
fn the_table_is_short_enough_to_drag_from() {
|
||||
let v = view_of(6);
|
||||
let html = crate::doc::document(&v, &[], "/command?t=x", Some(PlayerId(0)), false);
|
||||
let vb = html
|
||||
.split("viewBox=\"0 0 760 ")
|
||||
.nth(1)
|
||||
.and_then(|s| s.split('"').next())
|
||||
.expect("the table declares a viewBox");
|
||||
let h: f64 = vb.parse().expect("a number");
|
||||
assert!(
|
||||
h <= 460.0,
|
||||
"the table is {h}px tall; the action cards end up off-screen and \
|
||||
dragging to a Problem becomes impossible"
|
||||
);
|
||||
}
|
||||
|
||||
/// The three diagrams became one: the relationship circle and the
|
||||
/// piles picture are gone as separate views, and their content is on
|
||||
/// the table.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue