Skip to main content

My Cosmos — Celestial Companion Redesign — Design Spec

Date: 2026-07-25 Status: Draft for owner review Supersedes vibe of: the single "sprout" companion (Companion/CompanionView/CompanionService in client_sdk, ds_companion_creature.dart/ds_companion_scene.dart, companion_layer.dart, the SP‑C durable-dewdrops economy). SP‑C's earn economy is kept and re-themed, not replaced. Brand tie: the moon+house launch icon shipped 2026-07-24 (see [[rewhaven-launch-icon]]) — this feature is that same night-sky atmosphere, made personal.

Goal

Turn the companion from one fixed "sprout" plant into "My Cosmos" — an opt-in, customizable celestial companion that is born as a nebula and grows, chore by chore, into the celestial body the user chose. Each companion has a face and gentle animations, a personal name, and a color. The whole household's companions together are informally "the Cosmos" (a companion-scoped concept only — it does not change the household data model).

Locked decisions (owner, 2026-07-25)

  1. Opt-in creation. No silent auto-create. A dismissible "Meet your companion" prompt invites creation. Configure → create flow; Dismiss → the companion stays off for that member (re-enable anytime in My Cosmos).
  2. Celestial types, chosen at creation, re-choosable. v1 ships Star and Moon. Planet and Comet are a designed-in fast follow (the type → painter seam makes them near-zero structural change). Re-choosing the type melts the companion back to a nebula and re-forms along the new type, preserving earned progress.
  3. Nebula = the start / re-forming state. Before a type is chosen (fresh, or mid re-choose) the companion is a glowing nebula.
  4. Earn-to-grow, kept. Growth is by doing (lifetime chore completions), never regresses. The SP‑C dewdrop economy stays intact underneath and is re-themed to "Stardust."
  5. Per-type stage names. Growth has 3 stages (existing thresholds: 0 / 10 / 30 lifetime completions). Each type names its stages:
    • Star: Mote → Spark → Starling
    • Moon: Sliver → Crescent → Moon (real lunar phases)
    • (later) Planet: Grain → Pebble → World · Comet: Frost → Snowball → Comet
  6. Customization = name + color + type. Color is free and instant (curated celestial palette). Name is free (≤ 20 chars, existing rule).
  7. Stardust sink = adornments. A small set of Stardust-bought, type-agnostic celestial adornments: ring · orbiting moonlet · sparkle trail · aurora glow. Preserves the earn→spend loop; replaces the sprout hat/color cosmetics.
  8. Ambient replaces the "messy room." Idle a while → the scene gently dims / background twinkles fade (capped, forgiving). The companion itself always stays happy — the neglect signal is on the environment, never the creature (unchanged philosophy).
  9. "My Cosmos" is the feature name. The setting is "My Cosmos" (replaces "My Companion").
  10. Clean rename/rebuild, no migration. There are no real users yet (solo testing), so we do the clean cutover: rename the sprout-era vocabulary, drop the species='sprout' pin, add type + color_key, and reset any existing companion rows rather than migrate.

Architecture

1. Rendering — the type → painter seam (custom paint)

Keep the app's custom-paint approach (no new dependency, golden-testable). Refactor the single sprout _CreaturePainter into a small seam:

  • A CompanionType enum (nebula, star, moon; planet, comet reserved).
  • A companionPainterFor(type, stage) factory returning a CustomPainter for that body+stage.
  • Reused as-is (already decoupled in the current code): the face (two eyes + smile — always happy), the idle bob + happy bounce controller, the ambient layer, and the stardust float ("+N" overlay, renamed from dewdrop float).
  • Each type painter draws its stage silhouette + signature features on top of the shared face:
    • Star: Mote (glowing disc) → Spark (4-point sparkle) → Starling (5-point star + orbiting sparkles).
    • Moon: Sliver (thin crescent) → Crescent (fuller crescent + a crater) → Moon (full disc + craters). Uses the same crescent-mask math as the launch icon.
    • Nebula: a soft multi-lobe glowing cloud with a face; also the re-forming transition frame.
  • Color: the companion's colorKey resolves to a body tint + a matching glow; the painter is fully tint-driven (already true today).
  • Adornments are separate overlay painters composited over any body (ring/orbit/trail/aurora), so they are type-agnostic.

DsCompanionScene (creature + ambient + stardust float) stays the composition root; DsCompanionCreature becomes type-aware via the factory.

2. Model (client_sdk)

Reshape Companion (the sprout vocabulary → celestial):

  • type: CompanionType (replaces the species='sprout' string; nebula until a body is chosen).
  • colorKey: String (new first-class field; curated celestial palette, theme-independent like HouseholdMember.colorKey).
  • name: String? (unchanged, ≤ 20).
  • adornments: List<String> (replaces equipped; owned+equipped adornment ids).
  • Growth stage stays a pure function of lifetime completions (stageFor, thresholds 0/10/30) — unchanged.
  • CompanionView aggregate keeps its shape; dewdrops* fields rename to stardust* (earned/spent/balance), stage, ownedAdornmentIds, ambient tier.
  • The adornments catalog replaces kCompanionCosmetics (a small static celestial set with stardustCost, applicable to all types).

3. Service (CompanionService)

Verbs, minimally changed from today:

  • getCompanion(memberId)no longer create-on-read. Returns the companion if a row exists, else a sentinel "not created" view (drives the prompt). Creation is explicit.
  • createCompanion(memberId, {type, colorKey, name}) → new; inserts the row (opt-in creation).
  • setType(memberId, type) → re-choose (nebula re-form; progress preserved — stage is derived, so nothing to reset).
  • setColor(memberId, colorKey) → free, instant.
  • renameCompanion(memberId, name) → unchanged rule.
  • purchaseAdornment / equipAdornment → the renamed purchaseCosmetic/equipCosmetic, now over the adornments catalog + Stardust balance (append-only ledger + zero-floor unchanged).
  • All SP‑C invariants intact: Stardust sourced only from ChoreCompletion, never convertible to tokens, append-only ledger, zero-floor (service + SQL trigger), self-only mutation, parental read.

4. Storage + adapters

The abstract StoragePort companion verbs stay the same shape (get/insert/update companion; get/insert ledger; get/insert earn). Field renames (species→type, equipped→adornments, add color_key) flow through all 5 adapters as one compile unit (in-memory, FakePort, cached, Drift, Supabase).

5. Schema (infra/supabase/migrations/)

One new migration (clean cutover — file-only, prod apply DEPLOY-GATED):

  • member_companion: drop CHECK (species = 'sprout'); rename/replace speciestype text not null default 'nebula'; add color_key text; rename equippedadornments text[].
  • Keep the SP‑C economy tables (companion_earn, companion_ledger) and their triggers as-is internally (they are generic and load-bearing); "Stardust" is a user-facing/SDK rename only. The economy DB columns (dewdrops_amount, dewdrops_cost) keep their names and are mapped to the SDK's stardust* fields at the adapter boundary — this avoids churning the SP‑C trigger functions (credit_companion_earn, enforce_companion_zero_floor). companion_ledger.cosmetic_id conceptually becomes an adornment id — same column.
  • Reset: truncate member_companion (and, if desired, the ledgers) at cutover — no user data to preserve. Earn rows may be left (harmless) or truncated for a clean slate; spec default: truncate all three for a clean start.
  • RLS unchanged (self + parental read; self insert/update; no delete).

6. Create flow + the "Meet your companion" prompt

  • Prompt: a dismissible card on the Today tab, shown per member when no companion exists and the member hasn't dismissed it. Copy: "Meet your companion" + a small nebula preview. Actions: Create / Not now.
    • Create → the create flow.
    • Not now (dismiss) → sets a per-member "companion off" state; the card stops showing. Re-enable in My Cosmos.
  • Create flow (a focused sheet/route): pick type (Star / Moon), pick color (palette), enter a name. Confirm → createCompanion; the nebula condenses toward the chosen type at its current (starting) stage. The companion then mounts in the shell.
  • Default state is off/uncreated (opt-in), replacing today's auto-create + default-on.

7. "My Cosmos" setting (expanded)

A dedicated section/page (More tab entry "My Cosmos"):

  • Enable toggle (mount/unmount the companion for this member).
  • Name (edit).
  • Color (palette swatches, free/instant).
  • Type (re-choose → nebula re-form; progress kept).
  • Ambient intensity (full / subtle / off — reuses the existing intensity setting, now driving the dim-not-mess ambient).
  • Adornments tray (owned + buyable with Stardust; one per slot).
  • Shows growth stage + Stardust balance (read-only), like today's sheet.

8. Ambient (re-themed cozy-messiness)

Replace DsRoomTidiness (leaves/dust) with a cosmic-dim ambient: as days-since-last-completion grows, the scene's background twinkles fade and the glow softens — capped (never worse past the existing 30-day cap) and forgiving (companion stays smiling). Same tiered, deterministic, golden-stable approach; same CompanionMessinessIntensity (full/subtle/off) semantics.

Global constraints

  • One data path: Bloc → Repository → Client facade → Service → Adapter; presentation never imports drift/supabase.
  • SP‑C invariants preserved in service AND schema (append-only earn/ledger, zero-floor debit, Stardust never convertible to tokens, self-only mutation).
  • Custom paint only (no Rive/asset pipeline); everything golden-testable.
  • FVM only; no brand strings in package/class/file names; all copy via Strings; typed errors only (on <SpecificException>; never bare catch, never Error).
  • Companion is per member, device-local enable, keyed on authenticated member id (unchanged).

Scope boundary

In v1: the type → painter seam + Star & Moon (3 stages each) with faces/animations; nebula start/re-form; opt-in create prompt + create flow; name + color (palette) + type customization; Stardust rename; adornments catalog (ring/orbit/trail/aurora) + earn→spend; cosmic-dim ambient; expanded My Cosmos setting; clean rename/rebuild migration (reset, no data migration); tests.

NOT in v1 (fast follow / deferred): Planet & Comet types (seam ready, art+tests later); a household "Cosmos" constellation view (everyone's companions together); adornment expansion beyond the four; any change to the household/member data model; real user-data migration (there is none).

Testing

  • SDK unit: the renamed service (create/setType/setColor/rename/purchase/equip), Stardust earn projection + zero-floor + append-only (SP‑C arms retained, renamed), stageFor thresholds, the not-created sentinel.
  • Adapters: the field-rename compile unit across all 5; cloud routing + RLS live arms (renamed).
  • Widget/DS: the type → painter factory per (type, stage); face/animation reuse; adornment overlays; golden tests for Star & Moon × 3 stages + nebula.
  • Flow (one feature-set flowTest, multiple stories): My Cosmos epic — create-from-prompt (Today card → pick type/color/name → mounts), dismiss-keeps-off, grow-a-stage, re-choose-type (melts to nebula, re-forms), buy-an-adornment. Plus the settings-page path.
  • Baselines must not drop.

Resolved decisions (owner, 2026-07-25)

  1. Adornments in v1: ship all four — ring · orbiting moonlet · sparkle trail · aurora glow.
  2. Ledger reset: at cutover, truncate all three companion tables (member_companion, companion_earn, companion_ledger) for a clean Stardust slate.
  3. Prompt cadence: show the "Meet your companion" card each session until acted on (created or dismissed); thereafter it lives only in My Cosmos.