# BOB’s Yard — Avatar controller interface, v1

This is a simulator-only browser API. There is no CAN connection, serial connection, physical machine output, remote server, or supported Bobcat control interface in this build. It is a foundation for experimenting with accessible inputs and feedback before adding a separately reviewed hardware bridge.

## Keyboard and gamepad

Click the yard, then use the ISO keyboard layout: W/S for forward/reverse and A/D for steering (left joystick); ↑ lowers the arms, ↓ lifts, ← curls back and → dumps (right joystick). Hold keys together for simultaneous travel and hydraulics. R/F/Q/E have no control bindings. Shift holds precision mode. Space latches the simulated emergency stop; release all controls and press Enter to rearm. C cycles chase/cab/overhead views. Drag/scroll orbits/zooms the chase view. 1 is Tach-up (unlock/release); 2 is Tach-down (couple/lock a nearby aligned tool), while stationary, unloaded, lowered and level. Z selects Turtle travel; X selects Rabbit travel at three times the speed. V sets a carried pallet or the bonus truck down close to the ground.

A standard gamepad uses left stick for travel/steering and right stick for lift/tilt. Left shoulder holds precision, B stops. Vibration is optional, browser/device dependent, and off by default. Keyboard and gamepad adapters share the simulation input path. No real gamepad or M5Stack hardware was available for physical validation.

## Normalized command packet

`window.avatarSim.send(packet)` accepts:

```js
{
  sequence: 1,     // increasing integer; stale/repeated numbers are rejected
  drive: 0,       // -1 reverse, +1 forward
  steer: 0,       // -1 left, +1 right
  boom: 0,        // -1 lower, +1 lift
  tool: 0,        // -1 dump, +1 curl back
  deadman: true,  // false zeros commands and latches a stop
  precision: false
}
```

Every numeric axis must be finite. Values outside [-1,1] are clamped. Receipt time comes from the simulator browser's monotonic clock. External packets expire after 300 ms of wall time; timeout latches a stop. This watchdog is an experimental simulation parameter, not a specification for controlling a real T66.

The first external packet selects the external source and stops Bob. Send a neutral packet, explicitly rearm, then send fresh commands at about 20–50 Hz. Rearming never initiates motion. Repeated or malformed packets do not refresh the watchdog.

```js
let sequence = 0;
const controls = { drive: 0, steer: 0, boom: 0, tool: 0, deadman: true };
const send = () => avatarSim.send({ sequence: ++sequence, ...controls });
send();                              // selects external input and holds motion
avatarSim.rearm();                    // succeeds only with neutral controls
const heartbeat = setInterval(send, 50);
// Change controls only in response to the intended input device.
// controls.boom = 0.2;
// Stop and return to keyboard after the experiment:
// avatarSim.stop(); clearInterval(heartbeat); avatarSim.useKeyboard();
// Release keyboard/gamepad controls, then press Enter.
```

The example assumes a fresh yard with sequence starting at zero. Keep sequence monotonically increasing for the entire session. Reset yard starts a new sequence namespace. Loss of browser focus clears local keys and latches active external/gamepad control; suspended rendering cannot continue motion.

## Read and subscribe

- `avatarSim.telemetry()` returns an independent JSON-serializable snapshot.
- `avatarSim.subscribe(callback)` delivers snapshots at approximately 10 Hz and returns an unsubscribe function.
- `avatarSim.stop()` latches the emergency stop.
- `avatarSim.rearm()` returns true only after neutral-input checks pass.
- `avatarSim.useKeyboard()` selects keyboard input and zeros current motion; a latched stop remains latched.
- `avatarSim.tachUp()` / `avatarSim.tachDown()` return booleans and use the visible unlock/release and couple/lock actions. Both require an armed, stationary, empty machine with neutral commands and low, level arms.
- `avatarSim.setAttachment('bucket' | 'forks')` remains as a compatibility helper. It only couples the requested nearby aligned tool onto an empty coupler; it cannot bypass releasing the current tool. The already-mounted type is a harmless no-op.
- Telemetry `attachment` is `null` with an empty coupler. `tach` reports latch state and the nearest unmounted tool.
- `avatarSim.injectFault(name)` accepts `Control link lost`, `Hydraulic fault`, or `Emergency stop`.

Telemetry includes simulation time, pose in metres/radians, linear/angular velocity, attachment, boom [0,1], tool position, payload in kg, synthetic hydraulic effort [0,1], accepted commands, active source, precision mode, safety state, and delivered material/pallet counts. Forward is local negative Z; Y is up. Positive steering turns right. World yaw follows the 3D scene's right-handed convention.

## Recording

The lab records command and telemetry snapshots at approximately 10 Hz of simulation time. Export creates a local JSON download. No session data is uploaded. A one-hour/36,000-frame cap bounds memory. Resetting the yard clears the recording. This is an analysis log, not deterministic replay: initial material layouts, discrete UI actions, settings history, wall-clock gaps, and random visual particle state are not all encoded as replay events.

## Future hardware boundary

The M5Stack adapter should map accessible inputs into this API and subscribe to telemetry for lights/haptics. Browser Web Serial support varies by browser, so a future local bridge may be appropriate. This version does not guess proprietary CAN identifiers, transmit on a vehicle network, implement real remote control, or replace any machine interlock. The appropriate next hardware phase is simulator integration, followed separately by passive listen-only logging.

## Added yard and instrument telemetry

Snapshots now include `contact: { blocked, type, groundedAttachment, frontLiftM, trackTraction }` (type: dirt, concrete, ground, attachment, pallet, truck, animal, obstacle or null), `attachmentBay: { inside, distance }`, and `vitals: { simulated, engineRunning, rpm, fuelPercent, voltage, oilPressureKpa, hydraulicTempC, coolantTempC, engineHours }`. These are additive v1 fields. Instrument values are synthetic: engine demand drives RPM/voltage/oil pressure, temperature changes slowly, and fuel consumption is informational. Motion stops leave the virtual engine idling; temperatures and fuel do not instantly reset. Approved pile completion sets engineRunning false, freezes fuel use and engineHours, and holds powered motion. Explicit neutral rearm restarts the engine without resuming a job. Panel minimize choices are stored only in the current browser.

`world` adds pallet positions/status, truck state and bonus status, plus dog positions and alert flags. Free falling and already unstable loads continue after motion stops, as do the yard animals. All interactions are simulated.


## Autonomous-job telemetry

Browser telemetry, subscriptions and recorded frames include an `autonomy` object with `active`, `phase`, `mode` (trial/finish), `trialSucceeded`, `approved`, `loadsDelivered`, `totalDeliveredKg`, `remainingKg`, `waitingForDogs`, locked `source`/`delivery`, `deliveredKg`, a readable `message`, and `estimate: { loadsRemaining, totalLoads, bucketKg, percent, secondsRemaining, calibratedCycles, state }`. secondsRemaining is null until a successful trial; time values represent estimated working time and freeze during pauses/dog waits. During an autonomous job `controls.source` is `autonomous`.

Targets are selected through the on-screen panel: **Choose material** opens a whole-yard source picker, and **Choose delivery area** opens the destination picker. Click the highlighted pile or desired ground point; selection restores the previous view. **Cancel** or **Escape** restores the view without replacing that target. No scoop demonstration or exact parking position is required. Bob checks 24 candidate headings per target and searches valid approaches together under one shared planner budget of up to 90,000 state expansions, yielding every 450 expansions. Target selection does not start the job: **Proceed** runs one trial bucket, and **Approve & finish pile** separately authorizes the remainder. Changing either target invalidates approval. Selecting targets is unavailable while an external controller owns Bob and does not discard its heartbeat; select keyboard control first. This is not a hardware output channel.

An external command takes ownership through the existing neutral/rearm gate and interrupts the job. `useKeyboard()`, manual controls and focus loss also pause it. The panel provides explicit Resume. A successful trial waits for separate approval; the finish loop ends in phase finished after lowering the bucket and stopping the engine. Rearm alone never resumes an autonomous job.

`stability: { margin, tipping, tipped }` adds a synthetic lateral balance margin and fall state. `frontLiftM` measures approximate front-track clearance relative to the local supported chassis attitude. Pushing a grounded attachment raises hydraulic effort and can slowly move the chassis. Neither ground contact nor a tilt warning is a validated real-machine limit. After a tip, powered controls and rearm remain blocked until Reset yard; gravity and loose loads continue independently.

## Simulation playback

The on-screen 10× toggle is available only during an approved finish-pile job. Browser telemetry includes `simulationPlaybackRate` (1 or 10). `simulationTime` and autonomous cycle/ETA values remain simulated seconds; the visible ETA divides by the selected rate. Recordings and subscriptions sample at 10 Hz of simulation time (up to 100 callbacks per wall-clock second at 10×). Recorded `timestamp` remains wall time and may repeat within a rendered frame; use `telemetry.simulationTime` for simulated intervals. The one-hour recording cap means one simulated hour. External control, stops, pauses, focus loss and completion restore 1×. The external heartbeat timeout always uses real wall time. Playback does not increase machine speed or change physics timesteps.

## Work-physics telemetry

Additive v1 fields now expose the reduced work model for recording/controller experiments:

- `traction`: `surface`, `leftSlip`/`rightSlip` (0–1, relative belt/ground speed), `leftBeltMps`/`rightBeltMps`, and `availableForceN`.
- `digging`: sampled `depthM`, estimated `resistanceN`, and collected `flowKgS`.
- `hydraulics`: estimated `pressureMPa`, delivered cylinder `flowLpm`, and `relief`. Flow can remain positive if only one of the two implement axes is blocked.
- `soil`: `airborneKg`, current `dumpKgS`, and `totalKg` across ground piles, bucket soil and airborne parcels. Pallets and the bonus truck are not soil mass.

These are synthetic model quantities, not calibrated sensor channels or machine command limits. Full assumptions and reference sources are in MODEL-NOTES.md. Gravity continues to discharge an open bucket and settle existing soil after a motion stop; powered pose stays held. Autonomy preserves a paused delivery’s accounting when gravity empties its unchanged bucket, and waits for settlement before retreat.
