Notes were written to a file and shown back nowhere — the shape trials.py's own docstring calls this project's signature failure in a new medium. The log now carries the player's comments where they were made. Position is the feature: a remark like "why did that do nothing?" is about the move above it, and collected at the bottom it is a sentence with no subject. round/step cannot order a note against the log because several commands share a step, so the server records how many commands had been played — which it knows exactly — and the page places it there. A comment must not be readable as something the game did. The log is the recorder's vocabulary; notes render in their own block, attributed to the player, quoted. The .note CSS already existed and nothing had ever used it. No column was added to the trial log. trials.py skipped any row that was not five cells, silently, so a sixth column would have made `make trials` report zero notes for every log at once. That latent defect is fixed on its own terms: a wrong column count now raises, and the walk reports the real reason rather than blaming a missing block for every failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
499d9fe3d7
commit
4ddff3b17c
8 changed files with 455 additions and 26 deletions
|
|
@ -27,6 +27,11 @@ END = "<!-- trial-log:end -->"
|
|||
# cannot expire ages into an apparent finding.
|
||||
NOTE_EXPIRY_DAYS = 30
|
||||
|
||||
# The trial-log table's columns, named once. `write_trial_log` in
|
||||
# `hotseat.rs` writes them; this reads them; a row that is not this shape
|
||||
# is an error rather than a row to skip.
|
||||
COLUMNS = ("n", "round", "step", "state_hash", "comment")
|
||||
|
||||
|
||||
def parse(text):
|
||||
"""Rows between the markers. A log without the block is an error, not
|
||||
|
|
@ -41,9 +46,21 @@ def parse(text):
|
|||
if not line.startswith("|") or line.startswith("|---"):
|
||||
continue
|
||||
cells = [c.strip() for c in line.strip("|").split("|")]
|
||||
if len(cells) != 5 or cells[0] == "n":
|
||||
if cells[0] == "n":
|
||||
continue
|
||||
rows.append(dict(zip(("n", "round", "step", "state_hash", "comment"), cells)))
|
||||
# CB-WP-0032. A wrong column count RAISES; it used to `continue`.
|
||||
#
|
||||
# The docstring above already says silently reporting zero notes
|
||||
# for a file full of them is the failure this tool exists to
|
||||
# prevent -- and a `continue` here did exactly that for every row
|
||||
# at once. Adding a sixth column to the trial log (which CB-WP-0032
|
||||
# considered, and did not do) would have emptied every existing
|
||||
# log without a word.
|
||||
if len(cells) != len(COLUMNS):
|
||||
raise ValueError(
|
||||
f"trial-log row has {len(cells)} columns, expected {len(COLUMNS)}: {line!r}"
|
||||
)
|
||||
rows.append(dict(zip(COLUMNS, cells)))
|
||||
return rows
|
||||
|
||||
|
||||
|
|
@ -53,8 +70,13 @@ def logs(root=TRIALS):
|
|||
for path in sorted(glob.glob(os.path.join(root, "*.md"))):
|
||||
try:
|
||||
rows = parse(open(path).read())
|
||||
except ValueError:
|
||||
print(f" WARN {os.path.basename(path)}: no trial-log block — not a trial log?")
|
||||
except ValueError as e:
|
||||
# The REASON, not a guess at it. This said "no trial-log block
|
||||
# -- not a trial log?" for every failure, which is now wrong
|
||||
# for two of them: a malformed row is not a missing block, and
|
||||
# telling a maintainer to check for the wrong thing costs more
|
||||
# than saying nothing.
|
||||
print(f" WARN {os.path.basename(path)}: {e}")
|
||||
continue
|
||||
recording = os.path.splitext(path)[0] + ".yaml"
|
||||
out.append((path, rows, recording if os.path.exists(recording) else None))
|
||||
|
|
@ -159,6 +181,28 @@ def self_test():
|
|||
check("a trial log parses", len(parse(good)) == 1)
|
||||
check("the header row is not a note", all(r["n"] != "n" for r in parse(good)))
|
||||
|
||||
# CB-WP-0032. A row of the wrong shape must be LOUD. This used to
|
||||
# `continue`, so a format change would have reported zero notes for
|
||||
# every log at once -- the failure the parser's own docstring names.
|
||||
six = good.replace("| 1 | 3 | Select | abc123def456 | why is SOLVE doing nothing |",
|
||||
"| 1 | 3 | Select | abc123def456 | extra | why is SOLVE doing nothing |")
|
||||
try:
|
||||
parse(six)
|
||||
check("a row with the wrong column count is an error", False)
|
||||
except ValueError as e:
|
||||
check("a row with the wrong column count is an error", "6 columns" in str(e),
|
||||
"a silent skip would empty every log at once")
|
||||
|
||||
# And the surrounding walk must report the REASON it failed.
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
with open(os.path.join(d, "2026-08-07-bad.md"), "w") as fh:
|
||||
fh.write(six)
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
logs(d)
|
||||
check("the walk names the real reason", "6 columns" in buf.getvalue(),
|
||||
"it used to blame a missing block for every failure")
|
||||
|
||||
try:
|
||||
parse("# Trial log\n\nno block here\n")
|
||||
check("a file with no block is an error, not an empty log", False)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue