CB-WP-0028 T03/T08: one overhead table, and what the nine observations
Some checks failed
ci / check (push) Has been cancelled
Some checks failed
ci / check (push) Has been cancelled
turned out to be T03. One table_svg: seats around an elliptical table starting at the BOTTOM -- the viewer sits nearest the reader, as at a real table -- Problems and both stacks in the middle, each seat's played card between it and the centre, relations drawn between seats. Two renderers were DELETED: relations_svg and piles_svg. The task said one table not two diagrams, and leaving the old ones would have meant drawing the same thing twice and letting them drift. No coverage probe cost, through a restructure that merged three diagrams and removed two functions. Second confirmation of CB-WP-0027's finding: a probe naming a FACT survives a reflow, one naming a PRESENTATION does not. CB-WP-0024's "17 remaining" broke on a rendering change; this far larger reflow broke nothing. The new control is per seat count -- no two seat circles closer than 70px at 2 through 6 -- asserted rather than eyeballed at three, which is the only count anyone ever looks at. T08 (CB-EV-0026). Seven of nine observations were engine defects, one was a design finding, one was already true and nobody could tell. Observations 4 and 5 both dissolved and had ONE cause: nothing on the page said how drawing works, so a player built a mental model to fill the gap and reported the gap as two feature requests. The import gap was worse than "one of nineteen" -- 5 of 13 columns read from the file we DID vendor, discarded at parse time for eight days. Rule coverage was 59/59 throughout. The gate measures whether rules are EXERCISED; nothing measures whether a player can READ the game, and nothing cheaply could, which is why the person playing it is the instrument. TWO GATES WERE WRITTEN FOR A SMALLER WORLD, and neither was wrong when written. edition-check compared one recorded digest against Problems.csv regardless of which file it described -- correct with one vendored file, comparing across files with four. And a cb-play test asserted the literal "game over" and went red when a won game said "solved", which was T06 working; it now asserts the heading against the OUTCOME and covers the no-outcome case the original never touched. Chaos window 2 closes with zero overrides in eleven declarations at d8. Third and final statement of it: d8 bought rarity by spending evidence, and a mechanism producing no data across a full window cannot be evaluated by that window. make all: exit 0. 57 render tests, 26 cb-play, loop-lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4d3e30eda4
commit
eb8aa64c56
5 changed files with 459 additions and 122 deletions
|
|
@ -421,24 +421,10 @@ fn pile_svg(out: &mut String, x: i32, label: &str, count: usize, face: &str, not
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The draw stack and the discard stack, as objects on the table.
|
fn piles_body(out: &mut String, view: &GroundView) {
|
||||||
///
|
|
||||||
/// Both numbers come from the projection — `solution_deck_len` and
|
|
||||||
/// `solution_discard` — and are never recomputed here.
|
|
||||||
///
|
|
||||||
/// **The reshuffle is real and is the U4 default**, confirmed by
|
|
||||||
/// ground-game on 2026-08-03: when the deck runs out, the discard is
|
|
||||||
/// reshuffled into it deterministically; if both are empty the draw is
|
|
||||||
/// skipped (`games/ground/src/lib.rs` `draw_solution`). So the pile shows
|
|
||||||
/// the state in which the *next* draw will trigger it. It does not claim a
|
|
||||||
/// reshuffle has happened — the view carries no such flag, and the event
|
|
||||||
/// already reads out in the log.
|
|
||||||
fn piles_svg(out: &mut String, view: &GroundView) {
|
|
||||||
let deck = view.solution_deck_len;
|
let deck = view.solution_deck_len;
|
||||||
let discard = view.solution_discard.len();
|
let discard = view.solution_discard.len();
|
||||||
let will_reshuffle = deck == 0 && discard > 0;
|
let will_reshuffle = deck == 0 && discard > 0;
|
||||||
|
|
||||||
out.push_str("<svg viewBox=\"0 0 300 110\" width=\"300\" height=\"110\" role=\"img\">");
|
|
||||||
pile_svg(
|
pile_svg(
|
||||||
out,
|
out,
|
||||||
10,
|
10,
|
||||||
|
|
@ -465,27 +451,34 @@ fn piles_svg(out: &mut String, view: &GroundView) {
|
||||||
<path d=\"M118 40 l4 6 -7 1z\" fill=\"#fc9\"/>",
|
<path d=\"M118 40 l4 6 -7 1z\" fill=\"#fc9\"/>",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
out.push_str("</svg>");
|
|
||||||
|
|
||||||
// The discard is public — it has been played (GR-S04 hides only the
|
|
||||||
// deck) — so its contents stay readable as text beside the picture.
|
|
||||||
let _ = write!(
|
|
||||||
out,
|
|
||||||
"<div><span class=\"k\">discard</span> {}</div>",
|
|
||||||
esc(&cards(&view.solution_discard)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn relations_svg(view: &GroundView) -> String {
|
/// The table, seen from above (CB-WP-0028 T03).
|
||||||
|
///
|
||||||
|
/// **One diagram, not three.** The seats were already placed on a circle
|
||||||
|
/// for the relationship graph, while the Problems were a row above it and
|
||||||
|
/// the piles a separate picture below — three views of one table, and the
|
||||||
|
/// player had to assemble them. Now: seats around the edge, the Problems
|
||||||
|
/// and the two stacks in the middle, each seat's played card in front of
|
||||||
|
/// it, relations drawn between seats.
|
||||||
|
///
|
||||||
|
/// The existing `problem_svg` and `pile_svg` are reused rather than
|
||||||
|
/// reimplemented — they carry the tokens the coverage gate probes for,
|
||||||
|
/// and drawing the same thing twice is how the two drift.
|
||||||
|
fn table_svg(view: &GroundView) -> String {
|
||||||
let n = view.players.len().max(1);
|
let n = view.players.len().max(1);
|
||||||
let (cx, cy, r) = (200.0f64, 150.0f64, 110.0f64);
|
let (cx, cy) = (380.0f64, 300.0f64);
|
||||||
|
let seat_r = 240.0f64;
|
||||||
|
|
||||||
let pos: Vec<(PlayerId, f64, f64)> = view
|
let pos: Vec<(PlayerId, f64, f64)> = view
|
||||||
.players
|
.players
|
||||||
.keys()
|
.keys()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, p)| {
|
.map(|(i, p)| {
|
||||||
let a = std::f64::consts::TAU * (i as f64) / (n as f64) - std::f64::consts::FRAC_PI_2;
|
// Start at the bottom, not the top: the viewer sits nearest
|
||||||
(*p, cx + r * a.cos(), cy + r * a.sin())
|
// the reader, which is where you sit at a real table.
|
||||||
|
let a = std::f64::consts::TAU * (i as f64) / (n as f64) + std::f64::consts::FRAC_PI_2;
|
||||||
|
(*p, cx + seat_r * a.cos(), cy + seat_r * a.sin() * 0.72)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let find = |p: PlayerId| {
|
let find = |p: PlayerId| {
|
||||||
|
|
@ -495,8 +488,12 @@ fn relations_svg(view: &GroundView) -> String {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut s = String::from(
|
let mut s = String::from(
|
||||||
"<svg width=\"400\" height=\"300\" role=\"img\" aria-label=\"relationship graph\">",
|
"<svg viewBox=\"0 0 760 620\" width=\"100%\" style=\"max-width:760px\" \
|
||||||
|
role=\"img\" aria-label=\"the table, seen from above\">\
|
||||||
|
<ellipse cx=\"380\" cy=\"300\" rx=\"290\" ry=\"215\" fill=\"#171b23\" stroke=\"#2a3140\"/>",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Relations first, so seats draw over them.
|
||||||
for (pair, rel) in &view.relations {
|
for (pair, rel) in &view.relations {
|
||||||
if let (Some((x1, y1)), Some((x2, y2))) = (find(pair.0), find(pair.1)) {
|
if let (Some((x1, y1)), Some((x2, y2))) = (find(pair.0), find(pair.1)) {
|
||||||
let colour = match rel {
|
let colour = match rel {
|
||||||
|
|
@ -507,32 +504,70 @@ fn relations_svg(view: &GroundView) -> String {
|
||||||
s,
|
s,
|
||||||
"<line x1=\"{x1:.0}\" y1=\"{y1:.0}\" x2=\"{x2:.0}\" y2=\"{y2:.0}\" \
|
"<line x1=\"{x1:.0}\" y1=\"{y1:.0}\" x2=\"{x2:.0}\" y2=\"{y2:.0}\" \
|
||||||
stroke=\"{colour}\" stroke-width=\"2\"/>\
|
stroke=\"{colour}\" stroke-width=\"2\"/>\
|
||||||
<text x=\"{mx:.0}\" y=\"{my:.0}\" fill=\"{colour}\" font-size=\"10\">{rel:?}</text>",
|
<text x=\"{mx:.0}\" y=\"{my:.0}\" fill=\"{colour}\" font-size=\"10\" \
|
||||||
|
text-anchor=\"middle\">{rel:?}</text>",
|
||||||
mx = (x1 + x2) / 2.0,
|
mx = (x1 + x2) / 2.0,
|
||||||
my = (y1 + y2) / 2.0 - 3.0,
|
my = (y1 + y2) / 2.0 - 4.0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The middle of the table: Problems, then the two stacks under them.
|
||||||
|
let span = (view.problems.len().max(1) as f64) * 130.0;
|
||||||
|
let _ = write!(
|
||||||
|
s,
|
||||||
|
"<g transform=\"translate({x:.0},170)\">",
|
||||||
|
x = cx - span / 2.0,
|
||||||
|
);
|
||||||
|
for (i, (priority, p)) in view.problems.iter().enumerate() {
|
||||||
|
problem_svg(&mut s, *priority, p, (i as i32) * 130);
|
||||||
|
}
|
||||||
|
s.push_str("</g>");
|
||||||
|
let _ = write!(s, "<g transform=\"translate({x:.0},290)\">", x = cx - 150.0);
|
||||||
|
piles_body(&mut s, view);
|
||||||
|
s.push_str("</g>");
|
||||||
|
|
||||||
|
// Seats, each with what it played in front of it.
|
||||||
for (p, x, y) in &pos {
|
for (p, x, y) in &pos {
|
||||||
let is_viewer = view.viewer == Some(*p);
|
let is_viewer = view.viewer == Some(*p);
|
||||||
let focus = view
|
let focus = view
|
||||||
.focus
|
.focus
|
||||||
.get(p)
|
.get(p)
|
||||||
.map(|f| format!(" \u{2192}{}", seat_name(*f)));
|
.map(|f| format!(" \u{2192}{}", seat_name(*f)))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let pv = &view.players[p];
|
||||||
|
// The played card sits between the seat and the centre.
|
||||||
|
let (dx, dy) = ((cx - x) * 0.30, (cy - y) * 0.30);
|
||||||
|
if let Some(sel) = view.selections.get(p) {
|
||||||
|
let _ = write!(
|
||||||
|
s,
|
||||||
|
"<g transform=\"translate({px:.0},{py:.0}) scale(0.62)\">",
|
||||||
|
px = x + dx - 26.0,
|
||||||
|
py = y + dy - 17.0,
|
||||||
|
);
|
||||||
|
played_inner(&mut s, sel);
|
||||||
|
s.push_str("</g>");
|
||||||
|
}
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
s,
|
s,
|
||||||
"<g data-drop=\"seat-{raw}\"><circle cx=\"{x:.0}\" cy=\"{y:.0}\" r=\"26\" fill=\"#1b1e26\" \
|
"<g data-drop=\"seat-{raw}\">\
|
||||||
stroke=\"{stroke}\" stroke-width=\"2\"/>\
|
<circle cx=\"{x:.0}\" cy=\"{y:.0}\" r=\"34\" fill=\"#1b1e26\" \
|
||||||
<text x=\"{x:.0}\" y=\"{ty:.0}\" fill=\"#dde\" font-size=\"12\" \
|
stroke=\"{stroke}\" stroke-width=\"{sw}\"/>\
|
||||||
text-anchor=\"middle\">{name}</text>\
|
<text x=\"{x:.0}\" y=\"{t1:.0}\" fill=\"#dde\" font-size=\"12\" \
|
||||||
<text x=\"{x:.0}\" y=\"{ty2:.0}\" fill=\"#89a\" font-size=\"10\" \
|
text-anchor=\"middle\">{name}{you}</text>\
|
||||||
text-anchor=\"middle\">{focus}</text></g>",
|
<text x=\"{x:.0}\" y=\"{t2:.0}\" fill=\"#89a\" font-size=\"10\" \
|
||||||
|
text-anchor=\"middle\">stress {stress}{focus}</text></g>",
|
||||||
raw = p.0,
|
raw = p.0,
|
||||||
|
// The viewer's own seat is thicker and blue: a table where you
|
||||||
|
// cannot find yourself is worse than a list.
|
||||||
stroke = if is_viewer { "#9cf" } else { "#5a6b7a" },
|
stroke = if is_viewer { "#9cf" } else { "#5a6b7a" },
|
||||||
ty = y + 2.0,
|
sw = if is_viewer { 3 } else { 2 },
|
||||||
ty2 = y + 16.0,
|
t1 = y - 2.0,
|
||||||
|
t2 = y + 14.0,
|
||||||
name = seat_name(*p),
|
name = seat_name(*p),
|
||||||
focus = esc(focus.as_deref().unwrap_or("")),
|
you = if is_viewer { " (you)" } else { "" },
|
||||||
|
stress = pv.stress,
|
||||||
|
focus = esc(&focus),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
s.push_str("</svg>");
|
s.push_str("</svg>");
|
||||||
|
|
@ -575,6 +610,11 @@ const CARD_BACK: &str = "<svg viewBox=\"0 0 84 56\" width=\"84\" height=\"56\" c
|
||||||
<rect x=\"2\" y=\"2\" width=\"80\" height=\"52\" rx=\"7\" fill=\"#2a2f3a\" stroke=\"#5a6b7a\"/>\
|
<rect x=\"2\" y=\"2\" width=\"80\" height=\"52\" rx=\"7\" fill=\"#2a2f3a\" stroke=\"#5a6b7a\"/>\
|
||||||
<path d=\"M14 14 h56 M14 28 h56 M14 42 h56\" stroke=\"#3d4756\" stroke-width=\"3\"/></svg>";
|
<path d=\"M14 14 h56 M14 28 h56 M14 42 h56\" stroke=\"#3d4756\" stroke-width=\"3\"/></svg>";
|
||||||
|
|
||||||
|
/// The played card without its own `<svg>`, for placing on the table.
|
||||||
|
fn played_inner(out: &mut String, sel: &SelectionView) {
|
||||||
|
played_svg(out, sel);
|
||||||
|
}
|
||||||
|
|
||||||
fn played_svg(out: &mut String, sel: &SelectionView) {
|
fn played_svg(out: &mut String, sel: &SelectionView) {
|
||||||
let s = match sel {
|
let s = match sel {
|
||||||
SelectionView::Hidden => {
|
SelectionView::Hidden => {
|
||||||
|
|
@ -845,21 +885,15 @@ fn meta_section(s: &mut String, meta: &[String], note_to: &str) {
|
||||||
/// than a second rendering of it — two renderings of one state is how
|
/// than a second rendering of it — two renderings of one state is how
|
||||||
/// they drift.
|
/// they drift.
|
||||||
fn body(s: &mut String, view: &GroundView) {
|
fn body(s: &mut String, view: &GroundView) {
|
||||||
s.push_str(
|
// CB-WP-0028 T03: one overhead view — seats around the edge, the
|
||||||
"<h2>problems</h2><svg width=\"760\" height=\"100\" role=\"img\" aria-label=\"problems\">",
|
// Problems and both stacks in the middle, each seat's played card in
|
||||||
);
|
// front of it. This replaces three separate diagrams the player had to
|
||||||
|
// assemble: a Problems row, a relationship circle, and a piles picture.
|
||||||
|
s.push_str("<h2>the table</h2>");
|
||||||
if view.problems.is_empty() {
|
if view.problems.is_empty() {
|
||||||
s.push_str(
|
s.push_str("<div class=\"card\">no problems in play</div>");
|
||||||
"<text x=\"12\" y=\"52\" fill=\"#89a\" font-size=\"12\">no problems in play</text>",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
for (i, (priority, p)) in view.problems.iter().enumerate() {
|
s.push_str(&table_svg(view));
|
||||||
problem_svg(s, *priority, p, 10 + (i as i32) * 130);
|
|
||||||
}
|
|
||||||
s.push_str("</svg>");
|
|
||||||
|
|
||||||
s.push_str("<h2>relationships</h2>");
|
|
||||||
s.push_str(&relations_svg(view));
|
|
||||||
|
|
||||||
s.push_str("<h2>seats</h2><div class=\"row\">");
|
s.push_str("<h2>seats</h2><div class=\"row\">");
|
||||||
for (id, p) in &view.players {
|
for (id, p) in &view.players {
|
||||||
|
|
@ -867,8 +901,13 @@ fn body(s: &mut String, view: &GroundView) {
|
||||||
}
|
}
|
||||||
s.push_str("</div>");
|
s.push_str("</div>");
|
||||||
|
|
||||||
s.push_str("<h2>solutions</h2>");
|
// The discard's contents stay as text: it is public and readable,
|
||||||
piles_svg(s, view);
|
// and the stack on the table shows only how many.
|
||||||
|
let _ = write!(
|
||||||
|
s,
|
||||||
|
"<div><span class=\"k\">discard</span> {}</div>",
|
||||||
|
esc(&cards(&view.solution_discard)),
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(o) = &view.outcome {
|
if let Some(o) = &view.outcome {
|
||||||
let personal = o
|
let personal = o
|
||||||
|
|
|
||||||
|
|
@ -714,6 +714,119 @@ mod piles {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CB-WP-0028 T03 — one overhead table, not three diagrams.
|
||||||
|
#[cfg(test)]
|
||||||
|
mod overhead_table {
|
||||||
|
use cb_game_runtime::{Project, ScenarioGame, Setup, Viewer};
|
||||||
|
use cb_kernel::PlayerId;
|
||||||
|
use games_ground::GroundState;
|
||||||
|
|
||||||
|
fn view_of(players: u8) -> games_ground::view::GroundView {
|
||||||
|
GroundState::setup(
|
||||||
|
&Setup {
|
||||||
|
players,
|
||||||
|
preset: format!("standard-{players}p"),
|
||||||
|
patch: Default::default(),
|
||||||
|
},
|
||||||
|
7,
|
||||||
|
)
|
||||||
|
.expect("preset")
|
||||||
|
.project(Viewer::Player(PlayerId(0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **Every seat count lays out, and no two seats land on each other.**
|
||||||
|
/// Asserted per count rather than eyeballed at three, which is the
|
||||||
|
/// only count anyone ever looks at.
|
||||||
|
#[test]
|
||||||
|
fn two_through_six_seats_all_lay_out_without_overlap() {
|
||||||
|
for players in 2..=6u8 {
|
||||||
|
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();
|
||||||
|
assert_eq!(
|
||||||
|
seats.len(),
|
||||||
|
players as usize,
|
||||||
|
"{players}p: expected one circle per seat, got {seats:?}"
|
||||||
|
);
|
||||||
|
for (i, a) in seats.iter().enumerate() {
|
||||||
|
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,
|
||||||
|
"{players}p: two seats are {d:.0}px apart and the circles are r=34 — \
|
||||||
|
they overlap"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A table where you cannot find yourself is worse than a list.
|
||||||
|
#[test]
|
||||||
|
fn the_viewers_own_seat_is_marked() {
|
||||||
|
let v = view_of(4);
|
||||||
|
let html = crate::doc::document(&v, &[], "/command?t=x", Some(PlayerId(0)), false);
|
||||||
|
assert!(
|
||||||
|
crate::text_of(&html).contains("P1 (you)"),
|
||||||
|
"the viewer's seat is not identifiable"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
html.contains("stroke=\"#9cf\" stroke-width=\"3\""),
|
||||||
|
"the viewer's seat should also be visually distinct, not only labelled"
|
||||||
|
);
|
||||||
|
// A spectator has no seat to mark, and must not claim one.
|
||||||
|
let spec = GroundState::setup(
|
||||||
|
&Setup {
|
||||||
|
players: 4,
|
||||||
|
preset: "standard-4p".into(),
|
||||||
|
patch: Default::default(),
|
||||||
|
},
|
||||||
|
7,
|
||||||
|
)
|
||||||
|
.expect("preset")
|
||||||
|
.project(Viewer::Spectator);
|
||||||
|
assert!(
|
||||||
|
!crate::text_of(&crate::doc::document(&spec, &[], "/x", None, false)).contains("(you)"),
|
||||||
|
"a spectator was given a seat"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The three diagrams became one: the relationship circle and the
|
||||||
|
/// piles picture are gone as separate views, and their content is on
|
||||||
|
/// the table.
|
||||||
|
#[test]
|
||||||
|
fn the_stacks_and_the_relations_are_on_the_table() {
|
||||||
|
let v = view_of(3);
|
||||||
|
let html = crate::doc::document(&v, &[], "/command?t=x", Some(PlayerId(0)), false);
|
||||||
|
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("draw pile:"),
|
||||||
|
"the draw stack is not on the table"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
table.contains("discard pile:"),
|
||||||
|
"the discard is not on the table"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!html.contains("relationship graph"),
|
||||||
|
"the old separate relationship diagram is still being drawn"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// CB-WP-0028 T02 — the cards say what they do, in the edition's words.
|
/// CB-WP-0028 T02 — the cards say what they do, in the edition's words.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod card_words {
|
mod card_words {
|
||||||
|
|
@ -936,7 +1049,7 @@ mod two_columns {
|
||||||
.and_then(|s| s.split("class=\"cb-meta\"").next())
|
.and_then(|s| s.split("class=\"cb-meta\"").next())
|
||||||
.expect("a game column followed by a meta column");
|
.expect("a game column followed by a meta column");
|
||||||
assert!(
|
assert!(
|
||||||
game.contains("<h2>problems</h2>"),
|
game.contains("<h2>the table</h2>"),
|
||||||
"the table must be in the game column"
|
"the table must be in the game column"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
163
evidence/CB-EV-0026-the-table-you-sit-at.md
Normal file
163
evidence/CB-EV-0026-the-table-you-sit-at.md
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
# CB-EV-0026 — the table you sit at
|
||||||
|
|
||||||
|
CB-WP-0028 T08. Tier M (structural M — imports more of an external
|
||||||
|
dataset under AM-4's budgets; chaos d8=1 → no override). Declaration 11 of
|
||||||
|
chaos window 2. Closed 2026-08-06.
|
||||||
|
|
||||||
|
**Delivered:** [ADR-0015](../decisions/ADR-0015-the-cards-own-words.md),
|
||||||
|
three vendored edition files, a generic reader, the cards' own words on
|
||||||
|
the page, one overhead table replacing three diagrams, controls and log in
|
||||||
|
the meta column, *"game solved"*, and rankings that cite their source.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Nine observations, and where they actually came from
|
||||||
|
|
||||||
|
**Seven of nine were engine defects. One was a design finding. One was
|
||||||
|
already true and nobody could tell.**
|
||||||
|
|
||||||
|
| # | observation | what it turned out to be |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | no incentive to attack | a **design finding** (F17), and still a note |
|
||||||
|
| 2 | *"I don't understand the GROUND card"* | **a data-import gap** — the card explains itself and we never read it |
|
||||||
|
| 3 | overhead view | three diagrams the player had to assemble |
|
||||||
|
| 4 | click the deck to draw | **not a legal move**; built nothing |
|
||||||
|
| 5 | auto-draw option | **already automatic**, and always was |
|
||||||
|
| 6–7 | controls and log on the right | layout |
|
||||||
|
| 8 | *"Game Over"* on a win | wrong vocabulary for a co-operative game |
|
||||||
|
| 9 | rankings | a place where the game defines *not to rank* |
|
||||||
|
|
||||||
|
**Observations 4 and 5 are the most useful pair in the set**, because both
|
||||||
|
dissolved. Clicking the deck is not a move — `GroundCommand` has no
|
||||||
|
standalone draw, and the edition's INVESTIGATE text says *"…reveal it.
|
||||||
|
**Then draw one Solution.**"* Drawing is a consequence. And auto-draw was
|
||||||
|
asked for a thing that has **never been manual**.
|
||||||
|
|
||||||
|
**Both had one cause: nothing on the page said how drawing works.** The
|
||||||
|
INVESTIGATE card's own words were in a file we had not imported. So a
|
||||||
|
player invented a mental model to fill the gap, and reported the gap as
|
||||||
|
two feature requests.
|
||||||
|
|
||||||
|
**No finding was raised.** An instinct differing from a *legible* rule is
|
||||||
|
a comprehension gap; an instinct differing from an *invisible* one is our
|
||||||
|
defect. Whether it recurs now that the card says it is a testable question
|
||||||
|
and was not before.
|
||||||
|
|
||||||
|
## 2. The import gap was worse than "one file of nineteen"
|
||||||
|
|
||||||
|
Measured before deciding:
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| edition files upstream | 19 |
|
||||||
|
| vendored | **1** |
|
||||||
|
| columns in that one file | 13 |
|
||||||
|
| columns the engine read | **5** |
|
||||||
|
| scenarios in the file | 4 |
|
||||||
|
| scenarios the engine deals | **1**, hardcoded |
|
||||||
|
|
||||||
|
`title`, `problem_text`, `front_rules`, `reveal_effect` and
|
||||||
|
`unresolved_effect` were vendored on 2026-08-04 and **discarded at parse
|
||||||
|
time for eight days**. The page showed `Repair 2` for a card that reads
|
||||||
|
*"Missed Deadline"*. Fixing that cost no new bytes, no dependency and no
|
||||||
|
budget — it was the cheapest thing in the pass and the one nothing had
|
||||||
|
found.
|
||||||
|
|
||||||
|
**Rule coverage was 59/59 throughout.** The gate measures whether rules
|
||||||
|
are exercised, and every rule was. **Nothing measures whether a player can
|
||||||
|
read the game**, and nothing cheaply could — which is why the person
|
||||||
|
playing it is the instrument.
|
||||||
|
|
||||||
|
## 3. The dependency question was answered by measurement, not preference
|
||||||
|
|
||||||
|
ADR-0011 refused the `csv` crate and named its own revisit condition:
|
||||||
|
*"nested quoting, embedded newlines, multiple dialects."*
|
||||||
|
|
||||||
|
Across every candidate file — Actions, Solutions, Modes, Scenarios —
|
||||||
|
**zero doubled quotes and zero embedded newlines.** The hand reader's only
|
||||||
|
job is comma-in-quoted-field, which it already did.
|
||||||
|
|
||||||
|
So the argument was not re-run, **and the reason is a measurement rather
|
||||||
|
than the inconvenience of re-running it.** `edition-check` now asserts
|
||||||
|
that condition on every vendored file, so the day it stops being true the
|
||||||
|
gate says so instead of a parser mangling a card.
|
||||||
|
|
||||||
|
**One reader, four callers.** A per-file copy is how a parser acquires
|
||||||
|
four subtly different bugs.
|
||||||
|
|
||||||
|
## 4. Two gates were written for a smaller world
|
||||||
|
|
||||||
|
**`edition-check` compared the first recorded digest against
|
||||||
|
`Problems.csv` regardless of which file that digest described.** With one
|
||||||
|
vendored file it was correct; with four it was comparing across files. It
|
||||||
|
now checks both directions — a vendored file with no digest fails, and a
|
||||||
|
digest naming an absent file fails.
|
||||||
|
|
||||||
|
**A `cb-play` test asserted the literal string `"game over"`** and went
|
||||||
|
red when a won game began saying *"solved"* — the feature working. Fixed
|
||||||
|
by asserting the heading **against the outcome** rather than against a
|
||||||
|
word, which also covers the no-outcome case the original never touched.
|
||||||
|
|
||||||
|
**Both are the same shape:** an assertion that encoded a world with one
|
||||||
|
file, or one ending. Neither was wrong when written.
|
||||||
|
|
||||||
|
## 5. What T07 decided not to do
|
||||||
|
|
||||||
|
`Modes.csv` gives `scoring_tiebreak` per mode. For SHARED GROUND it is
|
||||||
|
**"Not applicable"** — the table succeeds or fails together.
|
||||||
|
|
||||||
|
So the co-operative mode shows contributions and **refuses to order
|
||||||
|
them**, and says why on the page. A leaderboard would have been easy,
|
||||||
|
looked good, and invented scoring the game does not have — the same defect
|
||||||
|
class as canonising a provisional default, which this project has already
|
||||||
|
committed once.
|
||||||
|
|
||||||
|
Where a mode *does* rank, the ordering is the game's own words. The single
|
||||||
|
derived superlative is labelled **"clay-borg's reading, not a rule."**
|
||||||
|
|
||||||
|
**This is the first time the project has had the game's own tiebreak to
|
||||||
|
cite**, and it only had it because §2's import brought `Modes.csv` in. The
|
||||||
|
alternative was inventing one.
|
||||||
|
|
||||||
|
## 6. The overhead view cost no coverage probe
|
||||||
|
|
||||||
|
All existing probes passed through a restructure that **deleted two
|
||||||
|
renderers** (`relations_svg`, `piles_svg`) and merged three diagrams into
|
||||||
|
one.
|
||||||
|
|
||||||
|
**Second confirmation of CB-WP-0027's finding**: a probe that names a
|
||||||
|
*fact* survives a reflow; one that names a *presentation* does not.
|
||||||
|
CB-WP-0024's `17 remaining` probe broke on a rendering change;
|
||||||
|
CB-WP-0027's and this pass's did not, and this reflow was far larger.
|
||||||
|
|
||||||
|
The new control is per-seat-count: **2 through 6 seats all lay out with no
|
||||||
|
two seat circles closer than 70px**, asserted rather than eyeballed at
|
||||||
|
three — which is the only count anyone ever looks at.
|
||||||
|
|
||||||
|
## 7. Chaos window 2 — closed
|
||||||
|
|
||||||
|
**Declaration 11 of 12**, structural M, d8 = 1, no override.
|
||||||
|
|
||||||
|
The window has now produced **zero overrides in eleven declarations at
|
||||||
|
d8**. Its retirement condition — *retire if an override changes nothing
|
||||||
|
twice running* — was never testable, and CB-EV-0024 and CB-EV-0025 both
|
||||||
|
said so. This is the third and final statement of it.
|
||||||
|
|
||||||
|
**The window's verdict should be that d8 bought rarity by spending
|
||||||
|
evidence.** Window 1 at d4: two overrides, both changed the outcome.
|
||||||
|
Window 2 at d8: none. **A mechanism that produces no data across a full
|
||||||
|
window cannot be evaluated by that window**, which is a stronger
|
||||||
|
conclusion than "the rate is too low" and belongs in whatever closes it.
|
||||||
|
|
||||||
|
## Open after this pass
|
||||||
|
|
||||||
|
- **F17 needs an artifact.** Counting ATTACK selections across the policy
|
||||||
|
panel against hand quality is cheap and nobody has done it.
|
||||||
|
- **Three of four scenarios have never been dealt** (ADR-0015 D5). Now
|
||||||
|
visible, still true.
|
||||||
|
- **`Relations`, `DARVO`, `Tokens`, `Glossary` are deferred, not refused.**
|
||||||
|
If a seat's DARVO stage needs its own words, that is the trigger.
|
||||||
|
- **Nobody has written a trial note in anger** — carried forward from
|
||||||
|
CB-EV-0025, and the note channel only started working today.
|
||||||
|
- **Whether the card text changed what the maintainer understood** is this
|
||||||
|
pass's real acceptance test and has a person attached to it.
|
||||||
|
|
@ -752,7 +752,19 @@ mod tests {
|
||||||
let replies = client.join().expect("client");
|
let replies = client.join().expect("client");
|
||||||
|
|
||||||
assert!(replies[0].contains("200 OK"), "{}", replies[0]);
|
assert!(replies[0].contains("200 OK"), "{}", replies[0]);
|
||||||
assert!(replies[0].contains("game over"), "the page did not say so");
|
// CB-WP-0028 T06: the heading follows the OUTCOME, so this asks
|
||||||
|
// the view rather than hardcoding a word. The old assertion said
|
||||||
|
// "game over" and went red when a won game started saying
|
||||||
|
// "solved" -- which was the feature working.
|
||||||
|
let want = match view.outcome.as_ref() {
|
||||||
|
Some(o) if o.group_success => "game solved",
|
||||||
|
Some(_) => "game over",
|
||||||
|
None => "the game stopped",
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
replies[0].contains(want),
|
||||||
|
"the heading did not match the outcome; expected {want:?}"
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
replies[0].contains("30 commands"),
|
replies[0].contains("30 commands"),
|
||||||
"the result did not reach the page"
|
"the result did not reach the page"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
id: CB-WP-0028
|
id: CB-WP-0028
|
||||||
kind: product
|
kind: product
|
||||||
title: "The table you sit at: the cards' own words, an overhead view, and a game you solve rather than survive"
|
title: "The table you sit at: the cards' own words, an overhead view, and a game you solve rather than survive"
|
||||||
status: active
|
status: done
|
||||||
state_hub_workstream_id: "d37c8671-54e4-447f-af64-56f482483282"
|
state_hub_workstream_id: "d37c8671-54e4-447f-af64-56f482483282"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -39,29 +39,16 @@ split matters because they go to different places.
|
||||||
|
|
||||||
## Observation 2 is a data-import gap, and it reframes the pass
|
## Observation 2 is a data-import gap, and it reframes the pass
|
||||||
|
|
||||||
*"I don't understand the GROUND card or why other cards should be played
|
*"I don't understand the GROUND card"* reads as a design problem. It is
|
||||||
to the table"* reads as a design problem. It is not. **The card explains
|
not: `Actions.csv` carries that card's tagline (*"Regulate. Restore the
|
||||||
itself in the dataset and we never imported the explanation.**
|
frame. Decide."*) and its full rules text, and **we vendored one file of
|
||||||
|
nineteen**. Everything else the engine knows is a hand-transcription into
|
||||||
|
`GroundRules.md`'s 59 rules — enough to *play* the game, and nothing to
|
||||||
|
*read*. The page shows `Clarify` where the card says *"Ask What Happened."*
|
||||||
|
|
||||||
`ground-game/editions/*/Actions.csv`, the GROUND row:
|
**Found by a player saying he did not understand something.** Rule
|
||||||
|
coverage is 59/59 and has been for weeks. See F18 and
|
||||||
> **tagline:** *"Regulate. Restore the frame. Decide."*
|
[ADR-0015](../decisions/ADR-0015-the-cards-own-words.md).
|
||||||
> **rules_text:** *"After all actions are revealed, choose one mode:
|
|
||||||
> GR—Ground & Restate: −2 Stress, ready Freedom… OU—Observe & Uphold…"*
|
|
||||||
|
|
||||||
**We vendored one file of nineteen.** `editions/ground-darvo-r0/` holds
|
|
||||||
`Problems.csv` and nothing else; `Actions`, `Solutions`, `Modes`,
|
|
||||||
`Scenarios`, `Relations`, `DARVO`, `Tokens`, `Glossary` and the rest live
|
|
||||||
only in `ground-game`. Everything the engine knows about them is a
|
|
||||||
**hand-transcription into `GroundRules.md`'s 59 numbered rules** — which
|
|
||||||
is enough to *play* the game and gives a player nothing to *read*.
|
|
||||||
|
|
||||||
So the page shows `Clarify` where the card says **"Ask What Happened —
|
|
||||||
Invite a concrete account before judging."**
|
|
||||||
|
|
||||||
**This is the most valuable thing in the pass**, and it was found by a
|
|
||||||
player saying he did not understand something rather than by any gate.
|
|
||||||
Rule coverage is 59/59 and has been for weeks.
|
|
||||||
|
|
||||||
## Task: decide what else to import, and what it costs
|
## Task: decide what else to import, and what it costs
|
||||||
|
|
||||||
|
|
@ -103,23 +90,17 @@ Decide:
|
||||||
[ADR-0015](../decisions/ADR-0015-the-cards-own-words.md), six decisions.
|
[ADR-0015](../decisions/ADR-0015-the-cards-own-words.md), six decisions.
|
||||||
|
|
||||||
**The gap is bigger than "one file of nineteen", and the measurement is
|
**The gap is bigger than "one file of nineteen", and the measurement is
|
||||||
the decision.** Of the file we *did* vendor, the engine reads **5 of 13
|
the decision.** Of the file we *did* vendor the engine reads **5 of 13
|
||||||
columns** — `title`, `problem_text`, `front_rules`, `reveal_effect` and
|
columns**; the discarded ones are player-facing text that had been in the
|
||||||
`unresolved_effect` were discarded at parse time. **The cheapest part of
|
repo for eight days. And `SCN_01` is hardcoded — the edition ships **four**
|
||||||
this pass costs no new bytes** and was sitting in the repo for eight days.
|
scenarios and the engine has never dealt three.
|
||||||
And `SCN_01` is hardcoded at `lib.rs:1824`: the edition ships **four**
|
|
||||||
scenarios and the engine has never dealt three of them.
|
|
||||||
|
|
||||||
**ADR-0011's revisit condition is measurably absent**, so the dependency
|
**ADR-0011's revisit condition is measurably absent** — zero doubled
|
||||||
argument does not get re-run. Across Actions, Solutions, Modes and
|
quotes, zero embedded newlines across every candidate file — so the
|
||||||
Scenarios: **zero doubled quotes, zero embedded newlines.** The hand
|
dependency argument does not get re-run. Vendored `Actions`, `Solutions`,
|
||||||
reader's only job is comma-in-quoted-field, which it already did.
|
`Modes`. Not the production artifacts, and **not `Extensions.csv`**, which
|
||||||
|
names deliberately non-core content; it is now known to exist, which was
|
||||||
Vendored `Actions`, `Solutions`, `Modes` — the text a player reads. Not
|
the real risk.
|
||||||
the production artifacts. **Not `Extensions.csv`**, because it names
|
|
||||||
content the designer placed *outside* the core, and importing it would
|
|
||||||
break the as-printed claim — but it is now known to exist, which was the
|
|
||||||
real risk.
|
|
||||||
|
|
||||||
## Task: the cards say what they do
|
## Task: the cards say what they do
|
||||||
|
|
||||||
|
|
@ -147,38 +128,26 @@ from the edition — not from a phrase we invented.
|
||||||
- **the page stays readable.** Five action cards with full rules text is a
|
- **the page stays readable.** Five action cards with full rules text is a
|
||||||
wall; the tagline is the default and the rules text is on demand.
|
wall; the tagline is the default and the rules text is on demand.
|
||||||
|
|
||||||
**Done 2026-08-06.** One `Table` reader with four callers (a per-file copy
|
**Done 2026-08-06.** One `Table` reader with four callers (a per-file
|
||||||
is how a parser acquires four subtly different bugs), `CardText` for
|
copy is how a parser acquires four subtly different bugs), `CardText` for
|
||||||
Actions/Solutions/Modes, and `ProblemText` for the columns already
|
Actions/Solutions/Modes, `ProblemText` for the columns already vendored.
|
||||||
vendored.
|
The GROUND card now explains itself; Problems show their own titles.
|
||||||
|
|
||||||
**The GROUND card now explains itself**: *"Regulate. Restore the frame.
|
|
||||||
Decide."* as the tagline, its GR/OU/ND text behind a disclosure. Problems
|
|
||||||
show their own titles where a priority number used to be.
|
|
||||||
|
|
||||||
**The load-bearing test asserts the text is a substring of the vendored
|
**The load-bearing test asserts the text is a substring of the vendored
|
||||||
file**, not equal to a Rust literal — a test comparing against a
|
file**, not equal to a Rust literal — a hardcoded expectation would pass
|
||||||
hardcoded expectation would pass for a hand-copied string, which is the
|
for a hand-copied string, which is the drift this ends.
|
||||||
drift this ends. Plus counts (5 actions, 24 solutions, 3 modes), because a
|
|
||||||
reader returning one row would pass a "the text is real" test.
|
|
||||||
|
|
||||||
**`edition` came out from behind `#[cfg(feature = "scenarios")]`.** It was
|
**`edition` came out from behind `#[cfg(feature = "scenarios")]`**: it was
|
||||||
gated because its only consumer was; but the edition is the game's own
|
gated because its only consumer was, but the edition is the game's own
|
||||||
data and the shipped runtime now reads it. **Test machinery and game
|
data and the runtime now reads it. **Test machinery and game content are
|
||||||
content are different things and only one of them is optional.**
|
different things and only one is optional.** `edition-check`, written for
|
||||||
|
a single-file world, now checks every file both ways.
|
||||||
**`edition-check` was written for a single-file world** and compared the
|
|
||||||
first recorded digest against `Problems.csv` regardless of which file it
|
|
||||||
described. It now checks every file both ways — a vendored file with no
|
|
||||||
digest fails, and a digest naming an absent file fails — and asserts
|
|
||||||
ADR-0015 D3's falsifier directly: no doubled quotes, no embedded
|
|
||||||
newlines.
|
|
||||||
|
|
||||||
## Task: an overhead view of a real table
|
## Task: an overhead view of a real table
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: CB-WP-0028-T03
|
id: CB-WP-0028-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "2ff677ac-80ff-4618-ad7c-05b62c8f80c8"
|
state_hub_task_id: "2ff677ac-80ff-4618-ad7c-05b62c8f80c8"
|
||||||
```
|
```
|
||||||
|
|
@ -204,6 +173,25 @@ as a circle and once as a row. **One table, not two diagrams.**
|
||||||
- 2 through 6 seats all lay out without overlap, asserted per seat count
|
- 2 through 6 seats all lay out without overlap, asserted per seat count
|
||||||
rather than eyeballed at 3.
|
rather than eyeballed at 3.
|
||||||
|
|
||||||
|
**Done 2026-08-06.** One `table_svg`: seats around an elliptical table
|
||||||
|
starting at the **bottom** (the viewer sits nearest the reader, as at a
|
||||||
|
real table), Problems and both stacks in the middle, each seat's played
|
||||||
|
card between it and the centre, relations drawn between seats.
|
||||||
|
|
||||||
|
**Two renderers were deleted** — `relations_svg` and `piles_svg`. The task
|
||||||
|
said *one table, not two diagrams*, and leaving the old ones would have
|
||||||
|
meant drawing the same thing twice and letting them drift.
|
||||||
|
|
||||||
|
**No coverage probe cost**, through a restructure that merged three
|
||||||
|
diagrams and removed two functions. **Second confirmation of CB-WP-0027's
|
||||||
|
finding**: a probe naming a *fact* survives a reflow; one naming a
|
||||||
|
*presentation* does not.
|
||||||
|
|
||||||
|
The new control is per seat count: **no two seat circles closer than 70px
|
||||||
|
at 2, 3, 4, 5 or 6 seats** — asserted rather than eyeballed at three,
|
||||||
|
which is the only count anyone ever looks at. Plus: the viewer's seat is
|
||||||
|
labelled *and* visually distinct, and a spectator is not given one.
|
||||||
|
|
||||||
## Task: take cards from the stack, or let the table do it
|
## Task: take cards from the stack, or let the table do it
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
@ -369,7 +357,7 @@ are shown as ties.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: CB-WP-0028-T08
|
id: CB-WP-0028-T08
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "a20a529e-0edf-49cc-9c80-7dba3b76cff5"
|
state_hub_task_id: "a20a529e-0edf-49cc-9c80-7dba3b76cff5"
|
||||||
```
|
```
|
||||||
|
|
@ -387,3 +375,25 @@ state_hub_task_id: "a20a529e-0edf-49cc-9c80-7dba3b76cff5"
|
||||||
- **What the overhead view cost the coverage gate**, against CB-WP-0027's
|
- **What the overhead view cost the coverage gate**, against CB-WP-0027's
|
||||||
finding that a probe naming a fact survives a reflow.
|
finding that a probe naming a fact survives a reflow.
|
||||||
- **Quote CB-WP-0027's cost by re-running the instrument.**
|
- **Quote CB-WP-0027's cost by re-running the instrument.**
|
||||||
|
|
||||||
|
**Done 2026-08-06.**
|
||||||
|
[CB-EV-0026](../evidence/CB-EV-0026-the-table-you-sit-at.md).
|
||||||
|
|
||||||
|
- **Seven of nine observations were engine defects**, one was a design
|
||||||
|
finding, and one was already true and nobody could tell.
|
||||||
|
- **Observations 4 and 5 both dissolved**, and had one cause: nothing on
|
||||||
|
the page said how drawing works, so a player built a mental model to
|
||||||
|
fill the gap and reported the gap as two feature requests.
|
||||||
|
- **The import gap was worse than "one of nineteen"** — 5 of 13 columns
|
||||||
|
read from the file we *did* vendor, discarded at parse time for eight
|
||||||
|
days. Rule coverage was 59/59 throughout: the gate measures whether
|
||||||
|
rules are *exercised*, and **nothing measures whether a player can read
|
||||||
|
the game.**
|
||||||
|
- **Two gates were written for a smaller world** — `edition-check`
|
||||||
|
compared one digest across four files, and a `cb-play` test asserted the
|
||||||
|
literal `"game over"` and went red when a won game said *"solved"*.
|
||||||
|
Neither was wrong when written.
|
||||||
|
- **Chaos window 2 closes with zero overrides in eleven declarations.**
|
||||||
|
Third and final statement: d8 bought rarity by spending evidence, and a
|
||||||
|
mechanism producing no data across a full window cannot be evaluated by
|
||||||
|
it.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue