Retract the pulse-length conclusion; show the minimum in the drill table

The fifth ladder, the first with a minimum pulse, moved the floor down a
full rung: only 5 percent failed to mark, and 5 percent now strikes. The
trace carries eight current segments where the run before it had seven,
with fire beginning at 6.2 s exactly at rung 1 and the usual flat
saturated final segment anchoring the count from the other end.

That retracts what the previous entry concluded. Pulse length is not
irrelevant: 10 percent moved from no mark at F100, with three times the
dose per millimeter, to a mark at F300 at the same density, the only
change being its pulses growing from 36-71 us stubs to 106 us. The
matched-pairs argument was sound but drawn entirely from comparisons at
or above 20 percent density, where every pulse length in play was already
long enough - it generalized from the one regime where pulse length does
not bite. Above ~100 us dose governs; below it pulse length does; below
~36 us the supply does not strike. The factory's 100 us quantum sits on
that boundary.

dladder now reads laser_pulse_min_ticks and prints what is actually
emitted. Without that its table reports the pulse density alone would
give, which is wrong wherever the minimum applies - at min 6 the bottom
four rungs all emit 213 us and vary their rate instead, and the operator
reads that table to interpret the material.
This commit is contained in:
ScottW514
2026-08-17 21:47:40 -04:00
parent a3e83c4fd5
commit ff454ed537
3 changed files with 76 additions and 20 deletions
+20 -14
View File
@@ -1090,22 +1090,28 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
under both models - the motion grid is identical and every density FIRE 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. tick is one the analog run also fired, so the model only ever masks.
The base period is settled: four bench ladders (F300 at periods 20, 40 **Both dose and pulse length matter, in different regimes.** Four
and 10, then F100 at 20) put the same six rungs on the material every ladders run with no minimum pulse (F300 at periods 20, 40 and 10, then
time, and the matched pairs across periods separate the variables - at F100 at 20) put the same six rungs on the material every time - 20 %
and up - and their matched pairs looked like a clean answer: at
identical pulse length, halving density killed the mark; at identical identical pulse length, halving density killed the mark; at identical
density, varying pulse length 3x changed nothing. Feed did not move it density, varying pulse length 3x changed nothing; and feed did not move
either: 10 % at F100 carries 44 % more energy per millimeter than 20 % it either, 10 % at F100 carrying 44 % more energy per millimeter than a
at F300, which marks, and still left nothing. So the low-end marking marking 20 % at F300 and still leaving nothing.
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: That reading was too broad. Every one of those comparisons sat at or
at 5 % the model emitted 36 us stubs and the `hv_current` trace shows above 20 % density, where the pulses in play were already long enough.
**no discharge at all** for that rung, while 10 % drew current for its A fifth ladder with a 3-tick minimum moved 10 % from nothing to a mark
full 15 s and simply marked nothing. The factory never emits below one at the **same density and less dose**, purely by lengthening its pulses
100 us tick, and reaches low density by skipping windows instead - from 36-71 us to 106 us. So above ~100 us the outcome follows dose;
which is now what `laser_pulse_min_ticks` does. below it pulse length dominates - too short and the energy does
nothing, shorter still (36 us, one tick) and the supply does not strike
at all, which the `hv_current` trace showed as a rung with no discharge
for its full 15 s. That is what the factory's 100 us quantum protects,
and what `laser_pulse_min_ticks` now protects.
With a minimum in place the low end is decoupled from the base period,
so the period is free to be chosen on other grounds. It stays at 20.
Owed: the user-facing scale. The factory maps its whole 1-100 power 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 scale onto density 18.9-79.5 % (fit from the three captures; Full Power
+41 -4
View File
@@ -2949,10 +2949,47 @@ 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 below the minimum (excepting one clipped by fire going off mid-burst), and
the rendered density still exact. the rendered density still exact.
Owed next: the user-facing scale. Under this model `$35` and `$36` are a ### The fifth ladder, and a conclusion retracted
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 Same ladder, period 20, F300, with the 3-tick minimum in place. **The floor
the 10 %→20 % step these four runs give. moved down a full rung: only 5 % failed to mark, and 5 % now strikes.**
The trace carries eight current segments where the F100 run had seven.
Segmenting by time rather than by zeros — at 5 % density the sampled current
aliases, so isolated zeros appear mid-rung and cannot serve as boundaries —
the rung period is 5.25 s and lines up end to end: fire begins at 6.2 s,
exactly at rung 1's start, boundaries fall at 11.4, 16.6, 21.9, 27.3, 32.6,
38.1 and 43.3 s, and the span is 42.0 s against 41.4 s for eight rungs. The
final segment reads 937–981 flat and saturated, which can only be full
density. Rung 1 shows peaks of 291, 286 and 204 where the F100 run held a
flat zero for the rung's entire fifteen seconds.
**This retracts the conclusion in the entry above.** Pulse length is not
irrelevant: 10 % moved from no mark at F100 — with three times the dose per
millimeter — to a mark at F300, at the same density, the only change being
its pulses growing from 36–71 µs stubs to 106 µs. The matched-pairs argument
was sound but drawn entirely from comparisons at or above 20 % density, where
every pulse length in play was already sufficient; it generalized from the one
regime where pulse length does not bite. Above ~100 µs dose governs, below it
pulse length does, and below ~36 µs the supply does not strike at all. The
factory's 100 µs quantum sits exactly on that boundary.
Not read into: the low-rung current means (76 and 82 raw for 5 % and 10 %).
At those duties a 3.3 Hz point sample of a pulsed current carries
presence-versus-absence and nothing more. Noted as a confound, though it cuts
against the result rather than for it — this ladder started at MPos 0,0 after
the controller restart, so it may be on different material than the stacked
Y=0/24/48/72 runs.
Owed next: whether a longer minimum reaches further down (`min_ticks = 6`,
213 µs, is set on the bench for the next ladder), and then 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. The trick has
a ceiling of its own: a longer minimum at fixed dose means longer gaps, and
once gap × feed approaches the beam spot a line dots. At 5 mm/s a 4.5 ms gap
is 22 µm against a ~200 µm spot; at 2000 mm/min it is 150 µm, where dotting
would start to show.
## Superseded status notes ## Superseded status notes
+15 -2
View File
@@ -521,6 +521,8 @@ DLADDER_PITCH = 3.0 # mm between rungs
STREAM_RATE_HZ = 28160 # machine tick (GFSINK_RATE default) STREAM_RATE_HZ = 28160 # machine tick (GFSINK_RATE default)
PWM_PERIOD = 127 # 7-bit power byte against PWMSAR PWM_PERIOD = 127 # 7-bit power byte against PWMSAR
CONF = os.environ.get('GFHOME_CONF') or '/data/forgefirm.conf' CONF = os.environ.get('GFHOME_CONF') or '/data/forgefirm.conf'
PULSE_MIN_KEY = 'laser_pulse_min_ticks'
PULSE_MIN_DEFAULT = 3 # glowforge_laser.c PULSE_MIN_TICKS_DEFAULT
def conf_get(key): def conf_get(key):
@@ -621,9 +623,16 @@ def drill_dladder(g):
# What each rung actually emits. The on-count is dithered between # What each rung actually emits. The on-count is dithered between
# adjacent integers, so the burst below is the mean. # adjacent integers, so the burst below is the mean.
min_ticks = int(conf_get(PULSE_MIN_KEY) or PULSE_MIN_DEFAULT)
if min_ticks < 1:
min_ticks = 1
print('period %d ticks = %.0f us at %d Hz -> %.0f Hz pulse rate' print('period %d ticks = %.0f us at %d Hz -> %.0f Hz pulse rate'
% (period, period * 1e6 / STREAM_RATE_HZ, STREAM_RATE_HZ, % (period, period * 1e6 / STREAM_RATE_HZ, STREAM_RATE_HZ,
STREAM_RATE_HZ / float(period))) STREAM_RATE_HZ / float(period)))
print('minimum pulse %d ticks = %.0f us (%s): below it the model skips'
% (min_ticks, min_ticks * 1e6 / STREAM_RATE_HZ,
PULSE_MIN_KEY if conf_get(PULSE_MIN_KEY) else 'driver default'))
print('periods and carries the debt, so the pulse never falls under it.')
print('rungs (drawn in order, alternating direction, +Y between):') print('rungs (drawn in order, alternating direction, +Y between):')
levels = [] levels = []
for pct in DLADDER_PCT: for pct in DLADDER_PCT:
@@ -638,8 +647,12 @@ def drill_dladder(g):
# skipped instead - that is the short end this drill is for. # skipped instead - that is the short end this drill is for.
lo = int(on) lo = int(on)
tick_us = 1e6 / STREAM_RATE_HZ tick_us = 1e6 / STREAM_RATE_HZ
if lo == 0: if on < min_ticks:
burst = '1 tick (%.0f us) on ~%.0f%% of periods' % (tick_us, on * 100) # Below the minimum the model skips periods and carries the
# debt, so the pulse holds at the minimum and the rate drops.
burst = '%d ticks (%.0f us) every %.1f periods (%.0f Hz)' % (
min_ticks, min_ticks * tick_us, min_ticks / on,
STREAM_RATE_HZ / float(period) * on / min_ticks)
elif on == lo: elif on == lo:
burst = '%d ticks (%.0f us)' % (lo, lo * tick_us) burst = '%d ticks (%.0f us)' % (lo, lo * tick_us)
else: else: