# Robot Arm Workshop

## Learning goal
Choose a target and let the arm solve its joint angles. Find the difference between reaching a point and reaching it safely.

## Model and limits
Analytic inverse kinematics for a yawing base and two planar links. Motion interpolates joint angles; it is not a dynamics or motor model. Collision checks measure the current links against one spherical obstacle. They do not plan a collision-free path, detect self-contact or certify robot safety.

## Primary references
- [MathWorks: inverse kinematics derivation](https://www.mathworks.com/help/symbolic/derive-and-apply-inverse-kinematics-to-robot-arm.html)

## Experiment design
- **Reach the target**: Follow the base rotation and the two-link solution. Parameters: `{"x":2,"y":1.5,"z":0.5}`.
- **Other elbow**: The second branch can reach the same target differently. Parameters: `{"x":2,"y":1.5,"z":0.5,"elbow":-1}`.
- **Too far away**: This target exceeds the sum of the link lengths. Parameters: `{"x":3.5,"y":3,"z":2}`.
- **Check the obstacle**: Watch the current-pose collision measurement. Parameters: `{"x":2.6,"y":1,"z":0.7,"obstacle":0.65}`.

These presets are design predictions to investigate, not claims from a usability study. All readouts, animation positions and exported observations come from the same local model. The 2D view uses the same state. Saved setups restart from their parameters, rather than claiming to resume exact progress. Captured baselines are stored separately and survive resets and preset changes.

## Verification
Run `node --test apps/shared/discovery/tests/models.test.cjs` for numerical invariants, limiting cases, determinism and failure-state checks. Run `node apps/shared/tests/catalog.test.cjs` for routes and references. Browser and keyboard validation must be reported separately from numerical tests; automated tests are not user research.

## Discovery studio interaction layer
The studio adds selectable model objects, ray-based 3D picking, keyboard-accessible object selection, an inspector, scene editing where appropriate, model-specific stepping, and 24 measured challenges across the twelve labs. This is an interaction redesign, not a claim of measured learning improvement.

Most parameter changes now preserve current state. Structural changes explicitly start a new run; the exact per-app list is in `apps/shared/discovery/studio-model.js`. Current settings, independently captured baselines and challenge progress retain separate storage. Undo/redo keeps up to 30 edit checkpoints; the timeline keeps the latest 80 model snapshots. Resuming from a historical state creates a new branch. Saved setups still restart from parameters; they do not claim to retain the timeline or local particle pin edits.

Additional instruments derive from the existing model and cited references. Camera and light sweeps hold the remaining inputs fixed; neural curves separate training and validation; compression uses actual coefficients and recomputed quality samples; traffic uses recorded car positions/speeds; factory queues and utilisation use actual events and busy time. Visible instruments can continue collecting data while the hidden 3D view stops drawing.

Run `node --test apps/shared/discovery/tests/studio.test.cjs` for live edits, preserved state, all challenge solutions, direct manipulation, rewind, picking, instrument exports and controller integration. These DOM/renderer-double checks do not replace rendered browser and device review.


## Detailed 3D apparatus
Shared procedural geometry in `apps/shared/discovery/apparatus.js` adds mechanical housings, lens and iris parts, cables, wheels, connectors, moving machine heads and subject-specific markings. The scene uses bevelled housings, smoother curved geometry, instanced material families, a key/fill/rim light arrangement and bounded soft shadows. Cloth has a procedural weave bump map attached to its original model coordinates.

Full detail can be switched to Essential detail without resetting the model. All movements use model state or model time: wheel markers use road position, machine heads use scheduled job progress, conveyors use accumulated worker time, iris blades use the illustrated aperture, and network packets use their scheduled flights. Signal and energy markers illustrate direction, not measured transit speed. The fan is an indicator of the wind setting; it does not add a fluid simulation. Shells, grippers, cables and surface textures do not change the underlying collision model. The 2D fallback retains the same state and geometry.

Additional checks: `node --test apps/shared/discovery/tests/apparatus.test.cjs`. Geometry, state preservation, bounded batching, semantic selection, pause/rewind consistency and fallback behavior are checked with renderer doubles; browser pixels and device frame rates remain unverified.


## Authored model assets
The physical-object labs now load original Blender-authored GLB assets through `model-assets.js` and `model-worlds.js`: articulated industrial arm, mirrorless camera, car (overview and inspection versions), network router, gantry station, optical laser and companion character. The geometry, authored material surfaces and packed texture maps replace corresponding primitive bodies only after loading succeeds. Source `.blend` files, reproducible Python generators, manifests and inspected Blender renders are retained in `assets/models/` and `scripts/`. Mathematical data surfaces stay tied to their original calculations.

The robot uses actual solved joints; camera iris hinges respond to the aperture and a Cutaway toggle exposes the sensor/rays; car wheels and brake lights follow model position/acceleration; factory heads use scheduled job progress. GLB equipment covers remain illustrative, not new collision or optical physics. Camera cutaway distances are display distances; numerical thin-lens readings remain authoritative. The companion uses the motion model's translation and squash/stretch.

Assets load on demand, repeated equipment shares instanced geometry/materials, and only the selected close-up car upgrades from the lighter road mesh. Failure preserves the diagram and exposes Retry model. Inspect model fits the selected geometry rather than applying a fixed camera distance. Full/Essential detail, pause, rewind and existing models/data remain available. A procedural studio reflection environment lights PBR surfaces without external network requests.

Run `node --test apps/shared/discovery/tests/model-assets.test.cjs` for real GLB parsing, file loading, texture/UV presence, hierarchy transforms, exact solver alignment, cutaways, pause/rewind, fitting and car detail selection. Asset product renders were visually inspected in Blender. Browser pixels and device frame rates are still unverified; product renders are not screenshots of the web app.

## Bespoke rebuild (2026-10)
The shared discovery template is replaced by a purpose-built page. `model.js` and `config.js` are unchanged.

- `arm-derive.js` wraps the model. A settings change calls `model.create` (so goal angles, reachability and limits are the model's own), then carries over the current joint pose and base angle. Motion is `model.advance` in fixed 1/60 s steps, with `model.observe` after each step, so contact on the way is sampled the same at any frame rate. Documented extra quantities: both elbow answers (`model.inverse` with ±1), clearance from the model's own contact rule (link distance < radius + 0.075 m, fixed obstacle centre 1.3, 0.8, 0.35), stretch sensitivity `|dq2/dr| = r / (l1·l2·|sin q2|)`, the obstacle's slice through the target's vertical plane, a joint-limit map and a joint-space contact map.
- `arm3d.js`: three.js stage. It poses the CM-120 GLB (`assets/models/robot`, parsed with `model-assets.js`) from the model's joint angles, with link covers stretched to the link lengths. A procedural arm is shown while it loads or if it fails, with a Retry button. It also draws a draggable target (moves across the floor at constant height), the reach slice facing the target, the other-elbow ghost and the obstacle. It has orbit, zoom, pan, keyboard control, reset view and on-demand rendering that pauses when idle, hidden or off-screen. Pixel ratio is capped at 1.5 on phones and shadows are off there. If WebGL fails or the context is lost, the page falls back to the 2D diagrams.
- `arm-diagrams.js`: the 2D views. **Side view**: a to-scale elevation of the vertical plane facing the target. It shows the links at their collision width, angle arcs with degrees, the reachable half-annulus, the area blocked by joint limits for the chosen elbow, the obstacle slice and contact zone, the ghost, and the distance to the target. When the target is out of reach, it shows the gap and the closest point the tip can reach. You can drag on it, or use the arrow keys on the target. **Joint map**: shoulder angle against elbow angle. The two answers sit on rows of equal reach (labelled with tip distance), with out-of-limit and obstacle-contact poses shaded. It also shows the rest pose and the path of the last move, with measured contacts marked. **Plan inset**: a top view for steering the base direction.
- `app.js`: presets, readouts, explanation and three challenges, all checked against derived state. Solutions were verified with the model: limit 45°, target (2, 2.3, 0); obstacle 0.6, target (2.2, 1.7, 0.6) elbow up; limit 110°, target (0.3, 2.5, 0) elbow down. Questions restart the move from the model's rest pose.
- Limits: the joint map's contact shading assumes the base is already turned to the target. The 3D covers are wider than the 7.5 cm collision rods. On slow devices the animation runs slower than real time (frame time is capped at 50 ms), but the sampled path stays the same.

## Floor rule (2026-10)
`model.js` has no floor, so its elbow-down answer for a low target could put the upper arm and elbow through the floor (owner's case: target 1.50 m out at the lowest height, elbow down = shoulder −51.6°, elbow +124.4°, elbow 1.33 m below the shoulder). The page now adds a floor as its own rule on top of the model, in `arm-derive.js`. `model.js` and `config.js` are unchanged.

- **Where**: 0.405 m below the shoulder pivot (the bottom of the pedestal). The model's height range (0.1–3.8 m above the shoulder) already keeps the target at least 0.505 m above the floor, in every input (3D drag, side-view drag, sliders, arrow keys).
- **What must stay above it**: the arm body, checked as two capsules as wide as the 3D covers (upper arm and elbow housing 0.25 m, forearm and wrist flange 0.19 m; from the GLB part bounds). The 7.5 cm rods are still used for the obstacle. The gripper is decoration past the tip (the wrist centre); the 3D view tilts it like a wrist just enough to keep it above the floor.
- **Choosing the goal**: if the chosen elbow answer is clear (and reachable without passing through the floor), the model gets it. If it would hit the floor and the other answer is clear, inside the joint limits and reachable, the model is created with the other elbow and the page says "Elbow down would hit the floor, so the arm uses elbow up". If neither works, the page hands the model the closest clear, in-limit, reachable pose (grid search on 4° cells, then refined) and shows "Blocked by the floor" with the shortfall.
- **Moving**: the model's motion is a straight line in joint space. Before a move the page samples it every 0.01 rad; if it would dip into the floor, the arm follows a path through a 4° grid of safe poses (flood-filled from the current pose), shortened to as few straight legs as possible. Every 1/60 s step is also checked; a step that would end below the floor is refused and the arm stops. If new link lengths would put the held pose into the floor, the arm restarts from the nearest clear pose.
- **Views**: the side view shades the ground as solid, hatches the reach area that only a through-the-floor pose could reach, draws the arm body used for the check, and cuts the other-elbow ghost off at the floor (red when it would hit it). The floor status has its own line under the title. The joint map shades poses that hit the floor. The 3D ghost and reach slice are clipped at the floor.
- **Checks**: random tests (300 runs × 6 setting changes, mid-move changes included) never put the body below the floor (worst clearance +5 mm). All six presets behave as before (the obstacle preset ends 3.4 cm above the floor). The three challenge solutions above still work.
- **Limits**: capsules are a simple envelope, so a pose can be refused when the real covers would just fit. Detours are safe, not shortest or smoothest. Blocking is a page rule, not a planner certified for a real robot.
