mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user