Skip to content

Operator speed control + max-rate (50 Hz) relay sampling#3

Merged
jacklange22 merged 1 commit into
mainfrom
feat/relay-speed-control
May 29, 2026
Merged

Operator speed control + max-rate (50 Hz) relay sampling#3
jacklange22 merged 1 commit into
mainfrom
feat/relay-speed-control

Conversation

@jacklange22

Copy link
Copy Markdown
Owner

Summary

Follow-up to #1/#2. Adds the two things requested:

  1. Speed control — a Speed slider on the demo page. Drag right for faster, left for slower/cinematic. It maps inversely to the relay's video_duration_s over [6 s fastest .. 45 s slowest] and shows the resulting "~N s per loop". (For this demo, speed = how fast the spine traverses the whole path = the motion duration; the servo profile stays un-throttled so it tracks the path at whatever speed you set.)
  2. Max-rate sampling — the dense sample + bus-write rate is raised to 50 Hz everywhere (preset, YAML, config defaults). 50 Hz is the realistic 8-servo sync-write ceiling — the highest rate that reads as smooth without making the demo run in slow motion (above the bus's sustainable write rate the absolute-paced loop falls behind a schedule it can't meet).

Also fixes the now-stale speed advisory: the relay streams one dense write per sample (not waypoint_count), so it now warns only above ~55 Hz and explains the slow-motion failure mode.

Test plan

  • tests/test_two_segment_slow_motion_demo.py, tests/test_sci_fi_waypoint_relay.py, tests/test_two_segment_motion_patterns.py149 passed
  • Speed mapping verified: slider 100 → 6 s, 1 → 45 s, 50 → ~26 s; syncs back from a 20 s config to slider 64; preview shows ~1000 bus writes @ 50 Hz
  • Hardware: confirm 50 Hz is smooth and the speed slider feels right; if the bus can't sustain 50 Hz the motion will run slow → drop command_rate_hz

Reviewer notes

  • If 50 Hz makes the demo run slower than the slider says, the bus topped out below 50 Hz — lower command_rate_hz in the YAML.
  • Scoped to the demo; the unrelated in-progress two_segment_workspace_repeatability_outputs.py change is left out.

🤖 Generated with Claude Code

…pling

- Add a Speed slider to the demo page (drag right = faster). It maps inversely
  to the relay's video_duration_s over [6 s fastest .. 45 s slowest] and shows
  the resulting "~N s per loop". Syncs back from config and is blocked during
  sync to avoid feedback loops.
- Raise the dense sample + bus-write rate to 50 Hz everywhere (preset, yaml,
  config + from_dict defaults). 50 Hz is the realistic 8-servo sync-write
  ceiling = the highest rate that reads as smooth without making the demo run
  in slow motion (the absolute-paced loop would otherwise fall behind a
  schedule the bus can't meet).
- Fix the now-stale speed advisory: the relay streams one dense write per
  sample (not waypoint_count), so warn only above ~55 Hz and explain the
  slow-motion failure mode. Update the matching test to 80 Hz.

149 demo/relay/pattern tests pass. Speed mapping verified (100->6 s, 1->45 s,
roundtrips through sync) and preview shows ~1000 writes @ 50 Hz for a 20 s loop.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jacklange22 jacklange22 merged commit 356e4fe into main May 29, 2026
1 check failed
jacklange22 added a commit that referenced this pull request May 29, 2026
…ore close-out

main carries the slow-motion demo work as the squash-merged 356e4fe (#3),
while this branch still had the original unsquashed d74752b of the same
work, so GitHub could not auto-merge PR #6. This commit aligns the five
overlapping slow-motion files byte-for-byte with origin/main (verified:
the four pure slow-motion files are identical to origin/main; experiment_pages.py
keeps the new full_factorial_grid work on top), resolving the divergence so
the squash merge applies cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jacklange22 added a commit that referenced this pull request May 29, 2026
…ial workspace grid (#6)

* feat(slow-motion-demo): operator speed control + max-rate (50 Hz) sampling

- Add a Speed slider to the demo page (drag right = faster). It maps inversely
  to the relay's video_duration_s over [6 s fastest .. 45 s slowest] and shows
  the resulting "~N s per loop". Syncs back from config and is blocked during
  sync to avoid feedback loops.
- Raise the dense sample + bus-write rate to 50 Hz everywhere (preset, yaml,
  config + from_dict defaults). 50 Hz is the realistic 8-servo sync-write
  ceiling = the highest rate that reads as smooth without making the demo run
  in slow motion (the absolute-paced loop would otherwise fall behind a
  schedule the bus can't meet).
- Fix the now-stale speed advisory: the relay streams one dense write per
  sample (not waypoint_count), so warn only above ~55 Hz and explain the
  slow-motion failure mode. Update the matching test to 80 Hz.

149 demo/relay/pattern tests pass. Speed mapping verified (100->6 s, 1->45 s,
roundtrips through sync) and preview shows ~1000 writes @ 50 Hz for a 20 s loop.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(two-seg demo): make penprobe lookup demo run on servo_only-derived maps

The two-segment penprobe chasing demo could not run on a map built from the
big collected dataset: that dataset is servo_only and never recorded a
physical_assembly, so the built map carries empty bottom/top segment keys.
The controller's check_compatibility() treated empty keys as a *mismatch*
against the live bench (segment_b/segment_a), blocking the demo in precheck.

Distinguish an UNKNOWN (empty) map assembly from a genuine CONFLICT:
- Controller (two_segment_lookup_controller.py): add
  LookupControllerConfig.allow_unknown_map_assembly (default False = strict).
  check_compatibility() now reports bottom/top_segment_key_unknown_in_map for
  an empty map assignment (relaxable via the flag) while a present-but-different
  assignment still hard-blocks as ..._mismatch. Add map_assembly_is_unknown.
- Demo experiment (two_segment_penprobe_lookup_demo.py): add
  allow_unknown_map_assembly (default True, mirrors allow_unknown_map_tip_tool),
  thread it into the controller, and record a loud map_assembly_unknown warning
  + flags into the run metrics. A genuine conflict is still blocked; servo IDs,
  per-servo tick envelope, nearest-distance hard stop and current limits are
  unchanged.

GUI (TwoSegmentPenprobeLookupDemoPage): add "Use Latest Built Map" + "Browse…"
buttons (scan data/experiments/two_segment_workspace_lookup_maps/), a CLI
build hint, and an "Allow unknown bottom/top assignment" checkbox so the demo
is operable without hand-editing the map path.

Verified end-to-end against a real map built from
20260526_235950_two_segment_collect_pose_command_dataset (2493 points): the
demo runs, issues commands for on-manifold targets, and records the warning.

Tests: controller unknown-vs-conflict matrix, demo runs on an unknown-assembly
map (and blocks when opted out), and a GUI page-construction test. Also add the
lookup + penprobe-demo test files to scripts/run_tests.sh two-segment so they
run in the bench-day net (267 pass). quick (126) and hardware-safe green.

Note: pre-existing unrelated failure test_shipped_system_yaml_carries_slowdown_profile
expects default_profile_velocity>=5 but the operator set it to 3; untouched here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(two-segment-workspace): full_factorial_grid target mode — equally-spaced N-per-axis lattice

Adds a fourth target-generator mode to the two-segment workspace
repeatability experiment alongside workspace_latin_hypercube (default),
rings_and_axes, and grid_subsample.

full_factorial_grid lays out N equally-spaced levels on every one of the
4 command axes (bottom_x, bottom_y, top_x, top_y) and takes the full
Cartesian product — a regular 4D lattice that covers the entire
[-amp, +amp]^4 box with the corners included (np.linspace endpoints, not
LHS midpoints). With N=3 the per-axis levels are exactly [-amp, 0, +amp],
the minimal grid that hits the centre, every face, edge, and corner.

Because N-per-axis in 4D is N^4 targets, the per-axis resolution is an
explicit knob (grid_points_per_axis, default 3 -> 81 targets) rather than
silently disagreeing with target_count:
  - from_dict derives target_count = grid_points_per_axis ** 4 when grid
    mode is active, keeping the round-robin visit plan, planned-visits
    math, and the thesis-validity check (planned_target_count >=
    target_count) all consistent.
  - grid_points_per_axis is clamped to [2, 10]; 10 -> 10,000 targets is
    the practical ceiling (the value the operator floated).
  - the lattice is deterministic (no RNG) — fully specified by N + amplitude.

GUI (TwoSegmentWorkspaceRepeatabilityPage):
  - "Full Factorial Grid (N per axis, equally spaced)" added to the Target
    Generator dropdown with an explanatory tooltip.
  - New "Grid Points / Axis (grid mode)" spinbox (range 2-10) with a
    tooltip spelling out N^4 (3->81, 4->256, 5->625, 10->10,000).
  - In grid mode the grid spin is the live control and Target Count is
    read-only (it shows the derived N^4); other modes do the reverse.
  - _on_target_generator_changed / _on_grid_points_per_axis_changed publish
    the derived target_count immediately and re-sync the page.

The summary text writer now reports grid_points_per_axis (annotated with
"targets = N^4" in grid mode). The run metric grid_points_per_axis is
recorded for every run regardless of mode.

Tests (6 new):
  - target_count derived from N^4, manual target_count ignored in grid mode
  - equally-spaced levels [-amp, 0, +amp] per axis; corners + centre present
  - deterministic regardless of seed; 4^4 = 256 targets
  - grid_points_per_axis clamped to [2, 10]
  - full_factorial_grid present in SUPPORTED_TARGET_GENERATOR_MODES
  - grid_points_per_axis is inert (doesn't touch target_count) in LHS mode

scripts/run_tests.sh quick: 126 passed.
scripts/run_tests.sh two-segment: 273 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* merge: reconcile branch slow-motion files with squashed main (#3) before close-out

main carries the slow-motion demo work as the squash-merged 356e4fe (#3),
while this branch still had the original unsquashed d74752b of the same
work, so GitHub could not auto-merge PR #6. This commit aligns the five
overlapping slow-motion files byte-for-byte with origin/main (verified:
the four pure slow-motion files are identical to origin/main; experiment_pages.py
keeps the new full_factorial_grid work on top), resolving the divergence so
the squash merge applies cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.

1 participant