CB-WP-0036 done: the pace flag and the first ornament declarations
Some checks failed
ci / check (push) Failing after 4s
Some checks failed
ci / check (push) Failing after 4s
--pace speed|interactive, defaulting to Speed. Nothing reads it yet, and that is the point: it is the seam clay-animate attaches to, and a seam is cheap now where a retrofit would not be. A misspelt pace is refused rather than defaulting, because quietly falling back to Speed would look exactly like the renderer being broken. I3 is asserted rather than intended: the same scripted game at both paces must produce a byte-identical serialised recording and the same end state hash. Mutation-proven — leak the pace into the seed and it fails with "the recording differs by pace, so a renderer has become mechanism". specs/OrnamentRegister.md carries four declarations. This reverses the reasoning written in T03 earlier, which said the first declarations would come from F18's unvendored files: instances already existed. Hand order is what prompted the category, and "who deals" was the maintainer's own example. O3 is the interesting one — seat ORDER is mechanism because GR-R08 rotates Lead, while where a seat is drawn is not. I5 is executable: check_ornament_falsifier fails any row still declared that names no falsifier, mutation-proven red on O1. Presence, never adequacy, and the finding text says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b231c1b5e2
commit
bd9e168af5
7 changed files with 315 additions and 9 deletions
69
specs/OrnamentRegister.md
Normal file
69
specs/OrnamentRegister.md
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Ornament register — what we claim does not matter
|
||||||
|
|
||||||
|
The declarations required by [`Ornamentation.md`](Ornamentation.md) §2.1.
|
||||||
|
|
||||||
|
**Every row is a claim that something does not affect the game.** This
|
||||||
|
project's finding register is largely a list of times that claim was
|
||||||
|
wrong, so each declaration carries a **falsifier**: the rule or
|
||||||
|
observation that would make it mechanism. A declaration that cannot be
|
||||||
|
wrong is not a claim (§2.1, invariant I5).
|
||||||
|
|
||||||
|
`grounded` is how the claim is supported — `rule` (the edition says so),
|
||||||
|
`measured` (we checked), or `provisional` (nobody has ruled, and we chose).
|
||||||
|
**`provisional` is a legitimate state and must be said out loud**; quietly
|
||||||
|
canonising a provisional default is a mistake this project has already
|
||||||
|
made once.
|
||||||
|
|
||||||
|
<!-- ornament-register:begin -->
|
||||||
|
|
||||||
|
| id | ornaments | grounded | state | raised |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| O1 | hand order | provisional | declared | 2026-08-07 |
|
||||||
|
| O2 | who shuffles and deals | provisional | declared | 2026-08-07 |
|
||||||
|
| O3 | where a seat is drawn | rule | declared | 2026-08-07 |
|
||||||
|
| O4 | card typography and illustration | provisional | declared | 2026-08-07 |
|
||||||
|
|
||||||
|
<!-- ornament-register:end -->
|
||||||
|
|
||||||
|
- **O1 — hand order.** The *contents* of a hand are mechanism; the order
|
||||||
|
the cards sit in is not. No numbered rule in `GroundRules.md` refers to
|
||||||
|
a card's position within a hand, and `PlayerView.hand` is a `Vec` whose
|
||||||
|
order comes from dealing, not from a rule.
|
||||||
|
**Falsifier:** any rule that refers to a card's position in hand —
|
||||||
|
*"play your leftmost"*, *"reveal the top of your hand"* — or any engine
|
||||||
|
behaviour that varies with it. Invariant I1 is the standing check:
|
||||||
|
sorting must not move `state_hash`, because a hash that moved would
|
||||||
|
break every trial note's binding (ADR-0019 D3) and every replay.
|
||||||
|
**This is the one that prompted the category**, from the request to
|
||||||
|
drag cards between hand slots.
|
||||||
|
|
||||||
|
- **O2 — who shuffles and deals.** The deck's permutation *is* mechanism
|
||||||
|
and comes from the seed; the ceremony around it is not. Nothing in the
|
||||||
|
rules names a dealer, and the hands are identical whoever performs it.
|
||||||
|
**Falsifier:** a rule that gives the dealer a decision, an advantage, or
|
||||||
|
an order of dealing that changes who receives which card. Note the deal
|
||||||
|
is **not** ornamental — only the ceremony around it is.
|
||||||
|
|
||||||
|
- **O3 — where a seat is drawn.** Seat *order* is mechanism: GR-R08
|
||||||
|
rotates Lead, so who follows whom binds. **Where a seat appears on the
|
||||||
|
drawn ellipse does not**, provided the drawn order preserves the
|
||||||
|
rotation order.
|
||||||
|
**Falsifier:** any rule referring to physical position — *"the player
|
||||||
|
across from you"*, *"your left-hand neighbour"* as distinct from the
|
||||||
|
next in rotation. This is `rule`-grounded rather than provisional
|
||||||
|
because GR-R08 states the ordering that binds.
|
||||||
|
|
||||||
|
- **O4 — card typography and illustration.** How a card looks does not
|
||||||
|
change what it does. **But it is not ours to invent**: ADR-0015's
|
||||||
|
discipline holds, and `Player_Mats` and `Glossary` — which carry exactly
|
||||||
|
this — are still unvendored (F18).
|
||||||
|
**Falsifier:** a card whose meaning is carried by its layout or art
|
||||||
|
rather than its text, which is a real possibility in a game about
|
||||||
|
perception and is the reason this is `provisional` rather than `rule`.
|
||||||
|
**Nothing may be declared about these files until they are read.**
|
||||||
|
|
||||||
|
## What is deliberately absent
|
||||||
|
|
||||||
|
**No row for animation, timing or movement.** Those are ornamentation, and
|
||||||
|
they belong to `clay-animate` (§3). A declaration here is a claim about
|
||||||
|
*the game*; how a renderer performs it is that renderer's business.
|
||||||
|
|
@ -1215,6 +1215,7 @@ mod tests {
|
||||||
serve: Some(0),
|
serve: Some(0),
|
||||||
trial: None,
|
trial: None,
|
||||||
mode: games_ground::ScoringMode::SharedGround,
|
mode: games_ground::ScoringMode::SharedGround,
|
||||||
|
pace: crate::table::Pace::Speed,
|
||||||
},
|
},
|
||||||
std::io::Cursor::new(Vec::new()),
|
std::io::Cursor::new(Vec::new()),
|
||||||
out,
|
out,
|
||||||
|
|
@ -1404,6 +1405,7 @@ mod tests {
|
||||||
serve: Some(0),
|
serve: Some(0),
|
||||||
trial: None,
|
trial: None,
|
||||||
mode: games_ground::ScoringMode::SharedGround,
|
mode: games_ground::ScoringMode::SharedGround,
|
||||||
|
pace: crate::table::Pace::Speed,
|
||||||
},
|
},
|
||||||
std::io::Cursor::new(Vec::new()),
|
std::io::Cursor::new(Vec::new()),
|
||||||
out,
|
out,
|
||||||
|
|
|
||||||
|
|
@ -764,6 +764,7 @@ mod tests {
|
||||||
serve: None,
|
serve: None,
|
||||||
trial: None,
|
trial: None,
|
||||||
mode: games_ground::ScoringMode::SharedGround,
|
mode: games_ground::ScoringMode::SharedGround,
|
||||||
|
pace: crate::table::Pace::Speed,
|
||||||
};
|
};
|
||||||
let mut sink: Vec<u8> = Vec::new();
|
let mut sink: Vec<u8> = Vec::new();
|
||||||
let summary =
|
let summary =
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ play:
|
||||||
--replay DIR write a .cbreplay bundle of the finished game to DIR
|
--replay DIR write a .cbreplay bundle of the finished game to DIR
|
||||||
--record FILE write the finished game as a scenario YAML
|
--record FILE write the finished game as a scenario YAML
|
||||||
--trial FILE write a trial log: what the player said, bound to where
|
--trial FILE write a trial log: what the player said, bound to where
|
||||||
|
--pace P speed (default) or interactive: how much ornamentation
|
||||||
|
is performed. Never changes the game -- the recording is
|
||||||
|
byte-identical either way (specs/Ornamentation.md)
|
||||||
--mode M scoring mode: shared (GR-E02), common (GR-E03),
|
--mode M scoring mode: shared (GR-E02), common (GR-E03),
|
||||||
coalitions (GR-E04). Default shared.
|
coalitions (GR-E04). Default shared.
|
||||||
--serve PORT play human seats in a browser on 127.0.0.1:PORT instead
|
--serve PORT play human seats in a browser on 127.0.0.1:PORT instead
|
||||||
|
|
@ -127,6 +130,18 @@ fn parse_args(argv: &[String]) -> Result<Mode, String> {
|
||||||
};
|
};
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
}
|
||||||
|
// CB-WP-0036 T02. A SEPARATE axis from --mode: that one is
|
||||||
|
// ScoringMode, a rule of the game; this is how much of what
|
||||||
|
// the rules cannot see gets performed (Ornamentation §4).
|
||||||
|
//
|
||||||
|
// **The flag exists before anything reads it, deliberately.**
|
||||||
|
// It is the seam `clay-animate` attaches to, and a seam is
|
||||||
|
// cheap now where a retrofit would not be.
|
||||||
|
"--pace" => {
|
||||||
|
play_flags.push(flag.into());
|
||||||
|
config.pace = value(i, argv, flag)?.parse()?;
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
"--trial" => {
|
"--trial" => {
|
||||||
play_flags.push(flag.into());
|
play_flags.push(flag.into());
|
||||||
config.trial = Some(value(i, argv, flag)?.into());
|
config.trial = Some(value(i, argv, flag)?.into());
|
||||||
|
|
@ -290,6 +305,7 @@ mod tests {
|
||||||
serve: None,
|
serve: None,
|
||||||
trial: None,
|
trial: None,
|
||||||
mode: games_ground::ScoringMode::SharedGround,
|
mode: games_ground::ScoringMode::SharedGround,
|
||||||
|
pace: table::Pace::Speed,
|
||||||
};
|
};
|
||||||
let script = "0\n".repeat(400);
|
let script = "0\n".repeat(400);
|
||||||
let mut out: Vec<u8> = Vec::new();
|
let mut out: Vec<u8> = Vec::new();
|
||||||
|
|
@ -312,6 +328,78 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// **Ornamentation §5, I3** — the invariant the clay-borg /
|
||||||
|
/// clay-animate split rests on.
|
||||||
|
///
|
||||||
|
/// > The same seed and the same decisions produce a **byte-identical
|
||||||
|
/// > recording** at any pace.
|
||||||
|
///
|
||||||
|
/// Asserted rather than intended. If this ever fails, something that
|
||||||
|
/// was called ornamentation has become mechanism, and the boundary in
|
||||||
|
/// `specs/Ornamentation.md` has stopped being real — which §7 names
|
||||||
|
/// as wrong at the root rather than patchable at the edges.
|
||||||
|
#[test]
|
||||||
|
fn pace_cannot_change_the_game() {
|
||||||
|
let at = |pace| {
|
||||||
|
let config = Config {
|
||||||
|
seed: 42,
|
||||||
|
players: 3,
|
||||||
|
human_seats: vec![0],
|
||||||
|
bot: "greedy".into(),
|
||||||
|
replay_dir: None,
|
||||||
|
record: None,
|
||||||
|
serve: None,
|
||||||
|
trial: None,
|
||||||
|
mode: games_ground::ScoringMode::SharedGround,
|
||||||
|
pace,
|
||||||
|
};
|
||||||
|
let script = "0\n".repeat(400);
|
||||||
|
let mut out: Vec<u8> = Vec::new();
|
||||||
|
let s = table::play(&config, script.as_bytes(), &mut out).expect("game");
|
||||||
|
(
|
||||||
|
serde_yaml::to_string(&s.scenario).expect("yaml"),
|
||||||
|
s.end_state_hash,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let (speed_yaml, speed_hash) = at(table::Pace::Speed);
|
||||||
|
let (inter_yaml, inter_hash) = at(table::Pace::Interactive);
|
||||||
|
|
||||||
|
// The recording, byte for byte.
|
||||||
|
assert_eq!(
|
||||||
|
speed_yaml, inter_yaml,
|
||||||
|
"the recording differs by pace, so a renderer has become mechanism"
|
||||||
|
);
|
||||||
|
// And the state hash, which is what §1.1 uses to tell the two
|
||||||
|
// categories apart in the first place.
|
||||||
|
assert_eq!(speed_hash, inter_hash, "pace moved the state hash");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `--pace` parses, defaults to speed, and refuses what it cannot do.
|
||||||
|
///
|
||||||
|
/// **Speed is the default** because `sim`, `trials`, the benchmarks
|
||||||
|
/// and every bot game run at it (Ornamentation §4).
|
||||||
|
#[test]
|
||||||
|
fn pace_parses_and_defaults_to_speed() {
|
||||||
|
assert_eq!(Config::default().pace, table::Pace::Speed);
|
||||||
|
assert_eq!(
|
||||||
|
play_args(&["--pace", "interactive"]).expect("parse").pace,
|
||||||
|
table::Pace::Interactive
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
play_args(&["--pace", "speed"]).expect("parse").pace,
|
||||||
|
table::Pace::Speed
|
||||||
|
);
|
||||||
|
// A misspelling must not silently mean the default: pace is the
|
||||||
|
// seam clay-animate attaches to, and a typo that quietly selects
|
||||||
|
// Speed would look like the renderer being broken.
|
||||||
|
let e = match play_args(&["--pace", "cinematic"]) {
|
||||||
|
Err(e) => e,
|
||||||
|
Ok(_) => panic!("a misspelt pace was accepted"),
|
||||||
|
};
|
||||||
|
assert!(e.contains("cinematic"), "{e}");
|
||||||
|
assert!(e.contains("speed") && e.contains("interactive"), "{e}");
|
||||||
|
}
|
||||||
|
|
||||||
/// K13 at the boundary that matters: what the human is *shown* must
|
/// K13 at the boundary that matters: what the human is *shown* must
|
||||||
/// not contain another seat's face-down selection.
|
/// not contain another seat's face-down selection.
|
||||||
///
|
///
|
||||||
|
|
@ -331,6 +419,7 @@ mod tests {
|
||||||
serve: None,
|
serve: None,
|
||||||
trial: None,
|
trial: None,
|
||||||
mode: games_ground::ScoringMode::SharedGround,
|
mode: games_ground::ScoringMode::SharedGround,
|
||||||
|
pace: table::Pace::Speed,
|
||||||
};
|
};
|
||||||
let mut out: Vec<u8> = Vec::new();
|
let mut out: Vec<u8> = Vec::new();
|
||||||
table::play(&config, "0\n".repeat(200).as_bytes(), &mut out).expect("game");
|
table::play(&config, "0\n".repeat(200).as_bytes(), &mut out).expect("game");
|
||||||
|
|
@ -395,6 +484,7 @@ mod tests {
|
||||||
serve: None,
|
serve: None,
|
||||||
trial: None,
|
trial: None,
|
||||||
mode: games_ground::ScoringMode::SharedGround,
|
mode: games_ground::ScoringMode::SharedGround,
|
||||||
|
pace: table::Pace::Speed,
|
||||||
};
|
};
|
||||||
let mut out: Vec<u8> = Vec::new();
|
let mut out: Vec<u8> = Vec::new();
|
||||||
let summary = table::play(&config, "".as_bytes(), &mut out).expect("bot game");
|
let summary = table::play(&config, "".as_bytes(), &mut out).expect("bot game");
|
||||||
|
|
@ -457,6 +547,7 @@ mod tests {
|
||||||
serve: None,
|
serve: None,
|
||||||
trial: None,
|
trial: None,
|
||||||
mode: games_ground::ScoringMode::SharedGround,
|
mode: games_ground::ScoringMode::SharedGround,
|
||||||
|
pace: table::Pace::Speed,
|
||||||
};
|
};
|
||||||
let mut out: Vec<u8> = Vec::new();
|
let mut out: Vec<u8> = Vec::new();
|
||||||
let summary = table::play(&config, "".as_bytes(), &mut out).expect("game");
|
let summary = table::play(&config, "".as_bytes(), &mut out).expect("game");
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,43 @@ pub struct Config {
|
||||||
/// patch, so two of the three shipped modes were unreachable from the
|
/// patch, so two of the three shipped modes were unreachable from the
|
||||||
/// only way anyone actually plays.
|
/// only way anyone actually plays.
|
||||||
pub mode: games_ground::ScoringMode,
|
pub mode: games_ground::ScoringMode,
|
||||||
|
/// How much ornamentation is performed (CB-WP-0036,
|
||||||
|
/// [`specs/Ornamentation.md`]).
|
||||||
|
///
|
||||||
|
/// **A separate axis from `mode`, deliberately.** `mode` is
|
||||||
|
/// `ScoringMode` — a rule of the game. `pace` is how much of what the
|
||||||
|
/// rules cannot see gets shown. Calling both of them "mode" on one
|
||||||
|
/// driver is a collision waiting to be mis-read.
|
||||||
|
pub pace: Pace,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Speed or Interactive (Ornamentation §4).
|
||||||
|
///
|
||||||
|
/// **Speed is the default and always will be.** `sim`, `trials`, the
|
||||||
|
/// benchmarks and every bot game run at it, and ornamentation must cost
|
||||||
|
/// them *nothing* — not "little".
|
||||||
|
///
|
||||||
|
/// **Pace can never select different mechanism** (Ornamentation §5, I3):
|
||||||
|
/// the same seed and the same decisions produce a byte-identical
|
||||||
|
/// recording at either pace. That is the invariant the whole
|
||||||
|
/// clay-borg / clay-animate split rests on, so it is asserted, not
|
||||||
|
/// assumed.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub enum Pace {
|
||||||
|
#[default]
|
||||||
|
Speed,
|
||||||
|
Interactive,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Pace {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, String> {
|
||||||
|
match s.to_ascii_lowercase().as_str() {
|
||||||
|
"speed" | "fast" => Ok(Pace::Speed),
|
||||||
|
"interactive" | "inter" => Ok(Pace::Interactive),
|
||||||
|
other => Err(format!("unknown --pace {other:?} (speed, interactive)")),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
|
@ -61,6 +98,7 @@ impl Default for Config {
|
||||||
serve: None,
|
serve: None,
|
||||||
trial: None,
|
trial: None,
|
||||||
mode: games_ground::ScoringMode::SharedGround,
|
mode: games_ground::ScoringMode::SharedGround,
|
||||||
|
pace: Pace::Speed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,51 @@ def check_gate_registry(root=REPO):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def check_ornament_falsifier(root=REPO):
|
||||||
|
"""Ornamentation I5 — an open declaration names what would refute it.
|
||||||
|
|
||||||
|
**A declaration is a claim that something does not matter**, and this
|
||||||
|
project's finding register is largely a list of times that claim was
|
||||||
|
wrong. One that cannot be wrong is not a claim, it is a preference
|
||||||
|
with a table row.
|
||||||
|
|
||||||
|
Presence, never adequacy -- the same split as ADR-0018 D5.
|
||||||
|
"""
|
||||||
|
out = []
|
||||||
|
reg = os.path.join(root, "specs", "OrnamentRegister.md")
|
||||||
|
if not os.path.exists(reg):
|
||||||
|
return out
|
||||||
|
with open(reg) as fh:
|
||||||
|
text = fh.read()
|
||||||
|
m = re.search(r"<!-- ornament-register:begin -->(.*?)"
|
||||||
|
r"<!-- ornament-register:end -->", text, re.S)
|
||||||
|
if not m:
|
||||||
|
return out
|
||||||
|
for line in m.group(1).splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line.startswith("|") or line.startswith("|---"):
|
||||||
|
continue
|
||||||
|
cells = [c.strip() for c in line.strip("|").split("|")]
|
||||||
|
if len(cells) != 5 or cells[0] == "id":
|
||||||
|
continue
|
||||||
|
oid, state = cells[0], cells[3]
|
||||||
|
# A refuted or withdrawn row is history; the rule binds a claim
|
||||||
|
# that is still being made.
|
||||||
|
if state != "declared":
|
||||||
|
continue
|
||||||
|
body = re.search(rf"^- \*\*{re.escape(oid)} [^\n]*(?:\n(?!- \*\*O\d).*)*",
|
||||||
|
text, re.M)
|
||||||
|
if not body or "Falsifier:" not in body.group(0):
|
||||||
|
out.append(Finding(
|
||||||
|
"ornament",
|
||||||
|
"specs/OrnamentRegister.md",
|
||||||
|
f"{oid} is declared ornamentation and names no falsifier "
|
||||||
|
f"(Ornamentation.md I5). Say what would make it mechanism. "
|
||||||
|
f"NOTE: this checks presence, not adequacy.",
|
||||||
|
))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def check_sensitivity_stated(root=REPO):
|
def check_sensitivity_stated(root=REPO):
|
||||||
"""GameDesign §1.4 / ADR-0018 — a finding whose claim is arithmetic
|
"""GameDesign §1.4 / ADR-0018 — a finding whose claim is arithmetic
|
||||||
must name the variable it depends on.
|
must name the variable it depends on.
|
||||||
|
|
@ -384,6 +429,7 @@ CHECKS = (
|
||||||
check_reporting_tools_self_test,
|
check_reporting_tools_self_test,
|
||||||
check_gate_registry,
|
check_gate_registry,
|
||||||
check_sensitivity_stated,
|
check_sensitivity_stated,
|
||||||
|
check_ornament_falsifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -473,6 +519,29 @@ def self_test():
|
||||||
check("sensitivity: a note has no measurement to be sensitive about",
|
check("sensitivity: a note has no measurement to be sensitive about",
|
||||||
not check_sensitivity_stated(tmp), "GameDesign §3.1")
|
not check_sensitivity_stated(tmp), "GameDesign §3.1")
|
||||||
|
|
||||||
|
# Ornamentation I5. Same shape: it must say NO and it must say YES.
|
||||||
|
def orn(state, prose):
|
||||||
|
body = ("<!-- ornament-register:begin -->\n\n"
|
||||||
|
"| id | ornaments | grounded | state | raised |\n"
|
||||||
|
"|---|---|---|---|---|\n"
|
||||||
|
f"| O9 | a thing | provisional | {state} | 2026-01-01 |\n"
|
||||||
|
"\n<!-- ornament-register:end -->\n\n"
|
||||||
|
f"- **O9 — a thing.** {prose}\n")
|
||||||
|
with open(os.path.join(tmp, "specs", "OrnamentRegister.md"), "w") as fh:
|
||||||
|
fh.write(body)
|
||||||
|
|
||||||
|
orn("declared", "It does not matter.")
|
||||||
|
check("ornament: a declaration with no falsifier is caught",
|
||||||
|
len(check_ornament_falsifier(tmp)) == 1,
|
||||||
|
"a claim that cannot be wrong is not a claim")
|
||||||
|
orn("declared", "It does not matter. **Falsifier:** a rule naming it.")
|
||||||
|
check("ornament: naming a falsifier clears it",
|
||||||
|
not check_ornament_falsifier(tmp),
|
||||||
|
"without this it would fire on everything")
|
||||||
|
orn("refuted", "It does not matter.")
|
||||||
|
check("ornament: a refuted row is history, not a live claim",
|
||||||
|
not check_ornament_falsifier(tmp))
|
||||||
|
|
||||||
wp("ready", ["done", "todo"])
|
wp("ready", ["done", "todo"])
|
||||||
f = check_workplan_lifecycle(tmp)
|
f = check_workplan_lifecycle(tmp)
|
||||||
check("lifecycle detects `ready` after work has started",
|
check("lifecycle detects `ready` after work has started",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
id: CB-WP-0036
|
id: CB-WP-0036
|
||||||
kind: product
|
kind: product
|
||||||
title: "Pace and ornamentation"
|
title: "Pace and ornamentation"
|
||||||
status: active
|
status: done
|
||||||
state_hub_workstream_id: "698651ed-cccd-46f2-84cc-07b0bdb85ad0"
|
state_hub_workstream_id: "698651ed-cccd-46f2-84cc-07b0bdb85ad0"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ any renderer — is the falsifier for the whole split.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: CB-WP-0036-T02
|
id: CB-WP-0036-T02
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "e978b93b-fcbd-4853-a27d-5eca00f9ca19"
|
state_hub_task_id: "e978b93b-fcbd-4853-a27d-5eca00f9ca19"
|
||||||
```
|
```
|
||||||
|
|
@ -104,11 +104,30 @@ state_hub_task_id: "e978b93b-fcbd-4853-a27d-5eca00f9ca19"
|
||||||
it is the seam `clay-animate` will attach to, and a seam is cheap while
|
it is the seam `clay-animate` will attach to, and a seam is cheap while
|
||||||
a retrofit is not.
|
a retrofit is not.
|
||||||
|
|
||||||
|
**Done 2026-08-07.** `--pace speed|interactive`, `Pace::Speed` the
|
||||||
|
`Default`.
|
||||||
|
|
||||||
|
**I3 is asserted, not intended.** `pace_cannot_change_the_game` plays the
|
||||||
|
same scripted game at both paces and compares the **serialised recording
|
||||||
|
byte for byte**, plus the end state hash. Mutation-proven: leak the pace
|
||||||
|
into the seed and it fails with *"the recording differs by pace, so a
|
||||||
|
renderer has become mechanism"*.
|
||||||
|
|
||||||
|
**A misspelt pace is refused rather than defaulting.** `--pace cinematic`
|
||||||
|
errors and names both values. Quietly falling back to Speed would look
|
||||||
|
exactly like `clay-animate` being broken, and that is the seam this flag
|
||||||
|
exists to be.
|
||||||
|
|
||||||
|
I4 needs no measurement yet and that is worth saying plainly: **nothing
|
||||||
|
reads `pace`**, so the cost to `sim`, `trials` and the benchmarks is zero
|
||||||
|
by construction rather than by benchmark. The measurement becomes real
|
||||||
|
when something reads it.
|
||||||
|
|
||||||
## Task: declarations, once there are any to make
|
## Task: declarations, once there are any to make
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: CB-WP-0036-T03
|
id: CB-WP-0036-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: low
|
priority: low
|
||||||
state_hub_task_id: "bf2a68b0-5985-40ff-b4f5-33c40d92cff0"
|
state_hub_task_id: "bf2a68b0-5985-40ff-b4f5-33c40d92cff0"
|
||||||
```
|
```
|
||||||
|
|
@ -116,12 +135,29 @@ state_hub_task_id: "bf2a68b0-5985-40ff-b4f5-33c40d92cff0"
|
||||||
Ornamentation declarations per §2.1 — what is ornamented, why it does not
|
Ornamentation declarations per §2.1 — what is ornamented, why it does not
|
||||||
bind, what would falsify it.
|
bind, what would falsify it.
|
||||||
|
|
||||||
**Not started deliberately.** GameDesign §2's rule applies: **no kind
|
**Done 2026-08-07.** [`specs/OrnamentRegister.md`](../specs/OrnamentRegister.md),
|
||||||
exists without an instance that instantiates it.** The first real
|
four declarations.
|
||||||
declarations are most likely to come from F18's unvendored files —
|
|
||||||
`Player_Mats` and `Glossary` are ornamentation-bearing data this engine
|
**This reverses the reasoning written here earlier**, which said the first
|
||||||
has never read — so **vendoring those is the trigger**, not a schema
|
declarations would come from F18's unvendored files and that vendoring was
|
||||||
designed in advance.
|
the trigger. That was wrong: **instances already existed.** Hand order is
|
||||||
|
the one that prompted the whole category — the request to drag cards
|
||||||
|
between slots — and *"who deals"* was the maintainer's own example. Waiting
|
||||||
|
for F18 would have left the register empty while its instances sat in the
|
||||||
|
conversation that created it.
|
||||||
|
|
||||||
|
The four: **O1 hand order** (provisional), **O2 who shuffles and deals**
|
||||||
|
(provisional), **O3 where a seat is drawn** (`rule`-grounded — seat
|
||||||
|
*order* is mechanism, GR-R08 rotates Lead; the drawn position is not),
|
||||||
|
**O4 typography and illustration** (provisional, and **nothing may be
|
||||||
|
declared about `Player_Mats` or `Glossary` until they are read** — F18).
|
||||||
|
|
||||||
|
**I5 is executable**: `check_ornament_falsifier` in `loop-lint` fails any
|
||||||
|
row still `declared` that names no falsifier. Mutation-proven on O1 —
|
||||||
|
strip its falsifier and the lint goes red. Three self-test controls: it
|
||||||
|
catches a bare declaration, clears one that names a falsifier, and skips
|
||||||
|
`refuted` rows as history. **Presence, never adequacy**, and the finding
|
||||||
|
text says so — the same split as ADR-0018 D5.
|
||||||
|
|
||||||
## No longer in this repo
|
## No longer in this repo
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue