Skip to content

feat: humanization injection — adaptive feed-rate, 912 MHz overclock, measured-rate synth cadence#8

Merged
ramseymcgrath merged 6 commits into
mainfrom
fix/humanization-injection
Jun 16, 2026
Merged

feat: humanization injection — adaptive feed-rate, 912 MHz overclock, measured-rate synth cadence#8
ramseymcgrath merged 6 commits into
mainfrom
fix/humanization-injection

Conversation

@ramseymcgrath

Copy link
Copy Markdown
Contributor

Integrates the work accumulated on fix/humanization-injection into main.

What's included (4 commits)

  • measured-rate synth injection cadence (cb64497) — drives the standalone synth mouse-report path from the firmware's measured real poll interval instead of a hardcoded ~1 ms gate. On an 8 kHz mouse the silent-injection cadence now matches the active (merge-path) rate instead of dropping to 1 kHz at liftoff. New pure, host-testable synth_cadence module (wrap-safe timing, measured-interval period with 1 kHz fallback + 8 kHz floor); retires the millis()/SYNTH_SILENCE_MS 1 kHz assumption.
  • KMBox Net features + deterministic hot path + 912 MHz overclock (287311e) — voltage-scaled 912 MHz default with thermal guard; motion-program path (automove/Bézier/phys-masking/input-monitoring); 64-bit-divide-determinism fixes in the hot path.
  • adaptive feed-rate timing + per-axis speed envelope; fix PIT clock (551dc69).
  • README/Layout docs polish (768b18c).

Testing

  • make and make PROTOCOL=ferrum build clean (zero warnings).
  • make test passes: humanize, motion, and synth_cadence host suites.

Notes

  • PIT-driven ISR synth emission was explored and parked on wip/pit-driven-synth-emission (has an open stale-frame edge case + needs on-target logic-analyzer validation). Not part of this PR.

ramseymcgrath and others added 4 commits June 4, 2026 23:47
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make injection cadence track the real measured mouse poll interval instead
of the nominal bInterval, add a per-axis normalized-speed envelope for noise
shaping, and fix two clock-source bugs found while grounding against the
i.MXRT1060 RM / NXP SDK / Teensy core.

Clock fixes (pre-existing bugs):
- PIT was loaded using F_CPU/4 (204 MHz) but is clocked by PERCLK = 24 MHz
  (core/startup.c). Every injection period was ~8.5x too long. Add PIT_CLK_HZ
  and pit_ldval_from_us() with 64-bit-safe conversion.
- GPT2 1 MHz tick was hard-coded /204 (only correct at F_CPU=816). Derive the
  prescaler from F_CPU so the microsecond tick survives a clock change.

Adaptive feed rate:
- Timestamp real mouse-report arrival from the free-running GPT2 counter at the
  precise poll-detect point (mouse interface only).
- EWMA the measured delivery interval with dropout/burst outlier rejection;
  pre-seed from nominal bInterval so the first (possibly huge, movement-gated)
  inter-report gap can't poison the estimate.
- Slew pit_base_ldval toward the measured target (~3%/tick, min +/-1 step so it
  fully converges) instead of jumping. RM-correct: applied at next reload via
  the existing precompute-in-ISR pattern. No FPU in the ISR.

Per-axis normalized-speed envelope (peak-with-decay, time-based decay):
- Track peak_x/peak_y in counts/sec (DPI-independent via measured interval).
- Gated behind HZ_ADAPTIVE_NOISE (default off): the default build is byte-for-
  byte the prior raw-speed behavior and pays no expf/divide per frame.

Determinism hardening:
- Explicit .fastrun (ITCM) on the humanize per-tick hot path (no-op on host
  tests) to lock in placement against LTO/flag changes.
- Linker ASSERT guarding the 64 KB non-cacheable DMA window; comment in
  usb_host.c documenting why the dsb-only pattern is correct.

LED feedback (no UART): heartbeat idle slot reports adaptive convergence --
0.5 Hz acquiring, 1 Hz once the PIT base locks on the measured rate. UART
ERROR/ACTIVE status keeps priority.

Tests: humanize host suite extended to 13 checks (interval convergence,
target-ldval math, confidence gate, dropout rejection, seed-poison guard,
conservation-with-feed). Passes in both default and -DHZ_ADAPTIVE_NOISE=1
builds; firmware cross-compiles and links.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the branch to one complete, verified state across the in-flight
work: KMBox Net endpoint features, hot-path determinism hardening, and a
guarded core overclock. Both protocol variants build warning-free and
host tests pass.

Features (KMBox Net spec, src/actions.*, hurra.*, kmbox.*, proto.h):
- Motion programs: timed linear + cubic Bézier moves, fixed-point with
  residue carry so endpoints land exact; stepped from the poll loop.
- Physical-input masking (mouse buttons/axes/wheel, keyboard keycodes)
  enforced in the merge path before injection.
- Physical-only telemetry (pre-merge, pre-mask) for true-input monitoring.

Deterministic hot path:
- Remove the last per-tick __aeabi_uldivmod: humanize_target_ldval and
  pit_ldval_from_us now use 32-bit us*(clk/1e6) (PERCLK is a whole MHz).
  Binary has zero soft-divide calls.
- DSB after W1C in all four ISRs (pit0, dma_rx, uart_idle, usb2) to stop
  spurious re-entry / double ticks.
- Build main.c at -O2 with the other hot sources (poll loop lives there).

Clocks:
- gpt_profile/led scale derived from F_CPU; static-assert F_CPU > 528 MHz
  (set_arm_clock fallback doesn't program dividers) and IPG whole-MHz.
- Fix LED heartbeat scale: was hardcoded for 150 MHz IPG, now F_CPU/4.

Overclock to 912 MHz (Makefile F_CPU ?=, overridable):
- Replace the under-volting two-point DCDC table with the interpolating
  voltage curve (1250 mV + 25 mV/28 MHz, capped at 1575 mV silicon max).
  Old table gave 912 MHz ~1275 mV vs required ~1525 mV (brownout); also
  corrects 816 MHz from 1275 -> 1425 mV.
- Thermal guard: tempmon wired into the 100 ms status tick; >=85 C trips a
  distinct ~12 Hz heartbeat (priority over UART status), clears at 73 C.
  Detection only — we never re-clock live (would corrupt F_CPU timebases).

Repo hygiene: ignore __pycache__/*.pyc; add motion_test, load-test tool,
and the KMBox Net requirements doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drive the standalone synth mouse-report path from the firmware's measured
real poll interval instead of a hardcoded ~1 ms gate. On an 8 kHz mouse the
silent-injection cadence now matches the active (merge-path) rate instead of
dropping to 1 kHz at liftoff.

- New pure, host-testable synth_cadence module (wrap-safe unsigned timing,
  measured-interval period with 1 kHz fallback and an 8 kHz floor).
- kmbox synth gate switched to GPT2 microseconds + measured interval; retires
  the millis()/SYNTH_SILENCE_MS 1 kHz assumption.
- Host unit tests wired into `make test`.
Copilot AI review requested due to automatic review settings June 16, 2026 04:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR merges the accumulated fix/humanization-injection work into main, updating the firmware’s injection cadence to track the measured real mouse poll interval, adding KMBox Net-oriented motion/mask/monitor features, and adjusting platform clocking/overclock + timing infrastructure. It also adds host tools and host-native unit tests to validate key behaviors without hardware.

Changes:

  • Add adaptive feed-rate timing driven by measured report delivery intervals (GPT2 µs), and use it to drive PIT reload selection and synth cadence decisions.
  • Implement KMBox Net features: motion programs (duration + Bézier), physical-input masking, and physical-only telemetry (Hurra; Ferrum builds link with no-op stubs).
  • Add tooling/docs/tests: Ferrum load/aim harness improvements, new host-native unit tests, and documentation updates.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tools/ferrum_load_test.py New command-channel load test (latency/throughput/integrity) for Ferrum over bridge PTY.
tools/ferrum_aim_test.py Reworked aim test with paced sender thread, higher-rate emission, and updated setup guidance.
test/synth_cadence_test.c New host-native unit tests for wrap-safe synth cadence decisions.
test/motion_test.c New host-native unit tests for motion-program endpoint/conservation/cancellation.
test/humanize_test.c Expanded tests for measured-interval EWMA and PIT target derivation behaviors.
src/usb_host.c Add cache/DMA rationale and DSB barrier in USB2 ISR for posted-write flush.
src/synth_cadence.h New pure, host-testable cadence policy helpers (period clamp/silence/due).
src/proto.h Add Hurra phys-telemetry aliases + Ferrum no-op stubs to keep merge path unified.
src/main.c PIT clock fix (PERCLK 24 MHz), adaptive PIT slew, GPT2 arrival timestamping, overtemp LED mode.
src/led.c Make LED heartbeat scale derived from F_CPU/IPG instead of a fixed constant.
src/kmbox.c Add motion tick, physical telemetry + masking pre-merge, and measured-rate synth cadence.
src/hurra.h Declare Hurra physical-only telemetry hooks.
src/hurra.c Add new Hurra frame types for motion programs, phys mask, phys telemetry enable, and phys TLM emitters.
src/humanize.h Expose adaptive feed-rate API: record arrivals, measured interval, target LDVAL.
src/humanize.c Implement measured-interval EWMA + target LDVAL; optional normalized-speed envelope (compile-time gated).
src/gpt_profile.h Make GPT2 prescaler derive from F_CPU for an exact 1 µs tick; add static asserts.
src/actions.h Add APIs for physical masking and motion programs.
src/actions.c Implement physical mask state + motion program generator (linear + Bézier) ticked from poll loop.
README.md Refresh project overview, testing instructions, and repo layout; reference new tools.
Makefile Introduce configurable F_CPU default (912 MHz), expand tests, and include main.c in hot-path -O2.
KMBOX_NET_FIRMWARE_REQUIREMENTS.md New detailed firmware requirements/spec document for KMBox Net endpoint support.
docs/adaptive-feed-rate-plan.md New design/plan doc capturing timing fixes and adaptive feed-rate approach.
core/startup.c Improve overclock voltage selection logic for >528 MHz; guard against undervolting.
core/imxrt1062_mm.ld Add linker ASSERT to keep .dmabuffers within the non-cacheable MPU window.
.gitignore Ignore Python bytecode artifacts for tools/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
Comment on lines 80 to 83
```sh
pip install pyserial pynput
tools/ferrum_aim_test.py /dev/tty.usbserial-XXXX
```

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5af01a0 — README now points the aim test at the hurra-bridge PTY (~/.hurra-bridge.tty).

Comment thread README.md Outdated
src/humanize.c/.h sub-pixel jitter + dwell
tools/ferrum_test.py protocol smoke harness
tools/ferrum_aim_test.py closed-loop aim test against on-screen dots
Makefile ARM GCC build, 816 MHz, -O2 hot path

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5af01a0 — layout table now reads 912 MHz default (F_CPU-overridable).

Comment thread Makefile Outdated
Comment on lines +34 to +35
# lifetime for clock. Drop to 816000000 to back off; the tempmon thermal
# throttle (main.c) guards against runaway heat at any of these.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5af01a0 — reworded; tempmon flags overtemp via LED/telemetry only and does not downclock. Dropped the 'throttle' wording.

Comment thread core/startup.c Outdated
Comment on lines +99 to +106
#define OC_VOLT_STEP_HZ 28000000u
#define OC_VOLT_STEP_MV 25u
#define OC_MAX_VOLT_MV 1575u
uint32_t voltage = 1250u; // >528 MHz base
if (frequency > 600000000u) {
voltage += ((frequency - 600000000u) / OC_VOLT_STEP_HZ) * OC_VOLT_STEP_MV;
if (voltage > OC_MAX_VOLT_MV) voltage = OC_MAX_VOLT_MV;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5af01a0 — the OC voltage constants are now local consts instead of function-scoped #defines.

Comment thread tools/ferrum_aim_test.py
Comment on lines 121 to +123
except serial.SerialTimeoutException:
pass # firmware backpressure — drop this tick, next one will resend
return False # firmware/bridge backpressure — drop, next tick resends
except serial.SerialException:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3e0c54c — comment now states the step is dropped (not resent); the next tick integrates velocity and computes a fresh delta.

- README: aim-test points at the hurra-bridge PTY (not a raw serial dev);
  layout table shows 912 MHz default (F_CPU-overridable), not 816.
- Makefile: tempmon flags overtemp via LED/telemetry only — it does not
  downclock. Drop the "thermal throttle" wording that implied protection.
- startup.c: the per-block OC voltage constants are local consts now, not
  #defines that leak identifiers into the rest of the file.
@ramseymcgrath
ramseymcgrath merged commit 621eb70 into main Jun 16, 2026
2 checks passed
@ramseymcgrath
ramseymcgrath deleted the fix/humanization-injection branch June 16, 2026 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants