mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
Prove the corner rolloff; document the one-press recorder
The stream harness gains rule 20: under M4 with the curve in force, the accelerate-in head of a line renders less density at the default gamma of 2 than at gamma 1 (0.317 against 0.537 on the host grid) while the cruise middle renders identically - the exponent shapes only the velocity-scaled rolloff, never the programmed level. The panel-serves covers widen to the whole recorder (src/curverec.*). BRINGUP describes the rolloff and the one-press recorder; the MOTION table gains laser_corner_gamma; the panel help explains the knob.
This commit is contained in:
+12
-5
@@ -250,11 +250,18 @@ pairs, ships with the bench-measured default compiled in, accepts
|
||||
`off` for the identity, falls back loudly on a bad value, and is
|
||||
reloaded at every precompute with the arm naming it (`laser armed
|
||||
(density, floor 10 %, curve bench-default)`). `$35`/`$36` still floor
|
||||
and ceil the result. An owner measures their own curve from the panel:
|
||||
the dose-curve recorder hands them a ladder G-code to run from their
|
||||
sender - every arm gate stands, forgectrl records the tube current and
|
||||
the head thermopile passively, fits the rungs, and Apply writes the
|
||||
result (`forgectrl/docs/SERVICES.md`). Rasters hold their tonality down
|
||||
and ceil the result. Under M4 the curve makes delivered light exactly
|
||||
proportional to velocity - and heat still accumulates where the head
|
||||
slows, so corners over-burned on the bench; `laser_corner_gamma`
|
||||
(default 2, range 0.25 to 4) bends the rolloff to light proportional to
|
||||
(v/v_programmed)^gamma, starving the slow spots the way the raw convex
|
||||
mapping used to by accident, while the programmed level at speed stays
|
||||
exactly the curve's. An owner measures their own curve from the panel:
|
||||
the dose-curve recorder streams the ladder job itself from one Record
|
||||
press (absolute from X0 Y0, refused while a sender is connected; the
|
||||
operator's button press starts the fire with every arm gate standing),
|
||||
records the tube current and the head thermopile, fits the rungs, and
|
||||
Apply writes the result (`forgectrl/docs/SERVICES.md`). Rasters hold their tonality down
|
||||
to ~14 pulse slots per pixel (508 DPI at 6000 mm/min): the dither
|
||||
accumulator's cross-pixel averaging recovers the levels, with no
|
||||
visible dither pattern.
|
||||
|
||||
@@ -428,6 +428,7 @@ only be changed while the machine is idle.
|
||||
| `laser_disarm_s` | 60 | Spindle-off grace before the armed window closes. |
|
||||
| `laser_floor_density` | 10 | The S-range floor, percent of full: the lowest pulse density that still marks. Loaded into `$35` at every spindle precompute; `$35` is derived, never typed. |
|
||||
| `laser_dose_curve` | (bench default) | The measured dose curve as density:light percent pairs; S commands a light fraction and the driver maps it onto the density that delivers it. `off` = identity; a bad value falls back to the default. The panel's recorder measures and applies a machine's own. |
|
||||
| `laser_corner_gamma` | 2 | The corner rolloff under M4: delivered light follows (v/v_programmed)^gamma, so 1 is plain proportionality and higher values starve the slow spots where heat accumulates. Rides the curve. |
|
||||
| `laser_pulse_ticks` | 20 | Density base period in machine ticks (35.5 us each). |
|
||||
| `laser_pulse_min_ticks` | 3 | Shortest density pulse in ticks; below it a period is skipped and its debt carried. |
|
||||
| `rail_settle_s` | 2.5 | Motor-rail off period when a controller takes the device standalone. |
|
||||
|
||||
@@ -223,7 +223,7 @@ def settings_bounds(ctx):
|
||||
covers=[("forgectrl", "src/ui.*"), ("forgectrl", "src/ui/**"), ("forgectrl", "src/status.*"),
|
||||
("forgectrl", "src/cam.c"), ("forgectrl", "src/main.c"), ("forgectrl", "src/super.c"),
|
||||
("grblhal-glowforge", "src/glowforge_status.c"), ("grblhal-glowforge", "src/serial.c"),
|
||||
("forgectrl", "src/curverec.c")],
|
||||
("forgectrl", "src/curverec.*")],
|
||||
description="The panel page is served, /status carries the machine telemetry the panel and "
|
||||
"the acceptance tool read (including the sys block: CPU busy percent over the "
|
||||
"interval since the previous read, memory used percent), and /cam/status "
|
||||
|
||||
@@ -68,6 +68,11 @@ over TCP, then checks the dumps against the kernel feeder contract:
|
||||
near 80 percent density), monotonic, floored and ceiled by
|
||||
$35/$36; every other session runs with laser_dose_curve = off so
|
||||
its S-to-level arithmetic stays exact
|
||||
20. the corner rolloff starves the slow spots: under M4 with the curve
|
||||
in force, the accelerate-in head of a line renders less density at
|
||||
the default gamma of 2 than at gamma 1, and the cruise middle
|
||||
renders the same - the exponent shapes only the velocity-scaled
|
||||
rolloff, never the programmed level
|
||||
|
||||
The analog sessions select the reference mode through the config; on
|
||||
hardware the controller ignores it (density is the only product model -
|
||||
@@ -861,6 +866,41 @@ def main():
|
||||
"ladder renders through the %g %% floor key, levels %s"
|
||||
% (PWM_MIN_PCT, list(expect_levels)))
|
||||
|
||||
# --- rule 20: the corner rolloff starves the accel head -------------
|
||||
# One long M4 line from rest under the curve, at gamma 1 and the
|
||||
# default 2. The accelerate-in head runs velocity-scaled; its
|
||||
# rendered density must drop with the exponent while the cruise
|
||||
# middle stays put.
|
||||
# F6000 = 100 mm/s: the accel from rest lasts ~143 ms (~4000 ticks),
|
||||
# so the first 2000 ticks are genuinely velocity-scaled.
|
||||
JOB_M4_LONG = ["G91", "G21", "M4 S1000", "G1 X60 F6000", "M5"]
|
||||
head_ticks = 2000
|
||||
dens_head = {}
|
||||
dens_mid = {}
|
||||
for gname, gconf in (("g1", "laser_corner_gamma = 1\n"), ("g2", "")):
|
||||
data = run_session("rolloff-" + gname, JOB_M4_LONG,
|
||||
conf=DENSITY_CONF_CURVED + gconf)
|
||||
ticks = tick_bytes(data)
|
||||
spans = fire_spans(ticks)
|
||||
if len(spans) != 1:
|
||||
fail("[rolloff-%s] %d fire spans, expected 1" % (gname, len(spans)))
|
||||
a, b = spans[0]
|
||||
seg = ticks[a:b]
|
||||
head = seg[:head_ticks]
|
||||
mid_a = len(seg) // 2 - 2000
|
||||
mid = seg[mid_a:mid_a + 4000]
|
||||
dens_head[gname] = sum(1 for t in head if t & 0x10) / float(len(head))
|
||||
dens_mid[gname] = sum(1 for t in mid if t & 0x10) / float(len(mid))
|
||||
if not dens_head["g2"] < dens_head["g1"] - 0.02:
|
||||
fail("[rolloff] gamma 2 does not starve the accel head (g1 %.3f, g2 %.3f)"
|
||||
% (dens_head["g1"], dens_head["g2"]))
|
||||
if abs(dens_mid["g2"] - dens_mid["g1"]) > 0.02:
|
||||
fail("[rolloff] gamma changed the cruise density (g1 %.3f, g2 %.3f): it "
|
||||
"must shape only the rolloff" % (dens_mid["g1"], dens_mid["g2"]))
|
||||
print("PASS [rolloff]: accel-head density %.3f at gamma 1 -> %.3f at the "
|
||||
"default 2; cruise %.3f alike" % (dens_head["g1"], dens_head["g2"],
|
||||
dens_mid["g1"]))
|
||||
|
||||
# --- rule 19: the dose curve bends S onto delivered light -----------
|
||||
cur = run_session("curve", JOB_CURVE, conf=DENSITY_CONF_CURVED)
|
||||
if "curve bench-default" not in run_session.text:
|
||||
|
||||
Reference in New Issue
Block a user