Files
clockwork-hero/docs/PROTOCOL.md
Mystica Venatus 55480b3fc6 feat(face): integrate Idle Task Protocol v1.0 end-to-end
Watchface now syncs with the Nine Hazes idle task system via its own
pkjs client — the three-artifact architecture is operational.

Wire side:
- pkjs fetches GET /v1/queue on launch, applies clock offset, converts
  epoch-ms to seconds for AppMessage, resolves task name from the
  cached dictionary (refetch on version drift)
- GameSnapshot persisted at key 0x30 (protocol-specified); task name
  at 0x32 (face-local nicety)
- AppMessage inbox widened to 256 bytes to carry the full snapshot
  dictionary in a single logical message

Display side:
- Task strip renders two lines: countdown-to-completion + used/36 +
  task count, then task name with "(+)" pending-rewards cue
- Status states render distinctly: FROZEN / ABORTED(code) / LOCKED /
  No hero yet (EMPTY)
- serverDown sentinel key: oxblood "!" icon on failed check; absence
  on successful sync clears it; volatile — never persisted
- Countdown math is server-clock corrected (clock_off), computed
  locally from bound_sec — zero radio to stay fresh

Also:
- ES5-normalized pkjs for SDK webpack 1.15/acorn parser
- Replaced face_packet.{h,c} with snapshot.{h,c} (PACKET_KEY 101
  retired; old blob orphaned harmlessly)
- Mock Task API server seeded with 3-task queue (in-memory)

Verified: seeded queue renders "0:xx 16/36 seg 3 tasks / Gather (+)";
server kill → cached strip + offline icon, no error surface.
2026-09-13 10:18:34 -04:00

5.2 KiB
Raw Blame History

Nine Hazes — Watch Client for OSRIC MUD Idle System

Consolidated record v3.0 (post-pivot, 2026-09-12)

1. Concept

Pebble Time 2 (emery) watchface + companion watchapp acting as remote monitor and manager for the Nine Hazes OSRIC MUD idle task system. NOT a standalone game — all game logic lives on the MUD server. The watch displays server snapshots and submits intents (add/cancel/claim); the server validates and executes. Watch behaves like a normal watchface except during active sync.

2. Critical architecture decision — "face gets its own sync"

Pebble persist storage is per-app sandboxed with NO app↔app communication (verified against platform docs). Therefore the watchface and watchapp each own an independent pkjs sync to the same Task API. No watch-side data sharing exists or is attempted. Shared truth = server.

3. Protocol

Wire contract: "Nine Hazes — Idle Task Protocol v1.0" (separate document, authored server-side; authoritative). Summary:

  • HTTPS + Bearer token (scoped queue:read queue:write claim; token on phone localStorage only, NEVER on watch)
  • GET /v1/dictionary (versioned, 304-style caching via ?v=)
  • GET /v1/queue — full sync in one call (tasks[], used/budget, pend, bound, status, tok.exp)
  • POST /v1/queue/tasks (reqId idempotency), DELETE /v1/queue/tasks/:tid, POST /v1/claim, POST /v1/token/refresh
  • Device protocol: GameSnapshot packed struct (~28 bytes) at persist key 0x30; dictionary blob at 0x31 (watchapp use)
  • Clock correction: all countdowns from bound + clock_off, never raw watch clock
  • Failure ladder: unreachable/malformed → silent, render cached snapshot

4. Artifacts & ownership

Artifact Owner Status
Watchface (C, canvas renderer, widget table) watch team RENDERING BASELINE COMPLETE: custom-canvas data-driven renderer, 10 widget types + 2 spare slots, per-widget layout persist (key 100), parchment/ink palette, task strip rendering, server-down icon, dev.sh tooling. Committed to Gitea.
Watchapp (queue manager UI) watch team deferred (build order item)
pkjs ×2 (face sync, watchapp sync) watch team face pkjs fetches /v1/queue → AppMessage snapshot
Bridge/state server OBSOLETE — deleted. Superseded by MUD gateway + placeholder mock (below)
Placeholder mock server (idle protocol §10) watch team zero-dependency node:http implementation (NOT Express — locked project law: TypeScript, node stdlib only)

5. Widget set (unchanged)

Time, date, watch battery (+days estimator, key 102), phone battery, steps, distance, weather composite (NWS default / Open-Meteo fallback — geographic auto-switch), advisory omens, sleep, Current Task Strip (2-line: line 1 = countdown to task completion + used/36 + task count, line 2 = current task name (+n) pending indicator; offline "!" icon on failed check). All toggleable/movable/font+color stylable. Spare slots reserved (incl. game preview / companion preview, deferred).

6. Design laws (north stars)

  1. Casual glance — no game-triggered interrupts, ever.
  2. Game logic dormant until invited; watch computes only countdown strings.
  3. Fully free face — no protected widgets; "restore default" escape hatch.
  4. Face shows minimal: countdown + queue line only.
  5. Battery-first: zero network in ambient use; one sync per interaction.
  6. Fail-dead-silent: server down → cached data or hidden strip; no errors.
  7. Zero-dependency TypeScript server code; no workers/background radio ever; wakeups = semantic events only, rolling single wakeup (Muninn pattern).

7. Platform/environment

emery only ("targetPlatforms": ["emery"]) · Core Devices PebbleOS SDK · system fonts first (carved from Apache release), OFL TTFs bundled later · Apache-2.0 · Gitea master (auth-gated), GitHub public mirror at v0.1.0 · Liberapay/Ko-fi FUNDING.yml.

8. Reference projects (ideas only, clean-room; unlicensed repos = read-for-protocol

never copy code; Remix-on-CloudPebble ≠ redistribution grant) IdleFantasy (queue model), Doors of Doom (AppMessage), Pixel8 (chunked transfer), Muninn (wakeup/battery sampling patterns), Tamagotchi-API/TamaTime (companion-preview idea, DEFERRED — feature not focus).

9. Build order (current)

  1. ✔ Watchface rendering baseline (committed)
  2. ✔ pkjs fetch + AppMessage + cached-packet strip + offline icon (v1 protocol)
  3. → NOW: idle-protocol adaptation — snapshot struct, mock server, clock-offset countdown (this revision)
  4. Widget wiring: health (steps/distance/sleep), weather (NWS/Open-Meteo)
  5. Watchapp: queue UI (add via presets/segment picker, cancel, claim), wakeup at segment boundary, launch_args deep links, App Glance/timeline pins
  6. Settings phase 2: hosted config page (drag-drop layout, presets, server URL/ token entry)
  7. Polish: PDC sprites/icons (pickaxe, fist, coin, moon), theme, vibration

10. Open items to relay to server team

  • bound/t/sync timestamps in epoch-ms exceed AppMessage uint32 → watchface uses epoch-SECONDS for wire transfer (recommend protocol clarify units)
  • Face receives resolved task NAME from pkjs (v1); dictionary blob 0x31 is watchapp-scoped, chunked transfer deferred
  • Player identity: X-Pebble-Id informational; real auth = MUD token