The stream harness keeps the analog rendering as the host-test
conservatism reference (rule 13's mask, the duty ladders) and drops the
M101 switch sessions; rule 18 stays as the derived-floor proof, now
satisfied from boot by the precompute. The lifecycle harness's
state-files scenario asserts the derived floor is in $$ before any arm.
The catalog's laser.power-model-switch goes and laser.power-floor reads
the one floor key with no M-code needed. The mswitch drill goes; the
m4corner drill becomes a single density pass; the dpatch drill returns
to density only.
The CAMPAIGN-LOG records the first rasters (254 and 508 DPI grayscale
wedges: tonality held to ~14 pulse slots per pixel, no dither artifact,
one benign stale-verdict suppression under CPU starvation) and the
decision that ends the analog mode - the strike transient fires a spot
at every beam-on, and the finish comparison found no advantage. BRINGUP,
LIGHTBURN, MOTION and SAFETY describe the density-only present.
The lifecycle harness gains the state-files scenario: with GF_STATE_DIR
at the session workdir, grbl.settings carries the $$ view and follows
the derived floor through an M101 switch and its M2 revert, and
grbl.state follows the sender connection (a reconnect bumps the
generation), the armed window and the dose model, with ts_mono
advancing. The panel-serves catalog test asserts the /status grbl block
in GRBL mode with a live controller (fresh age, state, sender, laser,
modals) and GET /grbl/settings, and its covers name the publisher and
the serial layer; the glowforge_status.c covers entry selects nothing
until the grblHAL pin moves past the commit that adds the file. The
CAMPAIGN-LOG records the build and its proof; BRINGUP describes the
mechanism.
laser_lifecycle_test.py gains sender-change-mid-job (a laser-on against a
window closed while the spindle was on must prompt again) and rx-overrun
(a job written past the RX ring is reported, stopped in alarm with the
window closed, and a clean job arms after it). The flowload drill's
verdict parser accepts the engine's laser-share suffix.
BRINGUP: item 20 holds only the owed work; item 21 opens the mid-job
sender-change discussion with the Grbl expectation; item 22 is the flow
check under a lit tube; item 23 is the power-good line's meaning.
CAMPAIGN-LOG records the driver fix and the flow-check reading, both
host-proven.
No catalog consequence: harness scenarios and documentation; no runtime
behavior of the release image changes in this commit.
laser.pause-resume-lid-cancel failed on the bench with the head 14.925 mm
along the first leg after the lid cancel: the driver took every resume
from a feed hold as a new job start (the core restarts a held cycle
through Idle), so the cancel returned the head to the pause point and
said "returned to the job start" about it. The driver fix is in
grblHAL-glowforge; this is its proof and the record.
The null-sink lifecycle harness gains rule 9: a job paused and resumed by
the button, then cancelled by the lid, returns to where the job began
(reproduces the bench failure to the millimeter on the old driver,
X=13.088 instead of 0); and a job abandoned in a hold and soft-reset is
over, so the next job from that spot returns to that spot. The campaign
log carries the dated account.
No catalog consequence: the acceptance test measured exactly this and
stays as it is; the harness is the CI side of the same contract.
laser_lifecycle_test.py: the button toggle (press = Hold, press = Run;
the arming press is not a pause), lid and interlock cancel mid-job with
the return to the job start and no alarm, and lid_policy=hold.
Acceptance catalog: motion.button-hold-resume, motion.lid-cancel-home
(operator: travel job, lid open -> cancel message, banner, no alarm,
autonomous return, Idle at the start), laser.lid-cancel-mid-fire (live:
emission stops in hardware, cancelled, returned, armed=false, latch
locked, hardware button latch SET). covers name the switch and laser
sources explicitly.
LIGHTBURN.md: what the lid, Stop and the button now do; SAFETY.md: the
door policy and the button as a software layer; BRINGUP.md: item 16
records the whole parity change (host-proven, bench validation pending)
and items 4/12 point at it.
laser_lifecycle_test drives the controller's button wait through the
file-backed switch source (GF_SWITCH_FILE): a press with the lid closed
arms and nothing arms before it; the lid or the interlock loop opening
during the wait cancels the job (reason reported, alarm 3, never armed).
Acceptance catalog: laser.arm-wait-lid (operator kind - no press is
given, so nothing can fire) starts a laser job, has the operator open
the lid at the white-button prompt, and checks the cancel message,
alarm 3, armed=false, the kernel latch locked, no emission, and Idle
after $X. covers names glowforge_laser.c, glowforge_switches.c and
glowforge_switch_map.h explicitly.
LIGHTBURN.md: opening the lid or the interlock loop while the button is
lit cancels the job; a press with the lid open never arms.
rsyslog replaces busybox syslogd/klogd (VIRTUAL-RUNTIME_base-utils-syslog,
trimmed PACKAGECONFIG) and becomes the only log writer: the appended
/etc/rsyslog.conf sets the inputs and the ff_line format and includes
the per-logger rules that `forgectrl --render-syslog` renders from the
machine settings at boot. forgefirm-logrotate becomes forgefirm-logging:
render before rsyslog starts (S19), sweep the pre-syslog log files into
/data/forgefirm/legacy-logs once, and rotate the tree at boot and hourly
by rename + HUP instead of copytruncate. Pins bumped to the pushed
forgectrl (syslog emitter, Logs tab, export), grblHAL-glowforge (syslog
emitter) and python3-gfhardware apps (syslog handlers, capture dir);
the CI harnesses set FFLOG_STDERR=1 so failure diagnostics keep the
controller's log lines. BRINGUP carries the bench validation checklist
(Next work item 14); this is an image change and rides the next flash.
- platform_drills.py (on the board, forgectrl stopped): dead-man trip
readback, rmmod/modprobe with concurrent attr reads, decay/microstep
readback, LED sequence.
- live_fire_drills.py: ircut (S/F selectable characterization job),
expstop (armed job + POST /controller/stop, controller left stopped),
ctrlstart (separate, operator-approved resume); the token header is sent
in exact case.
- laser_lifecycle_test.py: sigterm-mid-job - SIGTERM during an armed job
must stop it, relock the latch and exit promptly.
Four sessions against the reported messages: arm once per job with
M5/M3 persistence and the M2 close (grace pushed beyond the horizon so
only the program-end path can disarm); a displaced sender must re-arm;
the disarm grace counts down in Hold; a blocking cooling verdict
refuses the arm. Test-the-test proven: a build with the job-based
window reverted fails at the first discriminating assertion. Wired
into the controller repo CI alongside the emission harness.