ramble 0.10.0: heart containers and maximum energy (reward economy phase 2) - #339
Merged
Conversation
added 15 commits
September 9, 2026 10:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
servers/sharing/profile-avatar.jsstill 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.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_GENERATIONbumpHearts ride the existing
ramble_walletunderkind = 'heart', which phase 1 already registered at all five sync sites.git diff origin/mainis empty forscripts/init-db.js,bundles/ramble/server/init-tables.js,servers/sharing/instance-sync.js,servers/shared/sync-stamp.jsandservers/sharing/profile-avatar.js. The migration dry-run therefore has nothing to say.A heart row's
deltais always the integer1— a count of containers, never an energy amount.applyRambleWalletresolves withMAX(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 / 100exactly 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.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.heart.wild.rate(default 40). §6.4 givesheart.wild.daysbut 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.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_placeis 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
200cf834, so +39 tests)check-port-allocation.jsOK — this phase adds no portbuild-registry.mjs --checkOKorigin/main0.9.5→0.10.0in manifest and package.json, registry updated — without the bumprepairInstalledBundleAssetsnever refreshes grackle's installed copy and the deploy silently ships nothingKnown gap, pre-existing
There is no CI check that structurally diffs
docs/guide/*.mdagainstdocs/es/guide/*.md—tests/i18n-global-parity.test.jscovers the translation-key mechanism only. Parity was verified by hand here and holds; the gate does not exist for next time.