Record the campaign behind a print longer than the ring

The work ran from 2026-08-18 to 2026-08-20 and is finished, so the plan it ran
from is retired the way plans are retired here: what is durable moved into the
documents that describe the present, and the dated record of how the result was
obtained lands here.

The entry keeps the parts that live nowhere else. That the campaign started
from a wrong belief and was corrected by the factory itself, which stages a job
into its ring in free-checked segments and then appends to it for as long as
the job lasts. That the proof was already on the machine, a 107 MB job played
through a 32 MiB ring in this board's own factory logs. The two defects the
building surfaced, a dry ring reporting success and a pause that could not
retrace on a streamed job. And what it cost to be sure: a decoder 41 times too
slow to keep a ring fed, which only showed itself against a real 53 MB job.
This commit is contained in:
ScottW514
2026-08-20 16:20:03 -04:00
parent 3f4cd2b621
commit 54d238d67d
+60
View File
@@ -3107,6 +3107,66 @@ exception); the plan's F1/F2 rows are updated. The pause is also reported by
the factory as a ten-event phase machine against the two ForgeFIRM sends, noted
there as optional polish on the F2 work.
## 2026-08-20: the campaign behind a print longer than the ring
The work that made a cloud print independent of the ring size ran from
2026-08-18 to 2026-08-20 and is finished, so the plan it ran from is retired
into this entry and the durable documents. What follows is how the result was
obtained, which is the part that does not belong anywhere else.
**It started from a wrong belief.** The ring was 16 MiB and a job that did not
fit was going to be refused with a clean message, on the reasoning that the
factory must refuse one too. Re-reading the factory application against the
Ghidra project said otherwise on every point. Its ring is 32 MiB, allocated
through `dma_alloc_attrs` out of a 320 MiB CMA area with no device-tree pool
and no module parameter. It models the downloaded body as a pulse data source
with a cursor, gzip or plain behind one vtable, and stages it into the ring in
segments that are checked against free space, refusing with `-ENOMEM` rather
than writing a partial chunk. And it does not stop when the ring is full: it
starts the job and keeps appending for as long as the job lasts.
**The proof was on the machine already.** This board's own factory logs, kept
across slot switches on the shared `/data`, carry a 107 MB job played through a
32 MiB ring. Nothing needed to be induced; the factory had already done it and
written it down. A capture of the factory's own cloud session later showed the
same behavior on the wire, its progress `total` growing 256 KiB per interval as
it appended.
**So ForgeFIRM streams too**, and the shape follows the factory's: hold the
compressed body in memory and inflate only as far as the ring asks, which keeps
a three-hour job to a few MB and off the eMMC entirely; fill the ring before
the button is offered, so a job that cannot be loaded fails before the laser is
ever armed; declare the live feed to the kernel only when the job actually
outran the ring, so a job that fits behaves exactly as it always did; top up on
`-ENOMEM`; clear the live-feed flag after the last byte, so the real
end-of-data is a completion rather than a starved ring. The ring itself moved
to 32 MiB, at factory parity, through a size-aligned no-map device-tree pool.
**Two defects surfaced in the building.** A dry ring used to end the run loop
with `aborted=False`, which reported a job that stopped mid-cut as completed;
it aborts now. And the pause on a streamed job could not retrace, because the
old bound deducted the retained gap from a budget that was zero under a
topped-up feed, counted bytes enqueued rather than bytes played, and set its
dead stop a whole program back instead of one ring back. The retained gap *is*
the backtrack history, which is what the kernel now publishes as
`max_backtrack`, and a request longer than that is refused rather than quietly
shortened.
**What the campaign settled along the way**, each recorded where it belongs:
the factory reports progress on a `type:"progress"` frame that is the periodic
settings report; `CCbp`/`CCbt` are reported progress rather than the pause
constants an earlier reading took them for; and `CFrh`, `CCwp`, `CCrp` and
`CCup` have no consumer in the factory at all, so there is nothing to drive a
warm-up or a rest off. The contract is `kernel-module-glowforge/UAPI.md`, the
client behavior is `CLOUD.md`, and the tag findings are in the firmware
reference alongside the captures.
**What it cost to be sure:** the pulse decoder was 41x too slow to keep a ring
fed (a `sorted()` per byte, 32 kB/s against the 1.33 MB/s it manages now), and
that only showed up when a real 53 MB job was replayed through a fake ring
rather than a synthetic one. The job that hung the bench is kept as the
regression fixture.
## Superseded status notes
### Shared machine services — remaining polish, as listed 2026-08-13