commissioning: the layer, the acceptance tests, the harness rule, the docs, and the bench drills

meta-forgefirm: the forgefirm-users init replays the account at boot;
sshd refuses root and empty passwords and runs only while the panel
turns it on; the release image keeps an empty root password for the
console; the console banner; avahi announces forgefirm.local; https in
libmicrohttpd and ulfius; the panel on 80 and 443; the license bundle on
the rootfs; release.sh checks the root policy on the built rootfs.

forgetest: the commission suites (commission, commission_dark,
commission_sheet: 23 cases); the runner turns cloud mode on with the
typed phrase for a test that declares it; the baseline's motor_lock is
0; the log-export test checks the bundle for the camera key; the record
helpers write bytes as given and join the daemon's paths as POSIX. The
stream harness gains rule 24: a hold verdict is held again after a
resume. Bench drills: lens_travel.py and lens_stop_accel.py.

Docs: BRINGUP carries the present state; CAMPAIGN-LOG carries the dated
record.
This commit is contained in:
ScottW514
2026-09-06 19:56:05 -04:00
parent ff9796cde6
commit 97287aa6a9
59 changed files with 15113 additions and 9139 deletions
+2
View File
@@ -35,6 +35,8 @@ page's takeover does that; from a host, stop them first.
| `live_fire_drills.py` | **LIVE LASER** drills, on the board (the bench page) or from a LAN host (`GF_HOST`): `live_fire_drills.py <drill> [S] [F]` - `witness` (emission witness, lid-IR peaks vs the ambient baseline, HV current, job-based disarm on M2), `hold` (disarm grace in Hold), `faultpos` (armed job refuses a stale origin after an underrun), `ircut` (lid-IR characterization cut at S/F), `pthresh` (laser power-threshold ladder: 13 constant-power rungs from 2 % to 30 % of full on scrap; the lowest rung that marks is the tube's striking threshold and reads directly as the `$35` value - requires `$35` = 0 for the run), `dladder` (density ladder at a chosen base period), `pcurve` (laser performance-curve ladder: one 100 mm line per level at 10 mm/s under M3, the laser off between rungs and a mid-ladder rung repeated at the end; reads `pic/hv_current` and the head thermopile `head/beam_detect_analog` (a scatter detector in the beam path upstream of the final mirror, so it sees the beam, not the material) from sysfs at ~25 Hz on the board, brackets each rung on the controller's Run/Idle states, and reports per rung the current with a clipped-at-1023 flag, the thermopile delta over its laser-off baseline and in-line drift, then the normalized curve, monotonicity, a line fit with its threshold intercept and the repeat-rung drift; JSON record with the raw trace in the bench data directory; rungs follow `laser_power_model`, a comma list overrides; a curve measurement wants `$35` = 0), `dpatch [F] [pitch] [length]` (depth witness for the density dose curve: two rows of small serpentine-filled patches, row A CW at feeds giving relative doses 1.0 to 0.25 of the reference feed, row B at the reference feed at 100/80/60/45/30 % density; the operator matches each row-B patch to the row-A patch of equal depth, which reads the density's light fraction off the material beside the thermopile's prediction; JSON record), `m4feeds [S] [F1] [F2]` (the density time base across feeds: one out-and-back line pair per feed at the same S under M4 density, one armed run; the operator reads within-line evenness and reversal darkness at both feeds - M4's velocity scaling is what holds dose per mm through the accel), `m4corner [S] [F]` (M4 velocity-scaled power into corners: a corner-heavy vector pattern at 30 % under M4 density, one armed run; the operator confirms every commanded segment marks - the floor makes a dropout unreachable - and the drill asserts the arm report, one discharge window and dark after), `m5dark` (the rapids after an M5 ship dark: one 20 mm line at M3 S400, M5, dwell, rapid back, dwell, rapid forward; PASS when the 25 Hz current trace shows one discharge segment and reads dark after the M5 and `laser_on_sampled` never re-lights; the catalog's `laser.m5-rapid-dark` is its port), `flowload` (cooling under laser load, one armed run per invocation, the conf keys it writes put back at the end, the pump never commanded off: `t1` reproduces the flow-check trip with the check on at its defaults and two 30 x 4 mm CW fills at F1500 starting on the press with no dark dwell, and reports the engine's rise/dT verdict beside the 25 Hz trace of both coolant sensors, the current, the digital witness and the heater output in 5 s bins across the window, with the shape at fire start; `t2 <secs> [pct]` runs with the check off and one fill of about `secs` lit seconds at CW or at `pct` density, and reports the lag to each sensor, the rise per raw-second of `hv_current` and what a full 50 s window would add against the 1.6 C margin; `fit` fits rise against dose over every t2 record; JSON records), `expstop` (armed kill on the expected-stop path; needs the panel token - `GF_TOKEN`, or the board's token file) and `ctrlstart` (the separate controller restart after it). Every drill waits for the operator's physical arm press; eye protection, fire watch, extinguisher, and exhaust are mandatory. |
| `pacing_test.py` | Protocol-loop pacing check (runs on the board, dry motion): idle and parked-in-Hold states are coarse-paced, active motion is tight-paced, and a feed-hold/resume mid-move preserves position with no feeder starve. |
| `gfbench.py` | Not a tool: the helper the board/host tools share - `HOST`/`LOCAL` from `GF_HOST`, `board(cmd)` (local `sh -c` or ssh), the factory coolant conversion `degc()`, `data_path()` (`FORGETEST_BENCH_DATA` or next to the tool), forgectrl's HTTP API with the panel token, `setting(key)` (from forgectrl, or from `/data/forgefirm.conf` on the board while forgectrl is stopped). |
| `lens_travel.py` | Lens travel drill (runs on the board; stops the controller through forgectrl and restarts it; lens motion only): steps the lens over sysfs the way the focus card's lens home does and counts, per condition and round, the hall's hysteresis band (down off the rising edge until the hall leaves home, back up until it reads home), the rising edge's height above the bottom stop after a drive onto it, and the top stop's height above the edge (a drive up, down until the hall leaves home, back up; the difference). Conditions: the card's half-step drive onto the stop, a short drive, a long one, the factory's full-step home mode; `--ladder` drives a list of half-step descents below the leave-home point and counts each back (exact until the stop, short by an even number after a stall), `--current` picks the run current, the hold current, or hold down / run up, `--settle` and `--cadence` vary the timing; `--park bottom|top|edge-N|edge+N` holds the lens there for a depth-gauge reading with the controller in standby, `--park edge` returns it to the rising edge and restarts the controller. Stall drills on the bench reference machine only. The posture found is put back. |
| `lens_stop_accel.py` | Lens stop detection by the head accelerometer (runs on the board; stops the controller through forgectrl and restarts it; lens motion only; stall drills on the bench reference machine only): reads the LIS2HH12 straight over `/dev/i2c-3` in six-byte bursts at 800 Hz (the iio path waits a sample period per read), steps the lens one half-step at a time from the hall's rising edge toward each stop and past it, and prints per step the peak-to-peak on each axis. A free step rings strongly on every second half-step; at a stop the ring dies, and a rotor slip a few steps later is a burst three times any free ring. `--find N` runs the contact rule N times per stop (a strong-parity step ringing under `--thresh`, or a burst over four times it, calls contact; the lens backs off `--back` and the count home proves no slip). `--save` keeps the per-step sample traces as JSON. |
| `fan_test.py` | Fan/coolant bench (board or host; controller running): snapshots fan PWMs/tachs/temps, drives M8 → cut fans, M9 → cooldown → idle, verifying via tach readbacks. |
| `fan_floor_measure.py` | The numbers the airflow gates ship with (board or host): `spinup` opens a run session with M8 from idle and samples the four tachs and the purge current at 1 Hz, reporting per fan the steady speed, the time to 90 percent and the spread over the steady window, plus the purge current at idle and at run duty (the pump is always on; a dead one reads about 1), and candidate floors at 55 percent; `cut` samples only, during a real cut, for the spread under load. Results as JSON in the bench data directory. |
| `flow_characterize.py` | Coolant flow characterization using the factory temperature curve (board or host; forgectrl and controller stopped): baseline → flow → no-flow → recovery, printing the ΔT bands and their separation. Takes the heater duty as an argument (`flow_characterize.py 30`); aborts if downstream passes 45 °C. |