427cbaa280f4d1a0dd3347042e9edf2039ecadab
1632 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 427cbaa280 |
Merge pull request 'docs(reference): adopt the four homeless reference files, and a LICENSE' (#30) from harvest/xex2tractor-assets into main
Reviewed-on: #30 |
|||
|
|
3b90f1c29d |
docs(reference): adopt the four homeless reference files, and a LICENSE
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>
|
||
| 3977cc933e |
Merge pull request 're: land the F5/F6 title-clock corpus (docs/re, reference data, sylpheed-formats)' (#23) from auto/frame-blend-draw-path into main
Reviewed-on: #23 |
|||
|
|
af3b36c354 |
fix(lint): PathBuf is unused once the merge takes #22's shared disc_root
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3db0bf9aad |
Merge origin/main into auto/frame-blend-draw-path
`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> |
||
|
|
e6266b1966 |
fix(lint): clear the clippy gate across examples and tests
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> |
||
|
|
cae0a4bd4c |
fix(lint): the hand-judged clippy sites in library code
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d394ba6aed |
style: rustfmt sweep — 107 files the lint gate never saw
This branch predates CI on `main`. `cargo fmt --all` only; no behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
| 641be935d6 |
Merge pull request 'test: make $SYLPHEED_DISC an actual control, not a decoration (#16 remedy 3)' (#22) from fix/corpus-control into main
Reviewed-on: #22 |
|||
| c2213c0c47 |
Merge pull request 'feat: propose-work — push, open the PR, and move the issue, in one command' (#21) from feat/propose-work into main
Reviewed-on: #21 |
|||
| 90a0a5b862 |
Merge pull request 'docs(agents): a red PR is the next unit of work, and check the workspace' (#24) from fix/loop-verify-own-pr into main
Reviewed-on: #24 |
|||
|
|
891c38e87e |
re: fix two stale rows -- NEW GAME's destination, and my own repeat claim
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. |
||
|
|
3d7138892c |
re: F3 sting half closed -- no sting, measured with a working positive control
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.
|
||
|
|
e61937f7a1 |
re: F1 answered -- 12 frames initial delay, 4 frames interval, by patching in the repeat the driver lacked
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. |
||
|
|
2939fa21d8 |
re: F1 measured -- held DOWN moves the cursor once and no more, via the file driver
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). |
||
|
|
089aa8d7c2 |
re: F1 dynamic attempt -- harness debugged through four bugs, still no number
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. |
||
|
|
dfbefff512 |
re: F1 -- traced C_PAD_RINGBUF; the queue naming inference was wrong
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. |
||
|
|
9141741777 |
re: F1 -- the "no auto-repeat" finding was measuring our own driver
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. |
||
|
|
dff5f7759e |
re: F3's sting half -- the mechanism is decoded, the value isn't (issue #5)
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.
|
||
|
|
322869b89d |
re: shape the F6 out-of-sample residue (issue #9) -- one open question, not three
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. |
||
|
|
7c5a4a6961 |
fix: unbreak the merge -- ComposeOptions grew at, the Explorer's literal didn't
`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.
|
||
|
|
cddf2af3b4 |
docs(agents): a red PR is the next unit of work, and check the workspace
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> |
||
|
|
bade9721b0 |
Merge remote-tracking branch 'origin/main' into auto/frame-blend-draw-path
# Conflicts: # crates/sylpheed-cli/src/main.rs |
||
| 48e3f1a983 |
docs: strike propose-work from "Still to build" — it is built
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> |
|||
| 64f3df7d72 |
test(formats): make $SYLPHEED_DISC an actual control (#16 remedy 3)
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>
|
|||
| 2f49e191c6 |
feat: propose-work — push, open the PR, and move the issue, in one command
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> |
|||
| 4ca0b8ec85 |
Merge pull request 'docs: give the Port agent an Each iteration section (it had none)' (#20) from fix/port-loop-iteration into main
Reviewed-on: #20 |
|||
| 8eafb77377 |
docs: give the Port agent an Each iteration section (it had none)
`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>
|
|||
| 1ec2e4ca57 |
Merge pull request 'test: report which corpora a run actually had (#16 remedy 2)' (#19) from fix/corpus-report into main
Reviewed-on: #19 |
|||
| f78e308ca7 |
test: build the fallback message with format!, matching the commit before it
`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>
|
|||
| 4057bee897 |
test: report which corpora a run actually had (#16)
`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>
|
|||
| ff984e0350 |
Merge pull request 'style: rustfmt sweep — 774 hunks across 154 files → 0 (#12)' (#18) from chore/rustfmt-sweep into main
Reviewed-on: #18 |
|||
| b0729d5cc9 |
Merge pull request 'WASM: compile for wasm32, and fix trunk's binary selection on aarch64' (#17) from fix/clippy-lints-wasm into main
Reviewed-on: #17 |
|||
| c4c914ff59 |
style: rustfmt sweep -- 774 hunks across 154 files -> 0
`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 |
|||
| a364813df6 |
ci(wasm): drop the artifact upload, which cannot work on Gitea
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>
|
|||
| 7285a566dd |
fix(wasm): select the bin target, so trunk emits a real bundle
`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 |
|||
| d630bc996f |
Merge pull request 'Clear all 73 clippy lints, and make the Clippy step real' (#14) from fix/clippy-lints into main
Reviewed-on: #14 |
|||
| 9b450a3b2a |
Merge pull request 'Gitea working surface: MCP, branch protection, runbook, and two protocol rules' (#10) from agents/gitea-mcp into main
Reviewed-on: #10 |
|||
|
|
90365b1751 |
ci(wasm): bump trunk-action v0.5.0 -> v0.5.1 for aarch64
`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>
|
||
|
|
a7af8a41c2 |
fix(wasm): make cargo check --target wasm32 compile
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
|
||
|
|
9f6321d7d6 |
fix(formats): drop the unused normal tokio dependency
`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> |
||
| 885b4d4d67 |
docs: the handoff's resume steps assume a machine this is not
§§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>
|
|||
|
|
4b62252d94 |
docs: handoff for resuming on another machine
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. |
||
|
|
d8807c4f8f |
fix(formats): collapse the one else { if }, so both toolchains agree
`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.
|
||
|
|
e56c1228ac |
fix: stop the lint pass adding rustfmt debt to #12
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
|
||
|
|
a2e4dab0ab |
fix(viewer,cli,export): clear the remaining 30 clippy lints
`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 |
||
|
|
d7cd1e9173 |
fix(formats): clear all 43 clippy lints in sylpheed-formats
Run 204 gave this repository its first clippy measurement — 48 errors, 43 of them in `sylpheed-formats`. This clears that 43 to zero under the exact invocation CI runs, `cargo clippy -p sylpheed-formats -- -D warnings`. Why this crate first, and why it is safe to touch: Every one of the 43 sites was checked against the line ranges that `auto/frame-blend-draw-path` (495 commits) and `auto/port-p6-audio` (366) actually modify. None of them overlap. Eleven of the fifteen affected files are byte-identical on both branches, including `mesh.rs` and `texture.rs`, which carry 28 of the hits between them. The three sites in `audio.rs`, `ui_layout.rs` and `slb.rs` that live in files those branches do change fall outside every modified hunk. The collision argument that defers #12 does not transfer here; it was tested rather than assumed. It also unblocks a measurement. `-D warnings` turns a lint in this crate into a hard compile error, so its dependents never build — `sylpheed-cli` and `sylpheed-viewer` have never been linted at all, and viewer is the largest crate in the workspace. Both depend only on `sylpheed-formats` (`sylpheed-export` pins it from a git tag instead), so this commit is what makes their real counts knowable. 38 applied by `cargo clippy --fix` — chunks_exact_to_as_chunks, manual_div_ceil / is_multiple_of / range_contains, unnecessary_map_or, needless_borrow, let_and_return, dead_code, unused_mut/variables. Purely local expression rewrites: 38 insertions, 39 deletions. 2 by hand: a doc continuation that markdown was parsing as a list, and `d / frame` behind a `frame > 0` guard becoming `checked_div`. 3 `#[allow(clippy::too_many_arguments)]` with a stated reason. On those three allows: 8 parameters against a threshold of 7, in the mesh anchor path. The real fix is a shared params struct across `anchor_pool_mesh`, `validate_block` and `validate_block_report` — the latter two take the same eight arguments and one delegates to the other — which is a change to the decoder's signatures and belongs to whoever owns that path, not to a CI-lint pass. This is not the shape PROTOCOL.md forbids. `continue-on-error` suppresses everything, present and future, at the job level, and cannot tell "not yet" from "no longer". A site-local `#[allow]` with a reason is a decision recorded where it applies: one lint, one function, and any new violation anywhere else still fails the build. `sylpheed-export`'s remaining 5 are deliberately untouched — three of them sit inside hunks both long-lived branches modify, and that crate blocks nothing. Left for #13. Refs #13 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj |
||
|
|
6263b82ea8 |
docs: record the third softening, which was authored dirty
The two instances already in "Checks that were kind once" were correct when written and decayed. The third was wrong on its first commit, and it arrived by a different route: the check and the tree's failure to pass it land in the same change, so the softening writes itself. Concretely — the Clippy step had never run (no component in the toolchain), and the tree is not clippy-clean, so fixing the step and turning it red are the same commit. The first draft paired the fix with `continue-on-error: true` and a comment promising removal once the debt was paid: an expiry date nobody set, in the shape #12's closing line had already ruled out for rustfmt. Reverted on reading it. Adds the distinction, a table separating decay from dirty authorship, and an earlier tell than the mechanical test: If you are writing the softening in the same commit as the check, the thing you want is an issue, not a flag. The mechanical test is unchanged and still correct; this only catches the same failure sooner, at the keyboard rather than at review. Refs #12, #13 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj |
||
|
|
a3d99adaa6 |
ci: install the clippy component the Clippy step needs
`dtolnay/rust-toolchain@stable` installs a minimal profile. The `native`
job named no components, so every run that reached the Clippy step died
on
error: 'cargo-clippy' is not installed for the toolchain
'stable-aarch64-unknown-linux-gnu'
before clippy read a line of source. That is not a lint result; the step
had never run. The `fmt` job below always named `components: rustfmt`
correctly — this one never did.
Two lines of behaviour change. The rest is the comment explaining why the
step is left gating on `-D warnings` rather than softened: the workspace
is not clippy-clean (run 203's build alone emits ~13 rustc warnings that
`-D warnings` promotes to errors), and `continue-on-error` cannot tell
"debt not yet paid" from "debt paid". That debt is scoped in #13, the way
the rustfmt debt is in #12.
Run 203 is what made this visible. With the aarch64 fix in
|
||
| c457320210 |
ci: build for the machine that exists, on the runner that exists
This workflow has never once gone green on this instance: 23 runs cancelled, 2 waiting, zero successes. Not a regression -- it has been decorative since it was written, because it describes GitHub's hosted fleet and runs on one self-hosted aarch64 Pi advertising ["ubuntu-latest","ubuntu-24.04", "ubuntu-22.04"]. Two failures, both configuration rather than code: `windows-latest` and `macos-latest` match no runner label, so those jobs sit in WAITING for ever and the RUN never reaches a terminal state. A pull request's checks therefore never resolve either way -- not red, just never finished, which is worse than red because a red check tells you something. Removed: a second architecture here needs a second runner, not a second matrix row. `--target x86_64-unknown-linux-gnu` on an aarch64 host makes every build a cross-compile, and `wayland-sys`'s build script dies on it with "pkg-config has not been configured to support cross-compilation". Dropped; the native job now builds for its host. NOT touched, deliberately: the WASM and Formatting jobs still fail, on real code state rather than on configuration -- `getrandom` needs the `wasm_js` backend for wasm32-unknown-unknown, and `cargo fmt --check` reports a ~13,000 line diff across the tree. Editing those two into passing is precisely the leniency with an expiry date nobody sets that PROTOCOL.md now forbids. They are issues, not workflow lines. (One latent defect noted while reading: `jetli/trunk-action` fetches trunk-x86_64-unknown-linux-gnu onto this aarch64 host. It has never been reached because the WASM check fails first, and it will bite the moment that is fixed.) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj |