Skip to content

Follow-ups from the first hardware acceptance run - #10

Merged
LucasSKrewer merged 3 commits into
mainfrom
acceptance-followups
Aug 18, 2026
Merged

Follow-ups from the first hardware acceptance run#10
LucasSKrewer merged 3 commits into
mainfrom
acceptance-followups

Conversation

@LucasSKrewer

Copy link
Copy Markdown
Owner

Everything here comes out of running hardware_check.py against a real Micro DNC 2 (see #3). Three commits, independently reviewable.

hardware_check step [3] never answered Q3 on real hardware. It only looked in the root, and a box in use keeps programs in a subfolder — so it skipped on exactly the run meant to answer Q3. It also called download() without the size and then compared against the listing, which is the hazardous path: on a box whose largest file is an exact multiple of 512 it would time out and blame "block accounting" on a correct client. Now searches one level down and passes the size. Inferring the end without a size still has a home — the 512-byte boundary check, left as is.

The download timeout named the symptom, not the fix. On this firmware the likeliest cause of "no valid reply for block N" is that the file ended, the device stayed quiet, and no size was passed. It deliberately does not diagnose: a timeout always lands on a block boundary, so end-of-file and lost-packet are indistinguishable from inside download() — that being the whole reason silence is never read as EOF. It names both, plus the parameter that fixes the first. Nothing is added when no bytes arrived at all.

README states the answers. Recorded as what one unit did, not as a protocol guarantee — the box reports no firmware revision. The finding worth the protocol summary is the asymmetry: it never sends an empty final block, but happily accepts one.


Tests: 130 pass (124 + 6 new). Real-hardware evidence for the search fix: with the root holding only directories, step [3] now reports '0:program\3D.NC' (in 0:program): 259373 bytes, matches the listing where it previously reported SKIP.

Not addressed here: the 512-boundary check still reports FAIL for what is a discovered firmware behaviour the client already handles correctly. Making a clean run all-green needs a fourth outcome alongside PASS/SKIP/FAIL, which changes the script's result semantics — left for a separate decision.

LucasSKrewer and others added 3 commits August 18, 2026 15:53
Two things stopped step [3] from ever answering Q3 on real hardware.

It only looked in the root. A box in use keeps its programs in a subfolder -
the one this was written against lists exactly two directories at the root and
no files at all - so the check skipped, and the report came back saying Q3 was
unanswered on the very run that was supposed to answer it. It now falls back to
one level down, taking the largest file it finds, and only reports "no file" if
the box really has none anywhere. A folder that refuses to list is stepped over
rather than ending the search.

It also called download() without the size, and then compared the result
against the size from the listing. That is the hazardous path the parameter
exists to avoid: on a box whose largest file happens to be an exact multiple of
512, the read would time out and the report would print "block accounting is
off" about a client that had done nothing wrong. The size is now passed. That
does not weaken the check - a device sending more raises, a device sending
fewer still trips the comparison - and inferring the end without a size still
has a home, in the 512-byte boundary check, which is deliberately left as it is.

Evidence: 4 unit tests for the search (folder fallback, root preferred so no
needless round-trip, unreadable folder skipped, genuinely empty box). And a
real run: against a Micro DNC 2 whose root holds only directories, step [3] now
reports '0:program\3D.NC' (in 0:program): 259373 bytes, matches the listing -
where the previous code reported SKIP.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"No valid reply for block 2 after 10 attempts" sends you to check power,
cabling and the local-port-69 rule. On this firmware the likeliest cause is
none of those: the file ended, the device stayed quiet instead of sending an
empty final block, and nobody passed the size. That is fixable from the
message, so the message should carry it.

It deliberately does NOT diagnose. A timeout always lands on a block boundary -
a short block ends the loop instead of timing out - so "the file ended here"
and "a packet was lost mid-file" are indistinguishable from inside download().
That indistinguishability is the whole reason silence is never read as end of
file, and a message claiming to know which one happened would be worse than the
bare timeout it replaces. So it names both, and names the parameter that makes
the first one stop being a failure.

Nothing is added when no bytes arrived at all: that is a box that never
answered, and pointing at a missing parameter there would send someone editing
code while the real problem is the cable.

Evidence: 2 tests - the message names expected_size and list_dir() and still
admits a lost packet is possible; and a box that answers nothing produces the
plain timeout with no mention of the size. 130 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Q1/Q2/Q3 were posed as open questions because nothing had been run against
hardware. An acceptance run has now settled all three, and leaving the README
asking them buries the one thing this repository has that a reimplementation
would not: measurements off the real device.

Recorded as what one unit did, not as a property of the protocol. The box
reports no firmware revision, so there is no version to pin the claims to, and
someone else's unit may differ - which is exactly why the script ships rather
than just its output.

The finding worth putting in the protocol summary is the asymmetry: the device
never sends the empty final block, it just goes quiet past the end, while
happily accepting an empty final block when receiving. That is what makes
expected_size a correctness requirement rather than an optimisation on a file
whose length is an exact multiple of 512, and it is not something a reader
could infer from the opcode table.

Evidence: hardware_check.py against a MICRO DNC2 over WiFi - Q1 from a
104-byte canary and a 259,373-byte program carrying 14,141 CRLF pairs, both
returned byte for byte identical; Q2 from listings in the root and a subfolder;
Q3 from 507 blocks matching the listing, and from the 512-byte boundary check
timing out, which is the measurement behind the asymmetry above.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LucasSKrewer
LucasSKrewer merged commit 3020f02 into main Aug 18, 2026
4 checks passed
@LucasSKrewer
LucasSKrewer deleted the acceptance-followups branch August 18, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant