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)
- 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).
- Celestial types, chosen at creation, re-choosable. v1 ships Star and Moon. Planet and Comet are a designed-in fast follow (the
type → painterseam 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. - Nebula = the start / re-forming state. Before a type is chosen (fresh, or mid re-choose) the companion is a glowing nebula.
- 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."
- 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
- Customization = name + color + type. Color is free and instant (curated celestial palette). Name is free (≤ 20 chars, existing rule).
- 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.
- 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).
- "My Cosmos" is the feature name. The setting is "My Cosmos" (replaces "My Companion").
- 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, addtype+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
CompanionTypeenum (nebula,star,moon;planet,cometreserved). - A
companionPainterFor(type, stage)factory returning aCustomPainterfor 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
colorKeyresolves 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 thespecies='sprout'string; nebula until a body is chosen).colorKey: String(new first-class field; curated celestial palette, theme-independent likeHouseholdMember.colorKey).name: String?(unchanged, ≤ 20).adornments: List<String>(replacesequipped; owned+equipped adornment ids).- Growth stage stays a pure function of lifetime completions (
stageFor, thresholds 0/10/30) — unchanged. CompanionViewaggregate keeps its shape;dewdrops*fields rename tostardust*(earned/spent/balance), stage, ownedAdornmentIds, ambient tier.- The adornments catalog replaces
kCompanionCosmetics(a small static celestial set withstardustCost, 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 renamedpurchaseCosmetic/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: dropCHECK (species = 'sprout'); rename/replacespecies→type text not null default 'nebula'; addcolor_key text; renameequipped→adornments 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'sstardust*fields at the adapter boundary — this avoids churning the SP‑C trigger functions (credit_companion_earn,enforce_companion_zero_floor).companion_ledger.cosmetic_idconceptually 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 barecatch, neverError). - 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),
stageForthresholds, the not-created sentinel. - Adapters: the field-rename compile unit across all 5; cloud routing + RLS live arms (renamed).
- Widget/DS: the
type → painterfactory 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)
- Adornments in v1: ship all four — ring · orbiting moonlet · sparkle trail · aurora glow.
- Ledger reset: at cutover, truncate all three companion tables (
member_companion,companion_earn,companion_ledger) for a clean Stardust slate. - Prompt cadence: show the "Meet your companion" card each session until acted on (created or dismissed); thereafter it lives only in My Cosmos.