diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..7a39bb4 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,208 @@ +# Project Record: "Nine Hazes" (working title) — Celtic Idle RPG for Pebble Time 2 + +> v2.0 — post-architecture-revamp. The design was rebuilt on verified PebbleOS +> platform constraints (see §4.4). Game mechanics (§§6–9) carry over unchanged. + +## 1. Concept + +A Celtic-themed idle RPG ecosystem for Pebble Time 2 (emery), built as: +1. **A fully customizable watchface** — the flagship and first artifact. Ten + toggleable, movable, stylable widgets incl. a Current Task strip. Complete + and useful standalone, before any game exists. + 2. **A companion game watchapp** — the idle RPG itself. Canonical save lives + on the watch. Entry via Quick Launch. + 3. **A self-hostable bridge server** — lightweight mirror of game state so the + watchface can display the hero's current task. The developer's instance is + default; players may self-host. Later doubles as multiplayer backend. + + The game is played by queuing 15-minute-chunk tasks and returning to collect + rewards. Battery impact of the watchface must be indistinguishable from a + normal watchface. + + ## 2. Reference Projects (inspiration only — clean-room rules) + + | Reference | What we take | Boundary | + |---|---|---| + | IdleFantasy (tristinbaker) | Task/expedition loop, chunk queue | No code; repo is Android/Kotlin | + | Doors of Doom (msx80) | Combat structure, Range pattern, weighted loot, Entity/Run serialization, instance-vs-definition | Clean-room | + | Pixel8 watchface | AppMessage patterns, persist-key discipline | No code reuse; license unknown | + | My Vampire System | Leveling *system shape* only | No names/lore (IP risk) | + | **Muninn (C-D-Lewis)** | Mechanic of on-watch battery-days estimation: slow sampling, moving-average drain rate, charge-event recalibration, 3-sample cold-start ramp. Also: Wakeup-not-Worker, rolling-single-wakeup scheduling, wakeup-collision retry | Study only; no code copied (repo has no LICENSE file) | + | **freakified/pebble-calculator** | Existence proof that TouchService works on emery in the current SDK | API reference only | + + ## 3. Platform & Environment + + - **Target: emery only** (Pebble Time 2, 200×228 color). `"targetPlatforms": ["emery"]` + - **Toolchain: Core Devices PebbleOS SDK (Apache-2.0)**, local builds: `pebble build --emery` + - **Docs live at developer.repebble.com** — the project verified against these + - Custom fonts: bundled OFL TTFs at compile time (system fonts proprietary) + - **License for our code: Apache-2.0** + - **Server: TypeScript, zero dependencies**, pure `node:http` + stdlib. TLS + terminates at a reverse proxy (documented deployment pattern). State is + inspectable JSON files, atomic write-temp-rename. No database. + - Repo topology, donations: unchanged from v1 (Gitea master, GitHub mirror at + v0.1.0; Liberapay/Ko-fi + FUNDING.yml) + + ## 4. Architecture — three artifacts, one protocol + + ### 4.1 Topology + + ``` + ┌──────────────┐ PUT state ┌─────────────┐ GET packet ┌──────────────┐ + │ GAME WATCHAPP │ ───────────▶ │ SERVER │ ◀──────────── │ WATCHFACE │ + │ (truth) │ (JS, when │ (mirror, │ (JS, throttled│ (display only)│ + │ │ running) │ never │ poll) │ │ + └──────────────┘ │ authority) │ └──────────────┘ + └─────────────┘ + ``` + + - **Game watchapp** owns ALL game truth in its per-app persist storage. + Full buttons + TouchService. Entered via Quick Launch (can be a single + tap on current hardware). Presence via App Glance line and timeline pins. + - **Watchface** is pure display: no game logic, no input handling for game + purposes, fed by a cached server packet. End-timestamps are fixed at + scheduling, so the countdown is computed locally each minute from the + cached packet — zero radio needed to stay fresh. + - **Server** mirrors the small "face packet" (task name, end timestamp, + queue fill, sync_version). Game watch = source of truth, so last-write- + wins is correct, not a bug. Token in auth header, never query string. + + ### 4.2 Server (self-hostable by design) + + - Endpoints: `GET /v1/state/:player_id`, `PUT /v1/state/:player_id`. + Token auth, body-size cap (~2 KB), 401 on bad token, silent throttle. + - `sync_version` in every packet, forward/backward tolerant — strangers run + servers, coordinated upgrades are impossible. + - Contract lives in-repo as `/docs/PROTOCOL.md` (public spec for self-hosters). + - Server-down semantics: watchface renders cached data or hides the strip; + game is fully playable. **Fail-dead-silent, always.** + - Self-hosting = the privacy pitch for this audience. Developer instance is + default; donor-funded upkeep. + + ### 4.3 Wake cycle (game app only) + + - **No worker, no background process — ever.** Game logic is dormant until + invited (user opens app) or a user-enabled semantic wakeup fires. + - Wakeups launch the app to the FOREGROUND (visible blip ~1–2 s), it computes, + updates App Glance + pushes packet/pins via its JS, and exits cleanly + (AppExitReason). No invisible execution exists on PebbleOS. + - **Semantic events only** — whole-task completion, milestones worth pinning; + typically a handful per day. Never per-chunk. Never per-minute. The + API *permits* waking every minute via rebooking; the design forbids it + (violates Laws 1 and 3). + - **Rolling single wakeup**: exactly one pending event (next semantic event), + rebooked each cycle. Handle `E_OUT_OF_RESOURCES` and collision retry + gracefully (Muninn pattern). Max 8 pending / 1-minute spacing per docs. + - Wakeups are an *optimization over* lazy check-in, never a dependency. + Must tolerate "no wakeups ever fired." + - Default OFF or "pins only"; user opt-in. Missed-sample tolerance required. + + ### 4.4 Platform constraint ledger (verified — do not re-litigate) + + 1. Watchfaces receive NO button or touch input. UP/DOWN/BACK/SELECT are + OS-owned on the face (timeline/health/menu); watchfaces cannot push + interactive secondary screens into usability. Double-tap-accel entry was + considered and rejected as a user nightmare. + 2. Persist storage is per-app sandboxed. No app↔app sharing on watch, in the + Pebble phone app, or anywhere else. The only sanctioned cross-binary + bridges: timeline pins (with openWatchApp launch args), AppMessage + (phone↔own binary only). + 3. Timeline is write-only/push-only to apps; watchfaces cannot read pins. + 4. Quick Launch assigns apps (and OS functions) only — not watchfaces. + 5. PebbleKit JS lifecycle is tied to its watch binary: spawns at launch, + killed at exit. No persistent phone-side service via the Pebble app. + 6. Wakeup ≠ Worker. (Muninn demonstrates both coexisting; we use neither + worker nor any background process.) + 7. AppExitReason (SDK 4.0+) lets a wakeup-launched app exit back to the + watchface cleanly. + + ### 4.5 Input model (final) + + - **Watchface**: no input. All interaction is customization via the phone + settings editor (drag-drop on a virtual 200×228 screen, inspector, + restore-defaults). Touch-to-wake backlight is a user system setting we + never fight. + - **Game app**: standard buttons; TouchService edge zones (upper-right/mid/ + lower-right = UP/SELECT/DOWN analogues) plus the system gesture bridge on + MenuLayer/ScrollLayer where suitable; swipe-dismiss = BACK. Central input + dispatch; every screen declares its actions once. + - **Launch**: Quick Launch binds a button to the game app (single tap on + current hardware). `launch_reason()` detects QUICK_LAUNCH; timeline pin + taps deep-link via `launch_get_args()`. + + ## 5. Watchface Widgets (all toggleable, movable, font/color stylable) + + Ten widgets: Time, Date, Watch Battery, Phone Battery, Steps, Distance, + Weather composite (temp + hi/lo, sub-elements hideable), Weather Advisory + (omens), Sleep Time, Current Task strip (game). + + - Update frequencies user-configurable (health 1/5/15/30/60 min; weather + 15/30/60 min) — battery lever. Clock ticks per-minute only. Redraw on + change only (e-paper). + - Metric/imperial toggle; conversion at render time only. + - **Current Task strip**: renders from cached server packet; countdown + computed locally from end timestamp per-minute; staleness is detectable + and shown honestly ("Return for your reward"). With no packet: hidden or + "No hero yet". + - **Watch Battery**: modes `percent | days | both` (default `both`). + Days-remaining computed ON-WATCH (Muninn-inspired mechanics, own code): + battery samples piggyback on wake-ups the face already performs; + moving-average drain rate; charge events recalibrate; honest 3-sample + cold start (percentage-only until trend exists). Gradient moss→brass→ + oxblood applies to both display modes. **Muninn web ingestion rejected** + (unstable third-party surface; more code + radio than the math). + - Weather/phone-battery arrive via face's own pkjs; JS lifecycle = face + foreground (poll on JS wake, not fixed timers). Cache last-good in persist. + + ## 6.–9. Game Mechanics, Settlement, Theme + + **Carried over verbatim from v1 — FINAL, locked.** Idle pillar (15-min + chunks, 36-chunk budget, lazy resolution, deterministic seeds, items-as-XP, + skills-only), combat pillar (barrows, gear-defined builds, agility-as-armor, + blackout/permadeath), settlement chain, Celtic theme. Unchanged. + + ## 10. Multiplayer & Server Evolution (deferred) + + Solo-first. The bridge server's `player_id`+token becomes the account seed. + Phased: trade codes → shared-storage rivals → async competition / + leaderboards — all served by the same self-hostable TS server, which is + also the donation-sustainability story. No P2P on Pebble (established). + + ## 11. Design Laws (north stars) + + 1. **Casual glance game — benefit, not chore.** Nothing demands a session; + no interruptions; the watchface never buzzes for game reasons. + 2. **Game logic is dormant until invited.** Math only at check-in / enqueue / + cancel / collect / opt-in semantic wakeups. **No background process.** + 3. Fully freeform face; "restore default" escape hatch. + 4. **Never load-bearing externals.** Server down, Muninn gone, phone away — + the game plays, the face degrades gracefully and silently. + 5. Verified-platform-only features: if the docs don't permit it, we don't + ship it (see §4.4 ledger). + + ## 12. Status & Next Steps + + **Done:** full design spec; architecture verified against current SDK docs; + server language (zero-dep TS) and self-hosting model locked; battery-days + decision (self-compute) locked. + + **Build order:** + 1. **Watchface skeleton** — `main.c`: widget table (10 types), renderer, + layout persist (own key, own version byte), placeholder task strip, + clock/date/battery(percent) live, stubs for the rest + 2. **`/docs/PROTOCOL.md`** — packet/API contract (face-packet schema, + sync_version semantics, failure ladder) + 3. **`server.ts`** — zero-dep TS mirror; curl-testable before any client + 4. **`widgets.c` + font table** — health/weather wiring; battery days- + remaining estimator + 5. **Settings editor** (phase 2, drag-drop virtual face) + 6. **Game watchapp** — `game.c` in full (chunk resolver, enqueue validator, + XP routing, prorating, persist version byte); Play Mode menus; wake + cycle; App Glance + pins + 7. **Content tables** — TASKS/RECIPES/ITEMS (xp_value), founding chain, + MONSTERS[] + 8. PDC sprites, theme skinning, polish + + **First build verification checklist:** `pebble build --emery` + emulator; + TouchService delivery + subscription lifecycle; AppMessage plumbing against + stub server endpoint. \ No newline at end of file