Skip to content

feat(hosts): fixed-rate targets own their cadence — pocketbook declares 30 - #277

Draft
NathanWalker wants to merge 1 commit into
pocket-stack:mainfrom
NathanWalker:feat/fixed-rate-hosts
Draft

feat(hosts): fixed-rate targets own their cadence — pocketbook declares 30#277
NathanWalker wants to merge 1 commit into
pocket-stack:mainfrom
NathanWalker:feat/fixed-rate-hosts

Conversation

@NathanWalker

Copy link
Copy Markdown
Contributor

The migration deferral from #257: the two in-tree off-60 hosts weren't using the mechanism built for them, and the repo had three cadence notions with no single owner. This gives fixed cadences one owner — the target profile — and migrates both flagged hosts.

The ownership chain

TargetProfile.tickHz (new, optional) names the rate a fixed-cadence stock host drives. From there:

  • resolver: copies it into plan.target.tickHz (omitted — not undefined — for rate-less targets, so their plan shape and hash are untouched; the PSP plan pin still passes verbatim).
  • build: a plan carrying a rate bakes it (pocket compile --target pocketbook now bakes 30 with no flag); an explicitly conflicting --hz errors: --hz=60 conflicts with the pocketbook plan rate 30 — the target's host drives that rate. Rate-less plans (ios-dev's per-run --hz staging) keep today's flag flow.
  • host: hosts/pocketbook declares TICK_HZ = 30 before mount (ensure!d, not discarded) and derives its wall step from the same constant — Duration::from_micros(1_000_000 / TICK_HZ), replacing the TICK_MS = 33 approximation that ran ~1% fast.

So bundle and host pair by construction, and the pairing invariant enforces it: the profile is the only place the number 30 lives on the TS side, mirrored by one Rust const the way HOST_ID/HOST_ABI already are.

What this changes on a PocketBook

Every ms-based animation ran at half wall-clock speed on the device (core believed 60, host ticked ~30). Declared, they run wall-true. That is the observable-timing churn flagged in review — no committed tape records a pocketbook run today, so nothing in-tree churns; the change lands on the device, and the hardware checklist in docs/IMPLEMENTATION.md covers re-validating there. Pairing is enforced both ways: an old 60-baked bundle refuses the new host loudly, a 30-baked bundle refuses everything else (native target mismatch / tick-rate mismatch — both reproduced below).

pocket-widget

No in-tree widget runs off-60, so the hazard was the unwired seam: WidgetConfig.tick_hz was a free knob the embedded realm never heard about. Both examples now thread one TICK_HZ constant through the config and the surface declaration (ensure!d), and the tick_hz doc states the duty — a copy-pasted boot stays paired when someone turns the knob. uihost deliberately stays undeclared: it is the minimal identity-less host, and absent __tickHz means the 60 it drives.

Verification

  • bun pocket compile --target pocketbook --manifest apps/hero/pocket.json → plan carries target: { id: pocketbook, hostAbi: 5, tickHz: 30 }, build log reads 30Hz, target=pocketbook; --hz=30 accepted, --hz=60 errors as above.
  • A plan-less --hz=30 hero through render_hero with POCKET_TICK_HZ=30: 180 deterministic non-blank frames; the same bundle on an undeclared host dies with the 30-vs-60 mismatch. The plan-built pocketbook bundle on a non-pocketbook host dies with native target mismatch (bundle=pocketbook, host=desktop).
  • cargo check clean for pocketbook-host (plus cargo clippy, keeping the README's clippy-clean claim true), pocket-widget, note-widget, pocket-stage.
  • bun run test 11/11 stages green (38.4 s); bunx tsc --noEmit clean; bun tests/contract.ts green. New pins: the registry test asserts pocketbook 30 / everyone else rate-less; a resolver test asserts the pocketbook plan carries the rate and its hash verifies.

Series context: #275 (audio formula) and #276 (wasm export + sim coverage) are the sibling #257 follow-ups; all three are independent of each other.

🤖 Generated with Claude Code

…es 30

TargetProfile.tickHz names the rate a fixed-cadence host drives; the
resolver copies it into plan.target and plan builds bake it (a conflicting
--hz errors; rate-less targets keep their plan shape, hash and --hz flow).
hosts/pocketbook declares 30 before mount and derives its wall step from
the same constant, so ms animations run wall-true instead of half speed.
The widget examples wire one TICK_HZ constant through WidgetConfig.tick_hz
and the surface declaration so the two cadences cannot drift.

Co-Authored-By: Claude Fable 5 <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