Cover the minimum pulse width; record what the density ladders measured

Rule 15 in the stream harness holds both halves of the minimum
(grblHAL-glowforge f7e8c17, pinned here): no emitted burst falls below
laser_pulse_min_ticks, excepting one clipped by fire going off mid-burst,
and the levels too faint to fill a window still render their exact
average density. Checked against a run at minimum 1 so it cannot pass
vacuously - level 2 goes from 444 bursts of one tick to 147 of three at
the same density, and levels already above the minimum are unchanged.

Four bench ladders settle the base period at 20. The same six rungs
marked in all of them, and the matched pairs across periods separate the
variables: at identical pulse length, halving the density killed the
mark; at identical density, varying the pulse 3x changed nothing. Feed
does not move it either - 10 percent at F100 carries 44 percent more
energy per mm than 20 percent at F300, which marks, and still left
nothing. The low-end marking limit is average power, not dose per length
and not pulse length.

The F100 trace separates two failures that look alike on the material:
seven current segments for eight rungs, anchored by a flat saturated
final segment that can only be full density, put 5 percent at no
discharge at all and 10 percent at a full 15 seconds of current with no
mark. Only the first is ours, and the minimum pulse is the answer to it.

Also recorded: the factory's Precision Power 1 runs a 19.53 percent FIRE
duty cycle at full PWM duty, and its 1-100 scale maps onto density
18.9-79.5 percent, so its 1 percent is the bottom of the useful band
rather than 1 percent of the range. Under the density model $35 and $36
are that same control - a density floor and ceiling - which makes the
user-facing scale a settings choice rather than new code.
This commit is contained in:
ScottW514
2026-08-17 21:37:41 -04:00
parent b7091effb7
commit a3e83c4fd5
4 changed files with 165 additions and 14 deletions
+39 -5
View File
@@ -212,9 +212,16 @@ finer than one tick per period average out. The model is selected per arm
and reported (`laser armed (density)`). Density is what the tube's dead band
below its lasing threshold requires: every pulse it emits is full-power, so
no commanded level lands in the band, and a level change inside a run costs
no stream byte at all. It wants `$35` = 0 - the floor exists only to keep an
analog duty out of the band, and under density it just clamps the light end
of the range; the arm warns when a floor is set. Structurally the model is a
no stream byte at all. `laser_pulse_min_ticks` (default 3 = 106 us) is the
shortest pulse it will emit: below it a period is skipped and its debt
carried, so a faint level arrives as fewer full-width pulses instead of
stubs the supply cannot strike - measured on the bench, a 36 us stub draws
no discharge at all, and the factory never emits below one of its 100 us
ticks. The debt is conserved, so the average density is unchanged: at level
2 the stream goes from 444 one-tick bursts to 147 three-tick bursts, same
density to four decimals. Under this model `$35` stops being a duty floor
and becomes a density floor - the control that maps S onto the band that
does useful work, which is what the factory does with its own scale. Structurally the model is a
mask on the core's fire state and never a source of one, so emission stays
exactly where the core commanded it.
@@ -1083,8 +1090,35 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
under both models - the motion grid is identical and every density FIRE
tick is one the analog run also fired, so the model only ever masks.
Owed: one bench drill to choose the base period, which is the parameter
the host cannot answer. The factory never emits a pulse shorter than
The base period is settled: four bench ladders (F300 at periods 20, 40
and 10, then F100 at 20) put the same six rungs on the material every
time, and the matched pairs across periods separate the variables - at
identical pulse length, halving density killed the mark; at identical
density, varying pulse length 3x changed nothing. Feed did not move it
either: 10 % at F100 carries 44 % more energy per millimeter than 20 %
at F300, which marks, and still left nothing. So the low-end marking
limit is average power, not dose per length and not pulse length, and
the period can be chosen on other grounds. It stays at 20.
What the ladders did expose is a floor of our own making, since fixed:
at 5 % the model emitted 36 us stubs and the `hv_current` trace shows
**no discharge at all** for that rung, while 10 % drew current for its
full 15 s and simply marked nothing. The factory never emits below one
100 us tick, and reaches low density by skipping windows instead -
which is now what `laser_pulse_min_ticks` does.
Owed: the user-facing scale. The factory maps its whole 1-100 power
scale onto density 18.9-79.5 % (fit from the three captures; Full Power
is off that line at ~99.7 %), so its "1 %" is the bottom of the band
that does useful work rather than 1 % of the physical range. Under the
density model `$35` and `$36` are exactly that control - a density
floor and ceiling - so the scale is a settings choice, not new code.
The floor's value wants one more ladder: the step from 10 % to 20 % is
coarse, and the factory's own answer is 18.9 %. Note the captures also
run 6.5-18.8 % density on other jobs, so that intercept is a product
decision about cutting, not a physical limit - which is why the
minimum-pulse fix matters for the raster low end regardless of where
the cut scale starts. The factory never emits a pulse shorter than
100 us; a tick here is 35.5 us, and every pulse restarts the discharge,
so each carries the strike transient the threshold ladder made visible
- dose per pulse is therefore probably not proportional to pulse length
+94
View File
@@ -2860,6 +2860,100 @@ show each level firing its own move. It does — 28338 fire ticks each at
duties 30, 52 and 84, where before the fix duty 30 held all 85014 and the
two other levels never appeared.
## 2026-08-17 — the density ladders, and the minimum pulse
Four live ladders on one piece of scrap, 8 rungs each from 5 % to 100 % of
dose at constant power: base period 20, 40 and 10 ticks at F300, then period
20 again at F100. The same six rungs marked every time — 20 % and up. 5 % and
10 % never marked in any of the four.
### Pulse length is not the variable; average power is
Because the same pulse length occurs at different densities across the
periods, the runs contain matched pairs:
| pulse | density | period | marked |
|---|---|---|---|
| 107–142 µs | 20 % | 20 | yes |
| 107–142 µs | 10 % | 40 | no |
| 36–71 µs | 20 % | 10 | yes |
| 36–71 µs | 10 % | 20 | no |
Hold the pulse and halve the density: the mark goes. Hold the density and
vary the pulse 3×: nothing changes. Feed does not move it either — 10 % at
F100 carries 0.0567 dose/mm against 20 % at F300's 0.0394, **44 % more energy
per millimeter than a rung that marks**, and it still left nothing. Two
independent variables moved without shifting the boundary. What sets the
low-end marking limit is average power reaching a quasi-steady surface
temperature; going slower does not help, because the heat conducts away
between pulses.
So the base period can be chosen on other grounds, and stays at 20.
### The trace separates two different failures
The F100 run carried the `hv_current` trace (`pthresh` printed one, `dladder`
did not until this run — the omission cost the three F300 ladders their
per-rung witness). It shows **seven** current segments for eight rungs:
boundaries at 36.6, ~52.0, 67.3, 82.5, 98.0 and 113.2 s, each segment
14.4–14.9 s, one 25 mm rung at F100. The fire window is 106.8 s where eight
rungs would need 121.6 s.
The final segment anchors the count: 113.5–128.4 s reads 943–986 dead flat,
the saturated steady current of continuous fire, which can only be 100 %.
Counting back, the segment means rise monotonically — 182, 320, 330, 390,
450, 540, 967 — for rungs 10 % through 100 %. Rung 1 has no segment at all:
its fifteen seconds are the zeros before 21.6 s, indistinguishable from the
arm wait because nothing happened in them.
| rung | outcome |
|---|---|
| 5 % | **no discharge at all** |
| 10 % | discharge for the full 15 s, no mark |
| 20 %+ | discharge and mark |
Supply current is not light — `pthresh` already showed this tube drawing
current across a whole band while emitting nothing — so "10 % struck" is not
"10 % lased". But 5 % not striking is unambiguous, and it is ours to fix.
### The factory's own numbers, for scale
Precision Power 1 runs the power byte at 127 (PWM duty 100 %) and a **FIRE
duty cycle of 19.53 %** — 1.371 on-ticks of every 7-tick window. Fitting the
three captures, the factory maps its entire 1–100 scale onto density
18.9–79.5 %, with Full Power off that line at ~99.7 %. Its "1 %" is the
bottom of the band that does useful work, not 1 % of the physical range —
which is why no user ever meets the dead zone. The older `_RESOURCES`
captures run 6.5–18.8 % density on other jobs, so 18.9 % is a product
decision about cutting, not a physical floor.
### The fix: a minimum pulse width
At 5 % the model emitted one-tick stubs, 36 µs, and the supply did not
strike. The factory never emits below one 100 us tick and reaches low density
by skipping windows instead. `laser_pulse_min_ticks` (default 3 = 106 µs)
does the same: when the computed on-count falls below the minimum the period
is skipped and the **whole** debt carried, rather than a stub emitted. The
debt is conserved, so the average density is untouched.
Measured on the stream, level 2 (density 0.0159):
| | bursts | density |
|---|---|---|
| minimum 1 tick | 444 × 36 µs | 0.0158 |
| minimum 3 ticks | **147 × 106 µs** | 0.0159 |
147 × 3 = 441 against 444 — the same energy as fewer, longer pulses, and
every level already above the minimum is bit-identical, so the change touches
only what it must. Rule 15 in the stream harness holds both halves: no burst
below the minimum (excepting one clipped by fire going off mid-burst), and
the rendered density still exact.
Owed next: the user-facing scale. Under this model `$35` and `$36` are a
density floor and ceiling, so mapping S onto the usable band is a settings
choice rather than new code — but the floor's value wants a finer ladder than
the 10 %→20 % step these four runs give.
## Superseded status notes
### Shared machine services — remaining polish, as listed 2026-08-13
@@ -2,5 +2,5 @@
# changes; keep only SRCREV and PV here - the image manifest leaves
# *-pin.inc out of the layer content hash because the component entry
# already identifies the pinned source (forgefirm-image-manifest.bbclass).
SRCREV = "2bca017854291f4084ca2bdf6f1ffaed88ff6d4f"
SRCREV = "f7e8c17bb3065a6b27cef4bb1099c9564359980e"
PV = "0.1.0"
+31 -8
View File
@@ -38,6 +38,10 @@ over TCP, then checks the dumps against the kernel feeder contract:
13. the model is a mask and never a source: run the same job under both
models and every FIRE tick of the density run is a FIRE tick of the
analog run, on an identical motion grid
15. the minimum pulse width holds: no emitted burst is shorter than
laser_pulse_min_ticks, and the levels too faint to fill it still
render their exact average density - the debt is carried, so a low
level becomes fewer full-width pulses rather than stubs
14. a laser state change made while the stream is idle survives to the
next run: a standalone S word between moves, from a sender slow
enough to drain the planner, must still cut at the level it asked
@@ -136,8 +140,11 @@ JOB_LADDER.append("M5")
# band - under density every pulse is full-power, and a floor would just
# clamp the light end of the range.
DENSITY_PERIOD = 20
DENSITY_MIN_TICKS = 3
DENSITY_CONF = ("laser_power_model = density\n"
"laser_pulse_ticks = %d\n" % DENSITY_PERIOD)
"laser_pulse_ticks = %d\n"
"laser_pulse_min_ticks = %d\n"
% (DENSITY_PERIOD, DENSITY_MIN_TICKS))
DENSITY_LEVEL = tuple(int(x * PWM_PERIOD / RPM_MAX) for x in LADDER_S)
JOB_DENSITY = ["$35=0"] + JOB_LADDER
@@ -467,7 +474,7 @@ def fire_spans(ticks, gap=500):
return spans
def check_density(name, data, levels, period):
def check_density(name, data, levels, period, min_ticks):
"""Rules 11-12: pinned duty, and density per rung matching the level."""
# A power byte still leads every kernel run - the run start resets the
# hardware duty - but under this model it only ever carries full duty:
@@ -494,13 +501,28 @@ def check_density(name, data, levels, period):
if abs(got - want) > max(0.01, want * 0.06):
fail("[%s] level %d rendered density %.4f, expected %.4f"
% (name, level, got, want))
run = worst = 0
# Burst lengths inside the span. The last one can be clipped by
# the core turning fire off mid-burst, so it is not held to the
# minimum; every other burst is a whole pulse the model chose.
runs, run = [], 0
for t in seg:
run = run + 1 if t & 0x10 else 0
worst = max(worst, run)
if worst > period:
if t & 0x10:
run += 1
elif run:
runs.append(run)
run = 0
if run:
runs.append(run)
if not runs:
fail("[%s] level %d produced no bursts at all" % (name, level))
if max(runs) > period:
fail("[%s] level %d burst of %d ticks exceeds the %d-tick base "
"period" % (name, level, worst, period))
"period" % (name, level, max(runs), period))
short = [r for r in runs[:-1] if r < min_ticks]
if short:
fail("[%s] level %d emitted %d burst(s) below the %d-tick minimum "
"(shortest %d): a stub too brief for the supply to strike"
% (name, level, len(short), min_ticks, min(short)))
return out
@@ -572,7 +594,8 @@ def main():
run_session("density-setup", ["$35=0"], conf=DENSITY_CONF, workdir=wd,
keep=True, arm_required=False)
dens = run_session("density", JOB_DENSITY, conf=DENSITY_CONF, workdir=wd)
rendered = check_density("density", dens, DENSITY_LEVEL, DENSITY_PERIOD)
rendered = check_density("density", dens, DENSITY_LEVEL, DENSITY_PERIOD,
DENSITY_MIN_TICKS)
check_termination("density", dens)
if "laser armed (density)" not in run_session.text:
fail("[density] the arm did not select the density model")