Skip to content

ramble 0.10.0: heart containers and maximum energy (reward economy phase 2) - #339

Merged
kh0pper merged 15 commits into
mainfrom
feat/ramble-hearts
Sep 9, 2026
Merged

ramble 0.10.0: heart containers and maximum energy (reward economy phase 2)#339
kh0pper merged 15 commits into
mainfrom
feat/ramble-hearts

Conversation

@kh0pper

@kh0pper kh0pper commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Phase 2 of the reward-economy spec (docs/superpowers/specs/2026-09-08-ramble-reward-economy-design.md, §2.3, §3, §6, phase 2 of §9; decisions D6 and D2). Plan: docs/superpowers/plans/2026-09-09-ramble-hearts-phase2.md.

Walking new ground occasionally turns up a heart container, which permanently lengthens the bird's energy bar — so a longer bar means the bird stays happy far longer between walks. Mood thresholds stay absolute at 60/30, which is what makes a longer bar buy real slack rather than nothing (spec §4.3).

Scope, stated plainly

  • No shop, no wardrobe, no accessory catalogue, no spend path. Kevin confirmed strict phase order; the seed shop remains phase 4. Seed stays earn-only until then.
  • D2's sad-portrait-to-contacts is NOT in this phase. servers/sharing/profile-avatar.js still hardcodes mood "happy" and is untouched. D2's delivery vehicle is the avatar broadcast, whose pacing requirement (§5.4) belongs to the accessories phase — pulling the portrait forward pulls that requirement with it.
  • Phase 1's known follow-ups (unlockedCellsNear's full-table read, the seed cooldown's UTC bucket, the AR view filtering beacons out, spec §2.4's overclaim) are untouched and none is made worse.

No schema change, no migration, no SCHEMA_GENERATION bump

Hearts ride the existing ramble_wallet under kind = 'heart', which phase 1 already registered at all five sync sites. git diff origin/main is empty for scripts/init-db.js, bundles/ramble/server/init-tables.js, servers/sharing/instance-sync.js, servers/shared/sync-stamp.js and servers/sharing/profile-avatar.js. The migration dry-run therefore has nothing to say.

A heart row's delta is always the integer 1 — a count of containers, never an energy amount. applyRambleWallet resolves with MAX(delta), which is only convergent when the value cannot differ between instances for the same key; a hardcoded literal cannot disagree. This is the phase-1 wallet defect, avoided by construction.

What an existing player sees at upgrade

Kevin's ruling: nothing is granted silently. The hash decides identically for every cell, past or future, so cells unlocked before hearts existed keep their hearts as uncollected pips to walk back to.

Verified against grackle's live database (the only instance with Ramble installed): 27 unlocked cells, a live pet row at energy 100, zero heart rows. After deploy the bar reads 100 / 100 exactly as today, the counter reads 0, the pet page says "No heart containers yet", and 14 heart pips appear on already-cleared ground at zoom ≥ 15. Nothing is blank, zeroed, or retroactively taken.

14, not the "about 8" the plan predicted. The whole-branch review ran the shipped hash against the real 27 cells: 13 first hearts plus 2 wild (1 overlap) — 52% of cleared ground. That is +1.6σ on p=1/3, so it is luck rather than a defect, but the plan left heart.rate = 3 on the strength of "about 8" while arguing that carpeting the map is a real failure mode. Recorded here so the density is a judgement on the record. Hearts are taken permanently, so the carpet clears as it is walked and never returns.

Two recorded deviations from the spec

Both follow the precedent of seed.rate, which 0.9.5 added for the same reason, and both are settings rather than constants.

  1. heart.wild.rate (default 40). §6.4 gives heart.wild.days but no rarity for wild hearts. Without a rate, every unlocked cell yields one every 30 days — a 300-cell map would pay 10 a month, "rarely" would be false, and the map would carpet exactly as seed did before 0.9.5.
  2. energy.max.cap (default 300). The spec caps nothing. Uncapped, decay can never reach the 60 threshold, which makes mood permanent and pre-breaks phase 3, whose laying floor is gated on "the bird ends the day happy". 300 is base 100 plus 20 hearts — Zelda's 20-heart cap, and D6 makes that mapping the frame of the currency design.

The energy ceiling is asymmetric, deliberately

An addition stops at the ceiling, but the ceiling never reduces a value already above it. Sync applies entries one at a time in arrival order with no cross-table ordering guarantee, so an instance can hold a synced pet row at energy 150 while it still has no heart rows and computes a ceiling of 100. A symmetric clamp would write that down — on the very next area post, since visit_place is credited on every fix — and last-writer-wins would carry the loss back to the instance it came from, unrecoverably. Decay still brings an over-ceiling bird down; it just is not the ceiling that does it.

Review record

Three adversarial plan-review rounds before any code (15 findings, including a candidate function that would have made one cell in three permanently sterile for wild hearts, and a pinned regression vector whose every entry was a miss), then eight per-task reviews, then a whole-branch review on the strongest model. The whole-branch review approved with zero blocking findings and verified against grackle's live data rather than in the abstract.

One non-blocking finding was fixed rather than deferred: the energy bar visibly shrank at the moment you found a heart (the walk credited +15 against the old ceiling, then the heart raised it, so 100/100 became 100/110). The pickups now run before the walk is credited, so the energy lands in the ceiling the heart just created.

Verification

  • Full suite 4340 pass / 0 fail (baseline 4301 at 200cf834, so +39 tests)
  • check-port-allocation.js OK — this phase adds no port
  • build-registry.mjs --check OK
  • Panel client invariants hold: 0 backticks, exactly 2 markup sinks, unchanged from origin/main
  • Bundle 0.9.50.10.0 in manifest and package.json, registry updated — without the bump repairInstalledBundleAssets never refreshes grackle's installed copy and the deploy silently ships nothing
  • Guide docs updated in English and Spanish

Known gap, pre-existing

There is no CI check that structurally diffs docs/guide/*.md against docs/es/guide/*.mdtests/i18n-global-parity.test.js covers the translation-key mechanism only. Parity was verified by hand here and holds; the gate does not exist for next time.

@kh0pper
kh0pper merged commit 4a5c321 into main Sep 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant