- TypeScript 78.4%
- JavaScript 12.7%
- CSS 5.4%
- HTML 2%
- Python 1.3%
- Other 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
scripts/debrief.ts gains a highlights section computed against the rider's own history up to the debriefed ride: personal bests (longest ride, most kcal, longest steady block, hardest interval power, most hard minutes, cadence lock, lowest settled HR at a wattage, fastest 1-min recovery), milestones crossed, week streak, lifetime totals, the week's minutes bar, the VO2 ladder position, and the fitness trend across checks. The debrief skill is rewritten around it: headline, wins, a stats table, honest watch items, what's next, a send-off tied to the long game. Wins come only from what the script printed; the standing rules (no fan talk, meds only for a flat line, never push intensity) stay. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01NvknhqL86maTxZE4Ga4sgQ |
||
| .claude/skills/debrief | ||
| assets | ||
| docs | ||
| electron | ||
| relay | ||
| scripts | ||
| server | ||
| src | ||
| .gitignore | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| spike.html | ||
| tsconfig.json | ||
| vite.config.ts | ||
BikeBeater
Ride your Rogue Echo Bike (V3 connected console) to a program by ear: the app plays a metronome — one beep per pedal revolution — at the tempo that produces the target console RPM, and distinct audio cues when your actual cadence drifts outside tolerance. The console broadcasts live cadence/power over Bluetooth FTMS; the app compares what you're doing to what the program asks and nudges you back.
A one-time rhythm calibration (Sound card → Calibrate rhythm) maps beep
tempo to console RPM: the console reports flywheel RPM, which is belt-geared
faster than the cranks by an unpublished ratio, so the mapping must be
measured, not assumed. You pedal one revolution per beep through three tempos
(~90 s); the median transmitted cadence per tempo over the beat rate gives the
gear ratio, cross-checked for consistency and snapped to a simple mapping when
it is one (src/engine/calibrate.ts). Until calibrated, the app assumes 1:1
and the metronome will feel wrong — calibrate first.
Run
npm install
npm start # server + desktop overlay, one terminal; close the overlay to stop both
npm start starts the Vite dev server (with the SQLite API) if nothing is
on :5173, waits for it, then launches the overlay; a server already running
is reused. For the pieces separately: npm run dev (server) and
npm run app (overlay).
npm run install-shortcut puts BikeBeater in the Start menu (Windows /
WSL — pin it to the taskbar from there) or in the app launcher (Linux); one
click runs npm start. The repo is the app: a code change is live on the
next launch, nothing to rebuild or reinstall.
Platforms: on macOS, Linux and native Windows the overlay is Electron from
node_modules. Under WSL the overlay must run on the Windows side (a
transparent window doesn't survive WSLg), so install the Windows Electron
build into WSL — npm_config_platform=win32 npm install — and npm start
launches it through scripts/app.sh while the server stays in WSL.
Or open http://localhost:5173 in Chrome or Edge on the Windows side (Web Bluetooth doesn't exist in WSL, and localhost forwarding makes the WSL dev server reachable from Windows). Press the console's Bluetooth/connect state if it needs one, click Connect Echo bike, pick a program, Start ride.
?demo=1— desk demo: fake bike + short program, straight to the ride screen (click the sound chip once; browsers block audio until a gesture).
Hardware spike (do this first on the real bike)
http://localhost:5173/spike.html — connect, pedal a few minutes including
sprints and a full stop, Export JSON. The export answers the open
hardware questions (real notify rate/jitter, flag layout, 0-RPM behavior,
reconnect quirks) and becomes test fixtures in src/fixtures/.
Facts the design leans on
- The Echo's cadence is genuinely measured (reed switch on the flywheel) but the number is flywheel RPM, not crank RPM — fixed belt gearing links them by a constant, unpublished ratio (confirmed on-bike: a 1-beep-per- transmitted-RPM metronome is audibly out of sync with the pedals). Watts are derived from RPM via a fan curve — console-relative, not calibrated power. Program targets and drift detection stay in console units; only the metronome tempo is divided by the calibrated ratio.
- Because watts are a function of RPM, programs target cadence; expected
watts are shown as an estimate once the app has passively learned enough
(rpm, watts) pairs (
src/engine/powermodel.ts, localStorage). - FTMS Indoor Bike Data arrives ~1 Hz as a scalar — beeps set the tempo but cannot phase-lock to crank position.
- Drift alerting is a state machine (
src/engine/drift.ts): EMA smoothing, outer/inner hysteresis bands, dwell times, re-alert cooldown, grace period after interval changes, and separate "stopped" and "stale" states.
Tests
npm test
Covers FTMS frame parsing, the drift state machine (alert/clear/grace/ stopped/stale/overshoot timing), and program stepping. Recorded rides (Download recording / spike export) can be replayed through the detector to tune thresholds without re-riding.