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.
This commit is contained in:
Mystica Venatus
2026-09-13 10:18:34 -04:00
parent 9c45250f4c
commit 55480b3fc6
14 changed files with 567 additions and 118 deletions

89
docs/PROTOCOL.md Normal file
View File

@@ -0,0 +1,89 @@
# 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