Skip to main content

Companion creature ("the doubling")

Status: ⭐ Differentiator · 📐 Designed (spec approved 2026-07-18, not yet built)

No companion or dewdrop code exists yet. This page documents the approved design; implementation has not started. Source of truth: docs/superpowers/specs/2026-07-18-companion-creature-design.md.

Purpose & scope

A per-child virtual companion that lives ambiently in the horizon scene each kid already sees. It is the emotional skin on the chore loop: it reacts when a kid finishes a chore, grows as they keep going, and can be fed (with its own currency) and dressed up. It is forgiving — it never decays, gets sick, dies, or guilt-trips.

Strategic frame (Decision C): the companion ships as a motivator in v1 but is architected to become a co-regulation partner — the thing that, in a later release, invites a kid to take a breather on a big task and models calm during an "unwind" step. The co-regulation layer is not in v1 (it is gated on the future unwind-items feature), but the v1 design must not foreclose it.

Tier 1 scope (v1 — what this page covers):

  • One creature per kid, single species, ~3 behavior-gated growth stages.
  • Ambient in the horizon scene; tap opens a focused sheet.
  • Reacts to completions off the existing celebration signal — cross-link celebration.
  • Its own separate soft currency ("dewdrops"), earned from completions, spent only on a small cosmetics set. Never convertible to tokens.
  • Forgiving presence (no decay/death/guilt); a gentle, capped, optional "cozy-messy room" as the only neglect signal — on the environment, never on the creature.
  • Simple-art-first (DS visual language, few states).

Explicitly OUT of v1 (named so scope cannot creep):

  • Horizon Visitors / anti-novelty-cliff system (Tier 2).
  • Co-regulation layer — invites, breathing, unwind presence (Tier 3, gated on the unwind-items feature).
  • Rich Rive rig, large cosmetic catalogs, multiple species.
  • Parent-configurable creature settings.
  • Any dewdrops↔token convertibility — permanently out.

Data model

Projected vs stored

PieceSourceStored?
Dewdrops earnedProjection from ChoreCompletion rows (kDewdropsPerCompletion per completion, default 1)No — derived
Dewdrops balanceearned (projected) − spent (stored), zero-flooredFold, like tokens
Growth stagePure function of lifetime completions against threshold policyNo — derived
Cosmetics ownedDistinct cosmetic_ids in the spend ledgerDerived from ledger
Cosmetics equipped + creature nameGenuine mutable per-kid stateYes
Room cozy-messinessProjection from days-since-last-completion (capped) + intensity settingNo — derived

Two new tables (first real engagement-track migration)

member_companion — one row per kid: member_id (PK/FK), species (fixed single value in v1), name (nullable, kid-set), equipped (cosmetic ids), created_at.

companion_ledgerappend-only, physically separate from ledger_entries: member_id, cosmetic_id, dewdrops_cost, ts. Owned cosmetics = distinct cosmetic_ids; balance = earned − sum(dewdrops_cost).

Why a separate ledger, not a new LedgerEntryKind: the token-fading ADR's spirit is that the two economies must never cannibalize each other. Physical table separation enforces it — dewdrops cannot contaminate the token-balance fold even by accident. Dewdrops keep giving feedback even as tokens deliberately fade, which supports the fade-to-intrinsic moat.

Correctness carry-over: source from ChoreCompletion, not the token ledger

Dewdrops MUST be earned from ChoreCompletion rows via Client.getCompletions({memberId})not from ledger earns. Expectations pay zero tokens and write no ledger entry, so a kid on expectation-only chores would earn zero dewdrops if sourced from the token ledger. Sourcing from completions means every finished chore feeds the creature, token or not. This is the same trap that bit badges (A3) and streaks (A4).

One data path

CompanionBloc → CompanionRepository (cache-first)
→ Client facade → CompanionService (domain rules)
→ Adapter (Drift local + cloud PostgREST)

All economy and growth logic lives in CompanionService behind the facade — the app never computes economy or growth itself. Facade passthroughs: getCompanion, purchaseCosmetic, equip, rename.

Domain rules

  • Earn: each ChoreCompletion grants kDewdropsPerCompletion (named constant, default 1). No explicit "feed" action — fed passively by doing chores.
  • Growth stages (behavior-gated): pure function of lifetime completions. Default thresholds: stage 1 from start, stage 2 at 10 lifetime completions, stage 3 at 30. Growth only advances; it never regresses (forgiving-presence enforced in code, not just art). Cosmetics are purchased with dewdrops; growth is earned through completions — you cannot buy your way up.
  • Purchase validation (three typed failures): insufficientDewdrops / alreadyOwned (idempotent, no double-charge) / unknownCosmetic. Balance is zero-floored at the fold — a race or replay can never drive dewdrops negative.
  • Equip / rename: pure state writes on member_companion. Can only equip an owned cosmetic (notOwned otherwise); name is length-capped and profanity-light-checked.
  • Cozy-messiness: read-only projection — capped function of days-idle + the intensity setting (full / subtle / off). Hard-capped: two months looks no worse than one month. Any completion steps it back toward tidy. Never a failure mode.

Dual gate + RLS

  • A child can read and mutate only their own companion (spend, name, equip).
  • Parents in the household may read a kid's companion; the kid owns it (no parent write to a kid's creature in v1).
  • Service authz AND RLS, per house rule. Two-identity cloud smoke uses the request.jwt.claims technique (the headless publishable key can't attach a JWT).

Presence and wiring

Shell-chrome redesign (2026-07-26). The companion's presence was split in the Profile + Companion-as-Proxy shell redesign. The creature no longer sits bottom-left in the horizon scene as a standalone DsCompanionScene (the old companion_layer.dart bottom scene is retired). Instead:

  1. Creature → top-left of the top app bar as the active member's compact, animated creature (in the shared Shell_AppBar), on Today / Earn / Rewards / Household. The top-bar slot shows the creature only when the companion is device-enabled AND created (enabled && isCreated); otherwise it shows nothing — no emoji/initial fallback in the chrome (owner decision 2026-07-26). Any page actions (e.g. Home print, Household People & Access) render immediately to the right of the companion slot. The bottom-nav Profile tab icon still uses the shared Identity_Avatar resolver (companion when isCreated && enabled, else the member's emoji), so the nav always shows an identity; both surfaces update together on an active-member switch.
  2. Ambient (cosmic-dim / twinkles / glow) → a backdrop atmosphere layer behind the content and in front of the houses/horizon backdrop (z-order: houses backdrop → cosmic ambient → content). The "dim as days-idle grows" behavior moves here.
  3. The "+N" float anchors near the top-left companion on a reaction.

Proxy read-only rule: tapping the avatar opens the companion sheet; when the active member ≠ the authenticated self the sheet (and My Cosmos) is read-only — buy / equip / rename are the owner's self-action and the SDK's self-only mutation forbids them, so the UI hides/disables those affordances while proxied. See Personas and Authorization.

  • Tap opens a companion sheet: creature larger, name (tap to rename), growth stage, balance, small cosmetics tray.
  • Wired into the existing completion/celebration signal (ChoreDoneState): happy bounce alongside the celebration burst + a "+N" float, visually distinct from the token "+N" (different color/glyph) so the two economies read as separate at a glance. No new trigger plumbing.
  • Idle animation: a breathing scale (~±4%) + soft glow pulse on a ~2.6s period, layered on the subtle bob, wherever the creature renders (top-bar avatar, sheet, My Cosmos, nav icon).
  • Reduced-motion: under MediaQuery.disableAnimations, the creature settles to a static happy pose via MotionTokens.durationOrZero (the breathing/glow freeze too). The companion is never a motion problem.

Invariants

  1. Dewdrops are never convertible to tokens — permanently out; table separation enforces it at the data level.
  2. Growth never regresses — forgiving-presence is enforced in CompanionService, not just art.
  3. Sourced from ChoreCompletion, not the token ledger — every finished chore (including zero-value expectations) earns dewdrops.
  4. Cozy-messiness is capped, optional, and on the environment — never on the creature — intensity setting can be off; the maximum neglect state is bounded.
  5. No speculative schema — Tier 2 and Tier 3 add their own tables/services against the same facade; zero pre-added columns for visitors or co-regulation now.

Deferred / open

  • Tier 2 — Horizon Visitors: anti-novelty-cliff system, depends on proven retention data.
  • Tier 3 — co-regulation partner layer: invites a kid to pause, breathing, unwind presence. Gated on the future unwind-items feature.
  • Decay / neglect variant tuning: intensity setting defaults, cozy-messiness tier art thresholds.
  • Multiple species / rich Rive rig / large cosmetic catalogs — post-v1 art investment.
  • Parent-configurable creature settings — not in v1.

Sequencing note

Per the "deeper not wider" positioning: this is one deep feature to finish whole before opening the next front (unwind items → then native calendar). Tiers 2 and 3 are deferred by design and depend, respectively, on proven retention and on the unwind-items feature existing.

  • Token economy — the earn/spend loop the companion sits alongside (separate economy, not part of it).
  • Token fading — the ADR whose spirit motivates the physically separate companion_ledger; dewdrops support the fade-to-intrinsic moat by giving feedback even as tokens thin.
  • Celebration — the existing ChoreDoneState signal the companion reacts off; no new trigger plumbing needed.
  • Child timeline — companion growth events may surface here in a later pass.