Written for the other machine, which asked. Everything in it was read off the
running host rather than remembered.
Also adds docker/ci/ -- the CI image recipe and a capped runner -- because the
image existed on exactly one host and its Dockerfile was in a scratch directory
under /tmp, which was swept. That is the same shape as every other thing this
consolidation has turned up: something correct that exists in one place.
docker/ci/Dockerfile rust 1.98.1 + the apt list copied from ci.yml
docker/ci/Dockerfile.ffmpeg + ffmpeg, which sylpheed-export shells out to
docker/ci/run 6 CPUs / 7 GB / NO SWAP, named cargo volumes
The rule the runner exists to enforce: every heavy command goes in the capped
container. CARGO_BUILD_JOBS caps codegen units, not rustc's threads, not the
linker, not the test harness -- a bare host build is unbounded and has frozen
this box repeatedly.
The document also records the two agent defects the other machine will meet:
a brief change does not reach a RESUMED session, and nothing brought an agent
back to its own red PR (fixed in #24, which is itself subject to the first).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rustfmt, then clippy -D warnings across the three new crates. Mechanical,
except three decisions that are stated rather than silently allowed:
* lzx.rs gets file-scoped needless_range_loop/explicit_counter_loop allows.
Index arithmetic IS the algorithm -- LZX is defined over symbol indices,
Huffman slots and window positions, and a decompressor that is merely
idiomatic is worth nothing if it is not bit-exact.
* sylpheed-xexdb gets crate-scoped allows for needless_range_loop (nine
sites index reg[r] where r is the PowerPC register number -- the index is
the meaning), too_many_arguments and type_complexity. This code arrived
whole from a retired repository; a refactor here would be an unreviewed
edit dressed as a lint fix.
* Everything else clippy asked for is FIXED, including all 14 doc-indent
sites, the let-else, and a Prepared type alias in the binary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CONSOLIDATION.md Phase 2. Each of these existed only in a repository about to
be retired, or as an untracked file on one disk -- which, for anything that
has to survive a machine, is the same as not existing.
xex2-format.md 39 KB the XEX2 container format
xbox360-exports.{json,md} 1.1 MB 2,913 exports: xboxkrnl, xam, xbdm
ppc-instructions.{json,md} 340 KB the PowerPC instruction reference
LICENSE MIT -- this repo had none
The first two are byte-identical to xex2tractor's copies, verified with cmp,
and byte-identical again to the loose untracked copies in the project root:
three copies, zero of them tracked.
docs/reference/ is deliberately its own directory with a README that says
what it is NOT: nothing here is a finding, and no docs/re/ page should cite it
as evidence. Reference material filed beside measurements is how a borrowed
table starts getting read as a result.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`mesh_consistency_disc.rs` had the only conflict: this branch added a
`Sightings` type alias where #22 replaced the file's private `disc_root()`
with the shared `common::disc_root`. Both kept — they are unrelated edits
that happened to land in the same lines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
80 findings, not the 14 the first run showed -- clippy stops at the first
failing compilation unit, so `--keep-going` is what makes the list complete.
60 were machine-applicable (`cargo clippy --fix`). The rest by hand:
* five descending `sort_by` -> `sort_by_key(Reverse(..))`
* `chunks_exact(4)` on both sides of four zips, so the compared items stay
`[u8; 4]` rather than one array against one slice
* three `type` aliases for the census maps and the captured-quad tuple
* `&PathBuf` -> `&Path` in two disc tests
* two range loops; one of them keeps `#[allow(needless_range_loop)]` with the
reason -- the index is into a map's value, which changes each iteration
* the module doc list in `invert_capture` re-indented to markdown's rules
* `blit`'s eight arguments get `#[allow(too_many_arguments)]`, not a struct
One dead `let off = b.len();` in a `ratc` test is dropped rather than renamed.
The sibling test at :162 is the one that asserts an offset; if this one was
meant to as well, that is a test change and not a lint fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No emulator-requiring state/approved item was open this iteration (issues
#1, #3, #5 all landed and moved to state/needs-human; #25 still awaits
approval). Used the gap for corpus consistency instead of idling, per the
same "a correction that never reaches the row someone reads" failure this
project keeps naming.
1. menu-navigation-semantics.md's own Q4 table still said NEW GAME was
"not tested" and its own prose said it was "deliberately not pressed"
and hangs the emulator -- both refuted BY THIS SAME PAGE on 2026-08-28,
34 lines further down ("NEW GAME -- measured ... it is not a hang. It
opens DIFFICULTY then SELECT DATA"). The correction never propagated
backward into the table or the status line above it, so a reader
stopping at either would come away with the wrong (and already-refuted)
answer. Fixed in place, struck rather than deleted, with the actual
destination and an honest note that DIFFICULTY has no id-table name
match (already refuted separately) while SELECT DATA plausibly matches
GP_SELECT_STORAGE as a fresh, low-confidence guess.
2. Found the identical failure mode in my own recent work: HANDOFF.md's
original Q1-Q10 summary table (near the top of a 6600+ line file) still
quoted the 2026-08-30 "no auto-repeat" finding as current, three commits
after this same session measured 12 frames delay / 4 frames interval
through a repeat-capable driver and explained why the earlier negative
was a driver limitation, not a game fact. A reader who only sees the
summary table -- which is exactly what a long file trains a reader to
rely on -- would get the withdrawn answer. Fixed with an explicit note
pointing at the current entries rather than silently editing the number
in place, so the correction itself stays visible.
Refutation-shaped either way: two claims ("NEW GAME untested", "no
auto-repeat") checked against this corpus's own newer evidence and found
not to survive, recorded rather than left to be rediscovered.
Continues the static lead from two iterations ago
(f3-title-sting-mechanism-found-not-value.md) with the dynamic half it
named as the next step. Booted with --xma_param_probe=true (the same
census menu-audio-cues.md used for the menu's SE cues), no pad input,
recording continuously from window-open: a glyph time series (not a
threshold trigger) and every newly-seen XMA-PARAM stream, stamped on
arrival since Xenia's own log carries no timestamps.
Positive control, and a real one: the probe caught the title's two BGM
stems starting at t=147.6s, matching f3-title-plays-bgm-102-and-103.md's
already-established finding exactly -- the instrument finds a real stream
before being asked to find nothing.
From the plate's first visible activity through 68 seconds of build-in
plus fully-settled pulsing (killed at t=220.1s), zero new XMA streams
appeared beyond the two BGM stems and three unidentified early ones (boot
splash, not this question). No SE-range stream, no second BGM, nothing --
measured, not the prior static reach limit.
Refutation attempt this iteration, recorded either way: my first read of
the fine-grained glyph series said BGM and the plate's build-in start at
"essentially the same moment". Checking the raw per-sample data instead of
a coarse table refutes that -- first non-zero glyph reading is ~0.67s after
BGM onset, and immediately noisy rather than a clean climb. Corrected in
the doc rather than left as an overclaim for someone chasing frame-accurate
sync later.
Reference data: docs/re/data/f3-sting-{glyph-timeseries,xma-param-arrivals}.tsv
-- derived numeric/log-line data, not a capture of rendered game content.
Direct follow-through on this session's own named next step. The prior
result (f1-held-down-measured-no-repeat-via-file-driver.md) concluded the
file driver cannot show menu repeat because its GetKeystroke() never emits
a REPEAT-flagged event, and that the menu's repeat is very likely driven by
that flag rather than raw polled state. Testable, so tested: patched
/canary/src/xenia/hid/file/file_input_driver.h to add opt-in repeat behind
a new --pad_file_repeat cvar (off by default, every other scripted script
unaffected), using the SDL driver's own constants verbatim
(HID_SDL_REPEAT_DELAY/_RATE = 400/100, guest-time ms via
Clock::QueryGuestUptimeMillis) rather than re-deriving them. Incremental
rebuild, ~1 minute (only xenia_main.cc needed recompiling).
Control: the driver's own log confirms repeated keystroke events fire as
designed, zero crashes.
Result: re-ran the identical held-DOWN capture. The cursor that moved once
and stopped in the null result now cycles continuously through the whole
5-item menu, wrapping, for as long as the button is held -- the null result
was real for that driver path, and giving the driver the one thing it
lacked reverses it completely.
Measured at this run's achieved 29.87 fps guest rate: 12 frames (~402ms)
initial delay from the press-triggered step to the first repeat step; 4
frames (~133ms) steady-state interval for 13 of 15 gaps, 3 frames (~100ms)
for the other 2 -- slower than the raw 100ms constant driving it, which
this page flags but does not trace further (most likely the game batches
drained keystrokes per its own frame tick rather than reacting to each one
instantly). The 4-frame figure is what matters for the port: it's what the
cursor visibly does.
Honestly scoped: this measures what the game does when FED repeat events
shaped like the SDL driver's, not a capture through an actual physical
controller (none exists in this container) -- classified measured, not
decoded, for exactly that reason. One run only; the corpus's two-run
minimum isn't met, flagged rather than overclaimed.
f1_hold_capture.py gains an optional `repeat` argument. The Canary source
patch itself lives in /canary, outside this repo (Canary source, not
sylpheed-formats) -- fully described inline in the finding doc so it can be
reapplied if that tree doesn't persist across a container reset.
Reference data: docs/re/data/f1-repeat-cursor-transitions.tsv -- every
transition's frame, guest tick and Y position, not the raw draw log.
Fifth boot attempt this session, first clean one, using the harness debugged
last iteration (env-safe input, wide capture window, signed-in profile,
blanked X root). Reached the menu, held DOWN 2.5s wall-clock, armed the F10
draw capture, released, killed -- zero crashes.
Achieved vs requested: 433 of 600 requested frames, spanning 14.43s of GUEST
time against ~4.5s wall-clock for the hold+tail window -- this run went at
~3.2x real time (a cheap static menu, nothing pacing it to a display
refresh). The 2.5s wall-clock hold covers roughly 8s of that guest time.
Method: track one quad shape's position per frame via read_draws.py, not a
screen diff -- the approach f1-menu-repeat-harness-built-not-answered.md
already named as correct. Exactly one shape-group moves at all: one jump of
~0.21-0.225 NDC units (matching the known one-menu-item pitch), complete
within 4 frames / 133ms guest time of arming, then flat (a ~0.0125 pulse
wobble only) for the remaining 427 frames / 14.3s of guest time. Broadening
the match threshold finds no other moving element.
This is a STRONGER negative than the 2026-08-30 result it agrees with (that
one sampled a screen diff at ~4-5fps for 2.0s; this reads every quad every
frame for ~8s of guest-time held) -- it survives, rather than confirms, last
iteration's "coarse sampling hid a fast repeat" alternative, which does not
survive a per-frame instrument over a much longer window. Refutation
attempt against my own prior lean, recorded: it does not survive.
Reconciled, not left in conflict: the human's play-test and pad.py's
"auto-repeats" warning almost certainly went through a REAL controller
(Canary's SDL driver), which auto-repeats keystrokes at a documented
upstream 400ms/100ms (guest time) via the REPEAT keystroke flag. The file
driver deliberately never emits that flag, by design -- so it cannot show
repeat regardless of how carefully or how long it's held, which is exactly
the negative measured here. Still no F1 number; now a specific, well-
evidenced reason the file driver alone will never produce one, and a named
next step (add opt-in REPEAT-flag support to the file driver, matching the
SDL driver's constants, then re-run this exact capture) -- not attempted
this iteration, a second unit on an already-complete one.
Reference data: docs/re/data/f1-cursor-quad-y-per-frame.tsv -- the derived
per-frame Y position and guest tick, not the raw draw log itself (a capture
artifact, kept uncommitted per the corpus's game-content rule).
Tried to close out issue #1 with an actual draw-log measurement this
iteration: built f1_hold_capture.py to boot to the settled main menu via the
proven glyph-gated route, arm the F10 UI-draw capture, hold a direction, and
read cursor position per frame -- the instrument f1-menu-repeat-harness-
built-not-answered.md already validated but never got to run against a
reachable menu.
Four bugs found across four boot attempts:
1. tap() shelled out to pad.py without this script's own env, so the press
went to /tmp/xenia_pad.txt while Canary watched OUT/pad.txt -- an
unobserved press indistinguishable from a dead pad. Fixed with an
in-process tap() using the same pad() the hold uses; confirmed working
the next run (title 154.5s, menu 163.0s).
2. ui_draw_capture_frames/max were persisted at 3/20000 from a prior
session in xenia-canary.config.toml -- log_ui_draws is now a documented
no-op (F10 arms unconditionally) and these two cvars didn't visibly
respond to command-line overrides. Bumped to 600/400000 directly in the
config.
3. The real blocker: this container has no signed-in profile (no content/
directory at all -- a fresh container after a restart, which every
container is right after one). Without a profile the title's sign-in
dialog sets IsUIActive() true, which reproduces
structures/title-a-press-fault.md's already-diagnosed unbounded-
keystroke-queue crash -- verified byte-for-byte against that page's own
addresses (PC 0x868 past sub_82457038, 0x828F3xxx registers, identical
host/guest address arithmetic), looping continuously from before F10 was
ever pressed. Fixed by creating a profile (--create_profile_if_none) and
signing in (--logged_profile_slot_0_xuid), matching boot_menu.sh, which
already did this and so never hit it. Confirmed: zero crashes with the
fix, dozens per run without it.
4. Found but not re-verified: no xsetroot blank before launch, so a stale
X-root frame from a killed prior run gave a false "TITLE" read at 2.6s,
before any real window existed -- skip_intro.sh already blanks the root
for exactly this reason. Fixed in the script.
Ran out of budget before a clean end-to-end run landed. Still no number for
issue #1 -- the Port keeps -1.0. Flagged prominently (HANDOFF, REFUTED.md)
because bug 3 will hit any bare run-canary invocation in any fresh
container, not just this script.
Continuing the F1 investigation rather than starting a fresh one. Last
iteration left two competing hypotheses open (Keystroke-queue-driven vs
polled-state-driven repeat) and flagged C_PAD_RINGBUF's producer as the
cheapest thing to trace next -- named but not traced.
Traced it this time: C_PAD_DECODER's own constructor (sub_8220B610)
allocates C_PAD_RINGBUF (52-byte control struct, 1024-byte backing buffer,
confirmed against its own Shift-JIS trace strings -- "C_PAD_RINGBUF
initialization" and its allocation-error message). Its update function
(sub_8220B8C0) takes the input-manager singleton as a parameter and reads
the ring at offsets 12, 36, 40, 44 and 48 -- not just the one button word.
Offsets 36-48 are four consecutive fields read together through the same
int-to-double conversion an analog axis would use. XamInputGetKeystrokeEx
has no field for a stick position, so a structure carrying four axis-shaped
fields cannot be a keystroke queue -- it reads as a periodically-refreshed
polled-state snapshot. My own prior reading of the "ring buffer" name as
implying a queue was the wrong inference; refuted by tracing it, recorded
either way per adversarial duty.
This shifts the balance toward the second, previously-uncertain hypothesis:
the file driver's GetState() was always capable of showing real repeat (no
modification needed), and nav_repeat_and_b.py's null result is more likely
a sampling artifact of its ~4-5 fps screen-diff detector than a structural
driver limit. Revises "what would close it" accordingly -- re-run the
existing draw-log position-tracking instrument, gated on the menu properly,
before reaching for a driver change.
Not found: the actual producer that writes into C_PAD_RINGBUF each frame --
narrowed to "reachable from the input-manager singleton fetch in
sub_821A9DC8," not traced to completion. Still no number for issue #1;
this narrows the path to one, further than last iteration but not there.
Issue #1's premise (the human's play-test: a held direction repeats) directly
contradicted menu-navigation-semantics.md's 2026-08-30 "no auto-repeat"
row. Read the instrument before trusting either: nav_repeat_and_b.py drove
input through Canary's --hid=file driver, and that driver's GetKeystroke()
is explicitly, deliberately built to deliver exactly one event per held
press ("scripted input wants precisely one event per press, and repeat is
what makes menu steps overshoot" -- file_input_driver.h's own comment).
input-pad-read-path.md already established the game reads menu input via
this same Keystroke API. A driver engineered to prevent repeat cannot be
evidence the game lacks it -- the counter's control (a tap gives 1 spike)
proved the counter works, not that the driver could show more than one.
Not a clean reversal, and said so: the same driver's GetState() holds a
button continuously with no edge suppression, and pad.py's own docstring --
written by an earlier session driving this exact tool -- warns that a longer
dpad hold "auto-repeats and overshoots," describing an observed effect
through this same driver. The two pieces of evidence disagree and this page
does not resolve which wins.
Also read from Canary's source: the SDL input driver (what a real controller
goes through) auto-repeats keystrokes at 400 ms initial delay then 100 ms
interval, guest time (HID_SDL_REPEAT_DELAY/RATE, upstream Xenia, not a
project change) -- a concrete, testable prediction for what the real number
could be if the menu treats repeat-flagged keystrokes as nav steps, matching
the human's "medium pace" description. Not yet measured.
Refutation attempt this iteration, recorded per adversarial duty: targeted
the 2026-08-30 "no auto-repeat, ✅ measured" claim. Survives only partially --
demoted to unsettled, not flipped to a confident opposite. New REFUTED.md
section (Menu navigation and input) and the row in
menu-navigation-semantics.md both corrected in place, old text kept per
convention.
What would close it: trace C_PAD_RINGBUF's producer (keystroke ring vs
polled state) statically, or add an opt-in repeat mode to the file driver
and read cursor position off the draw log per frame. Neither run this
iteration -- this is the static half, and reversing a standing claim is
enough for one unit without stacking a build-and-boot run on top of it
unverified.
f3-title-plays-bgm-102-and-103.md's BGM census resolved a literal
`addi r5,r0,<imm>` before every call to the play primitive, which is blind by
construction to a cue id supplied any other way. Two of the primitive's 34
callers ARE supplied another way -- one is a register passthrough inside a
generic 6-caller wrapper `sub_821CCCB0(obj, cueId)` with a -1 "don't play"
sentinel. One of its six callers chains back to slot 1 of GamePart_Title's
OWN dispatch table at 0x820a3dec, identified by its adjacent
RegisterToFactory<0, class silph::GamePart_Title> string -- the same
convention already used disc-wide for the other 28 GameParts, not a
neighbourhood guess this time.
Traced as far as static analysis goes: the field that would carry the cue id
traces to a 19-caller shared helper's return value, too common to be
title-specific -- reads as "allocate a sound-emitter handle", not "here is
the cue". The value most likely gets set at whatever runtime moment the game
wants this emitter to speak, which a disassembly listing does not contain.
Classified undecodable-with-reach, not a fourth thing -- narrower and
better-aimed than the prior reach ("SE goes through a different call").
Every address cited was independently re-decoded from the raw .pe bytes
(file offset = VA - 0x82000000), not taken from the database's own
mnemonic/operands columns, and all agree.
Refutation attempt this iteration: checked whether the "6 callers is
exhaustive" claim for sub_821CCCB0 survives indirect dispatch (a plain
kind='call' xref scan is blind to a vtable/function-pointer-array entry).
Checked function_pointer_array_entries and kind='ind_call' xrefs for all
four functions in the chain -- none found. The claim survives.
What would close it: --xma_param_probe=true during a title boot's build-in,
watching for a newly-decoded stream when the plate reaches full alpha with
no input -- the same technique menu-audio-cues.md used for the menu's SE
census. Not run this iteration; this is the static half only.
f6-out-of-sample-RESULT.md left three failures unexplained beyond "n=2 wasn't
enough". Reviewing what's already decoded: two of the three (the ptcopyright/
parent ramp ratio, the sweep-leads-plate lead) fail in the SAME direction
(0.75x, 0.71x of predicted), and f6-unit10 already established the sweep and
plate families are gated by separately-triggered parents -- a ratio across
two independently-triggered elements has no structural guarantee of being a
constant, unlike a ratio internal to one element family (which is exactly
what the passing three checks are). The gross mislabeling that caused the
ORIGINAL 1.7x conflict was fixed two days before the prereg was written, so
that's ruled out as the cause here; whether the cross-group phase genuinely
varies boot to boot vs. an artifact in the frame-based ratio math is still
open, and needs more captures to tell apart -- filed as a follow-on, not run
here.
check_labels.py conflated two different claims under one "N LABEL(S) DRIFTED"
verdict: identity checks (which element -- clock-free, still 3-for-3 out of
sample) and timing checks (a cross-element ratio and a self-consistency curve
fit -- 0-for-2 out of sample). Split into two reported groups; only identity
gates the exit code now. Tolerances untouched -- widening them to pass f6c
would be tuning the check on the case that failed it, the same error class
already named twice in this corpus. Verified with synthetic data shaped like
the real f6c residue (3/3 identity, 0/2 timing, exit 0) and confirmed the
selftest's injected mislabel still fails an identity check (exit 1) -- no
capture exists in this fresh container to run it against real logs.
The withdrawn sweep->plate lead (0.138-0.141) stays withdrawn. Nothing here
reinstates a number.
`cargo build -p sylpheed-cli` after resolving the merge's one real conflict
(a rustfmt-only clash) was not "the merge is resolved" -- it only checked the
crate whose conflict I'd just looked at. `cargo check --workspace --all-targets`
finds the actual break: sylpheed-viewer's `compose_screen` builds a
`ComposeOptions` struct literal, and this branch added a field
(`at: Option<u32>`, for posing a composite mid-animation) that main's Explorer
code never had to name because every prior caller used `..Default::default()`
or the field didn't exist yet. Neither side's diff touched the other's line,
so nothing in the conflict pointed at it.
`at: None` is the only correct value here -- the doc comment on the field
already says so ("`None` keeps the settled composite, which is what every
existing caller wants"), and the Explorer's screen view has no notion of
mid-animation posing to wire up. This restores its exact prior behavior; nothing
about the Explorer's data source, static-only rule, or design changes.
PR #23 is red on this. Fixes it in place rather than opening a second PR, since
it is the same unit -- a merge I performed that did not actually leave a
building tree.
Two gaps the first post-migration decoder iteration exposed, both in the same
place — the loop ends before it can observe its own result.
1. Step 1 read notifications but never looked at the agent's own open PRs. It
opened #23, labelled the issue `state/needs-human`, and stopped; nothing in
the brief would ever bring it back to a red check. Next iteration it planned
to start a new question on top.
2. It called the merge resolved on `cargo build -p sylpheed-cli` — the crate the
conflict was in. The break was in `sylpheed-viewer`, which neither side of
the merge edited: one branch added a `ComposeOptions` field, the other had a
literal construction of it. The crate you just fixed is the one place the
breakage cannot be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GITEA-SETUP.md listed `propose-work` as unbuilt, and this PR builds it without
touching that list, so merging would leave the doc describing a tool as missing
that exists. Strike it the way `gitea-verify` already is: say what was built,
then say what is still missing — here, an end-to-end run, which only the agent
box can do because only it holds a token that can make the POSTs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Before this commit, `unset SYLPHEED_DISC` did not disable the disc-backed
suites on the machine that has the disc: every `disc_root()` fell back to a
hardcoded absolute path that exists on this box. The env var looked like a
control and was not one. Same for $SYLPHEED_RES3D and $SYLPHEED_ISO.
Replace the duplicated resolvers with one `tests/common/mod.rs`:
- 17 local `disc_root()` definitions -> 1
- 7 copies of the skip macro -> 1 (`skip_without_disc!` and siblings)
- 16 hardcoded absolute paths -> 0 executable ones
(3 of those were inline in `mesh_disc.rs`, in no resolver at all,
and 2 were in `examples/`)
- `corpus_report.rs` now reports on the SAME resolver the suites use,
instead of a second copy of the logic its own comments flagged as a
drift risk.
The 17 copies had already drifted into FIVE variants, and they were not all
the same function. `movie_manifest_disc`, `movie_subtitle_disc` and `slb_disc`
honoured $SYLPHEED_DISC and nothing else, while the other 14 fell back. So one
name already meant two things -- a third instance of the shape #16 is about.
The shared helper adopts the env-only behaviour those three already had, rather
than inventing a sixth variant.
Two module docs still described the fallback after it was deleted, which is the
same defect in prose: `texture_disc` claimed "or the default dev path exists"
and `pak_idxd_disc` said "or drop it at the default dev path below". Both now
say what the code does.
Verified both ways on the machine that HAS the corpus, which is the only place
this refactor can be falsified:
A env unset -> "ABSENT -- $SYLPHEED_DISC unset; its suites self-skip"
suites=31 passed=209 failed=0 ignored=14, slowest 0.12s
B env set -> "PRESENT via $SYLPHEED_DISC" (all three corpora)
suites=31 passed=209 failed=0 ignored=14,
slowest 1235.53s (mesh_consistency_disc)
Identical tallies, opposite corpus states, ~10000x apart in wall clock. (A) is
new behaviour -- it was previously unreachable here. (B) proves nothing broke.
`just test-disc` sources `.env` (already gitignored) for the set case. Note the
quoting trap documented there: the corpus paths contain spaces, and an unquoted
`VAR=a b c` parses as "run command `b`", failing silently into ABSENT -- which
looks exactly like a working skip.
Remedy (1) (`#[ignore]` + `--ignored`) is deliberately NOT done here: (3) already
moves the mode from the filesystem into the environment, and `#[ignore]` already
carries three meanings in this directory (corpus-absent, known-failing, bare).
Overloading it a fourth time would re-create the defect.
`cargo fmt --all --check` clean; no new compiler warnings.
Refs #16
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GITEA-SETUP.md has listed this under "Still to build" since 2026-09-04:
`propose-work`, superseding `push-work` -- push the branch AND open the PR
with `Closes #N` AND set the label, in one step. Today `push-work` does the
first third; the other two thirds being manual is how they get skipped.
PROTOCOL.md 145-149 requires all three:
* branch `auto/<agent>/<issue#>-<topic>`, one item per branch
* open the PR with `Closes #<issue>` in the body
* label the issue `state/needs-human` and say, in one line, what to look at
Until now those were three things to remember, and PR #20 had to add a warning
to `port-loop.md` about the two that get forgotten. A rule enforced by memory
decays; this makes the sequence structural.
WHAT IT DOES NOT DO is reimplement push-work's refusals -- it CALLS push-work,
so `main`, shared branches and force-push stay refused in exactly one place.
Duplicating them would let the copies drift, and the copy that drifts is the
one that matters.
Three design choices worth stating:
* THE ISSUE NUMBER IS DERIVED FROM THE BRANCH NAME, which PROTOCOL already
specifies as `auto/<agent>/<issue#>-<topic>`. So a PR cannot cite a
different issue than the branch was cut for -- a mismatch no reviewer would
catch. `-i` overrides.
* `-m` IS MANDATORY. PROTOCOL says an issue in `state/needs-human` must say
what to look at and what pass and fail look like, "so a person can judge it
in under a minute". Refusing without that line is cheaper than letting the
label carry an empty promise and costing a human a round trip.
* IT MOVES THE LABEL RATHER THAN ADDING IT -- other `state/*` labels are
removed. Leaving `state/in-progress` attached makes the board lie about
what is waiting on a person.
The token is read from a file and handed to curl through a `--config` document
on stdin: never an argument, never exported. Arguments are world-readable in
/proc and this token can push. Verified that the mechanism actually delivers
the header rather than silently dropping it -- with a bogus token the API
answers "invalid username, password or token", while the same URL with no
header returns the list anonymously, so the header is demonstrably being read.
Also verified: issue derived from the branch (#42 from
`auto/decoder/42-widget-census`), refusal without `-m`, refusal when the branch
carries no number, `--dry-run` sends nothing, and repo/API derivation from the
remote. `--dry-run` deliberately does NOT require a credential -- it exists so
an agent can check the command it is about to run, and demanding a token it
never sends would make the check unavailable exactly where it is cheapest.
Identical in both agents' bin/ on purpose: the agent name comes from the
branch, so there is nothing per-agent to diverge.
UNVERIFIED, and stated as such: no end-to-end run. That needs a real token and
a real issue, which this desktop does not have -- it is the second machine, and
the agent credentials live on the agent box. Everything above the network call
is exercised; the POSTs are not.
Refs #11
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`decoder-loop.md` has had `## Each iteration` since 2026-09-04, ending in
7. Commit, `push-work`, open the PR, label the issue `state/needs-human`,
and stop.
`port-loop.md` has never had that section at all. It states the rules -- "you
cannot close your own work", "you move an item to state/needs-human", work
reaches main "through pull requests" -- but nowhere gives the STEPS that carry
them out. The Port agent was told the rule and never the procedure.
Two steps were missing entirely, and both are Phase 7 check items:
* READ YOUR NOTIFICATIONS first. They are polled and nothing pushes them, so
an agent that skips this never receives anything addressed to it -- and the
symptom is silence, not an error. That is GITEA-SETUP.md Phase 7 check 1.
* OPEN THE PR AND SET THE LABEL. That is Phase 7 check 2.
The second carries a trap worth naming in the brief rather than in a runbook:
`docker/port/bin/push-work` ONLY PUSHES. Verified by reading it -- no /pulls
call, no `Closes #`, no label; its refusals (no main, no force-push) are
intact. `propose-work`, which would do all three, is still unbuilt --
GITEA-SETUP.md's own words are "the other two thirds being manual is how they
get skipped". The capability is there (`pull_request_write` and `issue_write`
are both in the pinned MCP tool set), so this is an instruction gap, not a
permissions one.
Written to mirror the Decoder's shape while using the Port's own vocabulary --
a gate is an artifact, verify against captures rather than our renderer, state
the expected number first, `authored/` entries carry a `why`, never an invented
number where a measured one is pending.
Found by the Pi agent while answering a Phase 7 readiness question. My own
check had been decoder-only, which is why the asymmetry survived it.
Refs #11
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`4057bee` claimed the report prints
SYLPHEED_DISC PRESENT via the HARDCODED fallback, NOT $SYLPHEED_DISC
and it did not. The string was built by `"...(not ${env})".replace("${env}",
env)` -- a placeholder substituted at runtime, which printed
`(not SYLPHEED_DISC)` and dropped the `$`. I had cleaned this up before
committing, but the edit silently no-op'd (rustfmt had already rewrapped the
line, so the pattern no longer matched) and the commit went ahead with the
message quoting output the code does not produce.
Now a plain `format!`, which is what it should have been: shorter, no
placeholder to keep in sync, and the `$` makes it read as the env var it is.
The quoted output in `4057bee` is accurate as of this commit.
Not squashed into it because force-push is denied on this remote, and a
visible correction is worth more than a rewritten history anyway.
Verified: `cargo fmt --all -- --check` clean, both tests pass, output matches
the text above exactly.
Refs #16
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`cargo test --workspace` reports the same tally whether the disc corpus was
exercised or entirely absent. Measured: the disc suites RAN on a developer
desktop (1936 s, mesh_consistency_disc alone 1220 s) and SKIPPED on CI
(2.4 s total) -- and both reported 207 passed / 0 failed / 14 ignored across
30 suites.
Two mechanisms compound:
* a skip is a PASSING test. The gated suites `eprintln!("SKIP: ...")` and
return early from a test that still passes, so a skipped suite and a
fully exercised one both score 1 passed. The totals are invariant.
* the message is invisible. `cargo test` captures a passing test's output,
so NEITHER log contains a `SKIP:` line. The absence of one proves
nothing, which makes the obvious check useless too.
And `14 ignored` cannot help: `#[ignore]` is static, so that column is the
literal count of attributes in the source and cannot move at runtime. Ask
what this check would still report if the corpus were entirely absent, and
the answer is 207/0/14.
This is #16's remedy (2) -- the only one that touches the REPORT, which is
the defect. Remedies (1) and (3) improve the control and are left open.
Adds `tests/corpus_report.rs`: always runs, never fails, resolves all three
corpora exactly as the per-suite helpers do, and records what was available.
It writes to a FILE rather than relying on stdout, because a passing test's
stdout is captured and would be invisible in exactly the CI log that needs
it; the workflow then prints that file. It also appends to
GITHUB_STEP_SUMMARY when set.
Run here, it immediately shows the thing the issue is about -- all three
corpora resolve through the HARDCODED fallback, so SYLPHEED_DISC is not
controlling anything on this machine:
SYLPHEED_DISC PRESENT via the HARDCODED fallback, NOT $SYLPHEED_DISC
SYLPHEED_RES3D PRESENT via the HARDCODED fallback, NOT $SYLPHEED_RES3D
SYLPHEED_ISO PRESENT via the HARDCODED fallback, NOT $SYLPHEED_ISO
The ABSENT branch is the one CI takes and cannot be reached on a machine
that has the corpora, so `resolve_renders_every_branch` exercises it
directly rather than shipping it unrun -- along with "set but does not
resolve", which is what a typo in the env var produces and which is
deliberately reported as a DIFFERENT state from absent, since the two want
different fixes.
Verified: `cargo fmt --all -- --check` clean. Clippy is unchanged by this
(a test target; CI's `cargo clippy --workspace` does not build test cfg) --
it fails identically on unmodified main here with
`only_used_in_recursion` at vfs.rs:85, which is the rustc 1.90.0 vs the
runner's 1.98.1 divergence, i.e. #15, not this.
Refs #16
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`cargo fmt --all -- --check` has failed on every run in this repository's
history, identically on `main` and on every branch. This is #12.
Mechanical: `cargo fmt --all`, nothing else. 154 files, all `.rs`, no other
extension touched. `cargo check --workspace` exits 0 afterwards, so nothing
changed semantically.
ON THE ORDERING, WHICH WAS THE REAL QUESTION.
HANDOFF-2026-09-06 section 7 warns this is the expensive fix: a whole-tree
reformat before #7 and #8 return "would put a conflict in every file of 861
commits and make the reviews those items exist to enable unreadable".
That is measurably too pessimistic, and it had been reasoned rather than
tested. Measured here by three-way merging a rustfmt'd `main` against both
unmerged branches, file by file:
file/branch pairs tested 32
merges CLEAN 28
merges CONFLICTING 4 (8 conflict hunks total)
sylpheed-cli/src/main.rs 1 hunk
sylpheed-export/src/check.rs 1
sylpheed-export/src/screen.rs 4
sylpheed-export/src/video.rs 2
All four are against `auto/frame-blend-draw-path` only;
`auto/port-p6-audio` does not conflict anywhere. The earlier framing --
154 dirty files, 133 that cannot collide, 21 that can, the collision set
carrying 147 of 774 hunks (19%) -- reproduces exactly. What it did not say
is that most of the 21 still merge cleanly, because rustfmt's edits and the
branches' edits rarely land on the same lines.
So the cost of sweeping now is 4 files and 8 hunks for one branch, against
a check that is otherwise red forever. Deliberately NOT folded into the
WASM PR: 154 reformatted files would make that one unreviewable.
Closes#12
With the build fixed, `trunk build --release` succeeds and the job then
died on its last step:
::error:: @actions/artifact v2.0.0+, upload-artifact@v4+ and
download-artifact@v4+ are not currently supported on GHES.
Gitea presents as GHES and the action aborts by design -- go-gitea/gitea
#31256 and #36024. Same class as the trunk-action bug this branch already
fixes: an action written for GitHub's hosted fleet, on a runner that is not
it.
Dropped rather than downgraded, because nothing consumes the artifact.
`web-dist` had exactly one reference in the repository -- the line that
produced it. There is no `download-artifact`, no deploy step, and no second
workflow. It was failing the job for no benefit.
The two ways to keep it are recorded in the file rather than here, because
choosing between them needs a fact this run does not provide (the runner's
node version) or a supply-chain decision (a third-party action).
What now runs end to end on aarch64, from scratch:
Install Trunk ok (v0.5.1, the arch fix, on the real runner)
cargo check wasm32 ok
trunk build --release ok 10m 56s, 3 files in dist/
Refs #11
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`trunk build --release` reached the asset pipeline for the first time and
failed there:
found more than one target artifact: ["sylpheed_viewer", "sylpheed-viewer"]
The crate declares both a [[bin]] `sylpheed-viewer` (src/main.rs) and a
[lib] `sylpheed_viewer` cdylib (src/lib.rs), and the index.html link named
neither, so trunk refused to guess.
Trunk's error offers two ways out and THEY ARE NOT EQUIVALENT. Measured,
both exiting 0:
data-target-name="sylpheed_viewer" 1_478 bytes, 1 app symbol
data-bin="sylpheed-viewer" 21_298_268 bytes, 2_998 app symbols
Selecting the lib "succeeds" while linking nothing, because there is no
wasm entry point in it -- no wasm-bindgen dependency, no import, no
`#[wasm_bindgen(start)]`. The linker drops the whole app and trunk emits an
empty module. That would have turned this job GREEN on a bundle that cannot
start, which is worse than the red it replaced.
`main()` is a valid wasm entry: it calls `sylpheed_viewer::run()` and its
only native-specific code is already `#[cfg(not(target_arch = "wasm32"))]`.
With the bin selected, trunk injects a real init -- `import init`, an
integrity-checked module preload, `__wbindgen_start`, and the
`TrunkApplicationStarted` event.
The lib.rs docs claimed this file was the WASM entry point "called from
`wasm_bindgen` init on the web". Nothing ever called it. That comment is
what made the lib look like the right target, so it is corrected here
rather than left to mislead the next reader.
Verified locally with trunk 0.21.7 on x86_64. The exit code does not
distinguish these two cases -- only the artifact does.
Refs #11
`Install Trunk` has never run to completion here, because the two steps
before it always failed first. With those cleared it becomes reachable,
and on this runner v0.5.0 would fetch the wrong binary.
v0.5.0 switches on PLATFORM alone and never consults the architecture:
case 'linux': arch = 'x86_64-unknown-linux-gnu'; break;
v0.5.1 reads it and maps it, failing loudly rather than wrongly:
const arch = process.env['ARCH'] || process.arch;
case 'x64': targetArch = 'x86_64'; break;
case 'arm64': targetArch = 'aarch64'; break;
default: core.setFailed(`Unsupported architecture: ${arch}`); return;
Node reports `arm64` here, so it resolves to `aarch64`, and upstream does
publish trunk-aarch64-unknown-linux-gnu.tar.gz. There is also an `ARCH`
env override if the mapping is ever wrong.
Read out of the two bundled dist/index.js files, not the release notes.
Counts moved as predicted: `x86_64-unknown-linux-gnu` 1 -> 0, `aarch64`
0 -> 1, `os.arch()` 5 -> 5. Confirmed independently on both machines.
Two further changes the bump carries, neither of them about architecture:
* the download host moves thedodd/trunk -> trunk-rs/trunk. Trunk moved
repositories and v0.5.0 still points at the old one -- arguably the
more durable half of the fix.
* `io.mv` 1 -> 0 and `io.cp` 2 -> 3. A cross-filesystem move throws
EXDEV; a copy does not. This is the fix for self-hosted runners whose
/tmp is a separate filesystem, which is ours. NOTE: the string EXDEV
appears zero times in either bundle, so this cannot be found by
grepping for the error it prevents -- it is visible only as the
primitive swap.
STILL UNVERIFIED: whether `trunk build --release` then succeeds. Everything
above concerns selecting and fetching the binary. The step after it has
never run in this repository's history, on any architecture, so there is no
basis to predict it. Expect to read that log fresh.
Refs #11
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With the `mio` subtree gone (previous commit), two blockers remain. Each
was invisible until the one before it was cleared, which is why #11 was
written around only the first.
1. getrandom 0.3 refuses wasm32-unknown-unknown without being told which
backend to use. It needs `--cfg getrandom_backend="wasm_js"` AND the
crate's `wasm_js` feature; its own error is explicit that either alone
is insufficient. Nothing here depends on getrandom directly -- it
arrives through `ahash`, in `sylpheed-viewer` only -- so the feature
half is declared there purely to switch it on.
2. error: bevy_egui uses unstable APIs to support clipboard on web.
Needs `--cfg web_sys_unstable_apis`.
Both cfgs live in /.cargo/config.toml scoped to the wasm target, so native
builds are untouched.
Verified on aarch64 / rustc 1.98.1 -- the runner's toolchain -- from
scratch with the cache cleared: exit 0 in 163s. Independently reproduced
on x86_64 / rustc 1.90.0 as a controlled A/B against the parent, both
running the job's exact invocation:
with this branch exit 0, zero errors, 38s
same command at 885b4d4 exit 101, the getrandom error
The control also shows `bevy_egui` is never reached when getrandom fails,
and the passing case contains no `mio` and no `tokio` in the wasm graph at
all -- so each blocker is confirmed separately rather than by the
aggregate exit code.
This does NOT make the WASM job green, and the next failure is already
identified rather than left to be discovered. `Install Trunk` uses
`jetli/trunk-action@v0.5.0`, whose bundled `dist/index.js` contains the
string `x86_64-unknown-linux-gnu` exactly once and `aarch64` not at all,
while calling `os.arch()` five times with no mapping for it. On this
aarch64 runner it will fetch an x86_64 binary. Upstream trunk does ship
`trunk-aarch64-unknown-linux-gnu.tar.gz`, so the asset exists and only the
action's selection is wrong -- but replacing the install step means
picking a version to pin and an install method, which is a decision, not a
fix. Left for #11 to decide.
Refs #11
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`sylpheed-formats` declared `tokio` as a normal dependency and never used
it as one. All four references in its `src/` are inside a `mod tests` --
three runtime builders in ship.rs (539, 566, 690; `mod tests` at 498) and
one `#[tokio::test]` in xiso.rs (182; `mod tests` at 179) -- and tokio was
ALREADY present in `[dev-dependencies]`, so the tests keep compiling
unchanged.
The unused normal dependency pulled `tokio/full`, whose `net` feature
drags in `mio`, which does not build for wasm32:
error: This wasm target is unsupported by mio.
Removing it is right on its own terms; the WASM job is merely what exposed
it. Native is unaffected -- `cargo check --workspace` exits 0 on x86_64.
This is separated from the CI configuration it was found through because
it is the one change here that touches another crate, and #11 is
`state/proposed` around the getrandom error alone. It is ordered first so
that every commit builds: the reverse order would leave an intermediate
commit still failing the wasm check on `mio`.
WARNING: this does NOT reach `sylpheed-export`, which builds
`sylpheed-formats` from the git pin `formats-pin-2026-09-01` (`1cd5b8b1`,
contained in `auto/frame-blend-draw-path` only) rather than the workspace
path crate. The dependency is not gone tree-wide until that pin resolves,
so anyone later adding `-p sylpheed-export` to the WASM job will hit `mio`
with this fix apparently already applied.
Refs #11
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
§§1-9 were written on fabi-Hyrican-PC. On the other desktop the ~/.sylph-*
credentials do not exist, the Pi does not resolve, and stable is 1.90.0 rather
than the 1.98.1 §8 records -- so §8.2 (fetch the WASM bundle) and §8.4 (Phase 7)
cannot be run from here at all. Says which of the four steps can.
Measured rather than carried over: protection holds (10/10), fmt is 774 hunks
across 154 files, check-citations is 19, and the tests are 207/0/14 across 30
suites. Two of those need reading carefully:
* clippy DIVERGES. The runner is rustc 1.98.1 -- read out of job 794's log,
not assumed -- and is clean; here 1.90.0 exits 101 on only_used_in_recursion
at vfs.rs:85. That is #15 ceasing to be theoretical. It is NOT evidence that
CI's green is fake, which is the §7 lesson-5 inference in the other
direction.
* the test tally matches to the unit while measuring something else. 15
*_disc.rs files resolve disc_root() through a hardcoded absolute path, so
unsetting SYLPHEED_DISC does not skip them: the disc suites RAN here (1936 s,
mesh_consistency_disc alone 1220 s) and skipped on CI (2.4 s total) -- and
both report 207/0/14, because the skip path returns from a test that still
passes. Good news for #14, since this run is the stronger evidence; and worth
an issue, since SYLPHEED_DISC looks like a control and is not one.
I got that last one wrong first -- inferred "the counts cannot match" from "the
fallback resolves", which is §7's shape a sixth time, recorded as such.
Also: a plain `git clone` of this repo fails three ways on the pack that still
carries the 545 MB; --filter=blob:none works. And §6's tokio claim was
challenged and survived -- every use is inside a #[cfg(test)] module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Written to be read cold by a session with no context: what is established
versus what is someone's claim, which of the four actors the gate actually
binds, and what is in flight but unpushed.
The transferable part is §7 -- one failure shape recurred five times in two
days across two agents and the assistant: a property inferred from something
ADJACENT to it rather than tested directly. Protection from a settings page,
reachability from a DNS record, a label count from a grep that matched the
function definition, Gitea's host from a DNS lookup, and a clippy version from
rustfmt output parity. The last is the sharpest: rustfmt 1.8.0 and 1.9.0, nine
months apart, both emit 774 hunks here, so formatting parity carries no
information about which clippy ran -- and the conclusion drawn from it (that
CI's green was cached or ungated) was wrong in a way that would have sent
someone chasing a phantom.
`ascii_runs` in movie_manifest.rs had a nested `else { if .. }`. Clippy's
`collapsible_else_if` flags it — on some toolchains.
WHY IT WAS NEVER CAUGHT, AND WHY THAT IS THE INTERESTING PART:
clippy::collapsible-else-if @1.92.0 (2025-12-08) -> warn
clippy::collapsible-else-if @1.98.1 (2026-09-01) -> allow
CI installs `dtolnay/rust-toolchain@stable`, which floats. The runner is on
1.98.1, where this lint is allow-by-default, so the Clippy job passed. A local
run on a nine-month-old stable failed on the same bytes. Neither instrument was
broken; "clippy clean" is simply a statement about a toolchain and a date, and
nothing in the pipeline records which. That is #15, and this commit does not fix
it -- it removes one instance of its consequence.
Fixing it rather than pinning, because `else { if }` is worse code on every
version. A tree whose cleanliness is contingent on a release date is the thing
to avoid; agreeing with both toolchains is cheaper than arguing about which one
is right.
⚠️ I FIRST DIAGNOSED THIS WRONG, and the wrong version is worth recording. From
CI's rustfmt reporting 774 hunks and mine reporting 774, I concluded the
toolchains matched, therefore the clippys matched, therefore CI's green must be
a cached or ungated result -- "the frozen splash again". Every step after the
first was false. rustfmt is deliberately output-stable within a style edition;
clippy explicitly moves lints between groups. Measured here afterwards:
rustfmt 1.8.0-stable -> 774 hunks
rustfmt 1.9.0-stable -> 774 hunks (nine months apart, identical)
So formatting parity carries no information about which clippy ran. It is the
same error as reading protection off a settings page or reachability off a DNS
record: a property inferred from something ADJACENT to it. The peer refuted it
by measurement -- `No cache found` in the run log, and `success()` evaluating
'true' in 207 against 'false' in 203 -- rather than by accepting the framing.
Verified on both: 1.92.0 `clippy --workspace -- -D warnings` rc=0 (was 101);
1.98.1 with the lint forced on, 0 sites. rustfmt still 774, so no debt added to
#12. Tests 207 passed / 0 failed / 14 ignored, unchanged.
The lint work added 8 rustfmt hunks. Run 206's Formatting job reports 782
where run 204 reported 774, and the difference is mine — debt added to the
one issue the lint pass argued should not be disturbed. Measured against a
`b6466cb` worktree under the same rustfmt, the tree is back to 774: equal
to baseline, not merely close to it.
Two causes, both interactions rather than mistakes of judgement:
`cargo clippy --fix` writes its replacement on one line. Where the call
sat inside a multi-line method chain that produced
.as_chunks::<2>().0.iter()
which rustfmt wants split across three lines. Seven sites, in
`formats/{audio,vfs,game_data}.rs` and `cli/main.rs`. An eighth was a
`for` header pushed past the width limit; it reads better as two bindings
than as a six-line chain, so that is what it became.
The last one is subtler and was the only file left over after the other
seven were fixed. `ship_capture.rs` had
let mut flush = |base: u32,
size: u32,
and clippy correctly removed the `mut`. That shortens the line by four
characters, so the closure's six continuation parameters were then
aligned four columns too far right — a formatting change caused by a
change on a different line, in a file whose own hunk count is what
exposed it.
Worth recording because it generalises: `--fix` output is not rustfmt
output, and on a tree that is not rustfmt-clean the difference is
invisible in the diff and only shows up as a hunk count moving. The check
that catches it is a count against a same-toolchain baseline, not an
inspection of the patch.
Re-verified after the reformat, since these edits changed real lines:
cargo clippy --workspace -- -D warnings exit 0
cargo test --workspace exit 0 207 passed, 0 failed
cargo fmt --all -- --check 774 hunks == baseline
Refs #12, #13
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
`cargo clippy --workspace -- -D warnings` now exits 0. `cargo test
--workspace` still reports 207 passed, 0 failed, 14 ignored across 30
suites — identical to runs 203 and 204, so none of this changed behaviour.
The workspace total was 73, not the 48 run 204 reported. `-D warnings`
turns a lint into a hard compile error, so `sylpheed-formats` failing
stopped its dependents from ever being built: `sylpheed-viewer` (14) and
`sylpheed-cli` (11) had never been linted by anyone. Clearing formats in
5c35a34 is what made them visible.
formats 43 -> 0 (5c35a34)
viewer 14 -> 0
cli 11 -> 0
export 5 -> 0
Collision surface, measured rather than assumed. Every viewer file
carrying a lint is byte-identical on both `auto/frame-blend-draw-path`
(495 commits) and `auto/port-p6-audio` (366). All eleven cli sites fall
outside every hunk either branch touches. 68 of the 73 sites could not
collide with anything.
The five that can are all in `sylpheed-export`, and three of those are
real:
main.rs:278 `&out` -> `out`, inside frame-blend's hunk -278,12
main.rs:318 `&out` -> `out`, inside port-p6-audio's hunk -303,44
audio.rs:113 an added `#[allow]` in a file frame-blend DELETES
Each is one line. Resolving the first two means taking the branch's
version and re-applying a borrow removal; the third resolves to the
deletion. Flagged here so neither branch owner meets them cold.
Judgement calls, all stated at the site rather than suppressed globally:
* Three `too_many_arguments` in the viewer are false positives.
`draw_viewer_ui`, `poll_loader_channel` and `apply_pak` are Bevy
systems — every parameter is a `Res`/`ResMut`/`EventWriter` the
scheduler injects, so the count is the framework's dependency list and
cannot be reduced without a `SystemParam` struct.
* `cmd_screen_render` (cli, 8/7) is a plain function, so that one is real
if mild; its arguments are the subcommand's flags.
* Two `dead_code` fields in export are serde schema fields. They model
what the on-disc JSON accepts; deleting them would quietly change that.
* `iso_loader.rs` gains a `FrameRx` alias for the ffmpeg frame channel,
which is what "very complex type" was asking for.
A site-local `#[allow]` with a reason is a decision recorded where it
applies: one lint, one function, and any new violation elsewhere still
fails the build. That is not the shape PROTOCOL.md forbids.
Closes#13
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj