Skip to content

ramble 0.11.0: eggs are earned (reward economy phase 3) - #342

Open
kh0pper wants to merge 20 commits into
mainfrom
feat/ramble-eggs
Open

ramble 0.11.0: eggs are earned (reward economy phase 3)#342
kh0pper wants to merge 20 commits into
mainfrom
feat/ramble-eggs

Conversation

@kh0pper

@kh0pper kh0pper commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Phase 3 of the reward-economy spec (docs/superpowers/specs/2026-09-08-ramble-reward-economy-design.md §4 entire) — the risky phase, deliberately late. The four parts ship together because removing the free egg without the laying floor is exactly the harshness the design exists to avoid.

  • The auto-minted successor egg is gone. It was created from four call sites, two of them pure reads, so merely looking at a screen recreated it.
  • Hatching promotes the oldest non-locked shelf egg instead of minting. With an empty shelf the slot genuinely stays empty.
  • Laying: while you hold no egg anywhere, each local day you end happy banks one; at lay.days (default 14) you lay one yourself. Days need not be consecutive, and the count runs only while eggless — a floor, not a faucet.
  • The prologue, Ramble's first narrative content: one starter egg, once ever, and a second beat when it hatches.

No schema change, no migration, no SCHEMA_GENERATION bump. Laying rides the existing ramble_wallet (kind='layday'/'lay', delta always the literal 1); the prologue flags ride ramble_settings. servers/ and scripts/ are untouched entirely — git diff --stat main..HEAD -- servers/ scripts/ is empty. init-tables.js is comment-only.

Ramble is installed on grackle only — crow primary and r4 have no Ramble bundle.


⚠️ A deviation from binding spec §4.2 that needs an explicit accept or reject

Spec §4.2 says: "When the incubating slot empties, the oldest shelf egg is promoted into it automatically. The user is therefore only ever eggless when they genuinely have none."

As shipped, promoteFromShelf has exactly one caller: hatchIfReady. Three paths leave the slot empty without promoting — a gift or swap arriving while eggless, a lapsed or declined swap unlocking your last shelf egg, and a sync apply that empties the slot while only 'user' shelf eggs remain. In those states the egg waits on the shelf and the pet card offers a one-tap Warm it.

This matters because creditWarmth burns its dedup key whether or not an egg exists: visit_place:<cell>:<isoweek> is consumed with zero warmth banked and cannot pay again that week. So warmth earned with an empty slot is lost even if eggs are sitting on the shelf. Both guides now say so explicitly.

Two alternatives were tried during plan review and both reverted, for reasons recorded in promoteFromShelf's docstring:

  • promote on read paths — races the sync drain; applyRambleEgg's isUserShelve carve-out exists precisely because the replacement egg "follows in the same drain", so a GET landing in that window drafts the egg you just parked and it then out-ranks your real choice on both machines. The mitigation (marking such a promote 'sync') launders provenance that the nest shelf cap, shelf_count, RAMBLE_EGG_REPROMOTE_SQL and a panel label all read.
  • promote from the trade-closing paths — promoting inside expireTrades strands an in-flight completed envelope, so the hand-over UPDATE matches nothing while the received egg still inserts and the user keeps both eggs. A free-egg race, in the phase built to remove the free egg.

The engineering call stands, but it is a spec deviation and it was decided inside the plan rather than escalated. If §4.2 should hold literally, the clean version is promote-on-gift-receipt as its own change, with expireTrades deliberately left alone.

Other recorded deviations

  1. The starter egg is a random UUID gated on an empty egg table, not derived from the Crow identity as §4.4 specifies. That mechanism is impossible: loadOrCreateIdentity generates a random per-instance seed, so crowId differs between your own machines and deriving from it would grant two eggs — the outcome it was written to prevent. A fixed constant is worse: a contact could gift you their starter egg and the ids would collide. A genuine race now resolves through the existing convergence rule (older survives incubating, younger is shelved with its warmth).
  2. creditWarmth keeps credited meaning "this ledger key was new" and writes the row even with no egg. feedAll gates the pet feed on that flag, so reporting false would stop walking, meeting crows and checking in from feeding energy — exactly while eggless, which is when laying needs you happy. That would have made the floor unreachable.
  3. Two eggs per lay is accepted on a multi-instance fleet (both eggless, both crossing the threshold before a sync). Unreachable today; the spare lands as shelf_origin='sync' and does not consume the nest shelf cap. A replicated per-user salt is the cheap fix if a second instance ever installs Ramble.
  4. Laying requires walking. Decay is 10 per 6 h (−40/day) against a maximum of checkin 5 + 3 × chore 8 = 29/day, so a player who never posts a location fix banks three happy days and then sits below the happy threshold permanently. §4.3 framed 14 days as protecting a housebound player; it does not. Operator ruling: leave it — the point of the game is to go outside, and the queued pedometer arc is the proper fix.

Tests

4394 pass / 0 fail, against a 4340 baseline — +54, nothing regressed. Multi-instance coverage for the lay ledger as spec §8 requires, including one test that applies a row with a rewritten created_at and proves the count does not move (layProgress orders by key, never created_at, because applyRambleWallet resolves conflicts with created_at = MIN(...)).

⚠️ A pre-existing flake, not caused by this branch. One full-suite run failed tests/sync-stamp.test.js:174 (concurrent first-ever boot returned an empty instance id). The branch touches nothing in servers/, the file passes 10/10 alone, and a re-run of the whole suite was clean. This branch adds 54 tests, which shifts suite timing and may surface it more often. It deserves its own hunt.

⚠️ No CI gate exists for guide-doc i18n paritytests/i18n-global-parity.test.js covers the translation-key mechanism only. docs/es/guide/ramble.md was updated and checked by hand.

Process

The plan survived five adversarial review rounds (9 / 13 / 6 / 5 / 4 findings) before any code was written; each round's fixes introduced some of the next round's findings, and the two best outcomes were deletions rather than additions. Nine tasks, each with a fresh implementer and a two-stage review, then a whole-branch review that found two blockers — both in the seams between tasks, where the per-task reviews could not look.

Three findings during implementation were tests the plan specified that could not fail against the bug they guarded — source-string greps standing in for behavioural checks. Each was replaced with a pure extracted helper (nextEggVisibility, shouldShowIntro) tested directly, and verified red before green.

https://claude.ai/code/session_01BpkA6EtBWm2rnr7miS8yAV

kh0pper added 20 commits September 9, 2026 13:26
- B1: paintPet now clears rb-nextegg-warm's disabled state whenever it
  repaints the card visible, not just on incubate()'s own failure path —
  a successful warm otherwise leaves the button dead the next time a
  gift, claimed nest, or lapsed swap re-shows it.
- N5: restore the static "One's waiting on your shelf." copy when the
  waiting card repaints, instead of leaving a failed warm's error message
  stuck there for the life of the page.
- N1: correct the rb-pet-nextegg comment, which claimed an auto-mint
  this branch removed.
- N7: correct the prologue comment, which claimed a dismissal control
  that does not exist — only the Go button closes it.

Claude-Session: https://claude.ai/code/session_01BpkA6EtBWm2rnr7miS8yAV
B2: both guides claimed the incubating slot auto-promotes from the shelf
whenever it empties. promoteFromShelf's docstring in
bundles/ramble/server/eggs.js is explicit that it is called from exactly
one place, hatchIfReady — a gift, a swap, or an egg a lapsed swap frees
while the slot is empty all wait on the shelf instead, with a one-tap
Warm it on the pet page. Warmth earned with an empty slot still vanishes
even while eggs sit on the shelf, so the old text materially
overstated how safe it is to walk around without warming a waiting egg
first.

Claude-Session: https://claude.ai/code/session_01BpkA6EtBWm2rnr7miS8yAV
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