You mute every game within a week — none of their pushes were ever worth it. Lunker sends one kind: a bite. Tap within 60 seconds, hold the needle in the zone for six seconds, and the fish is yours. Miss it and it escapes.
ttl: 60the push expires on its own· 274 tests
0:37 · reeling0:03 · about to escape
The deep link opens the minigame, not a home screen. The roll seed is written to D1 before this request leaves, so the phone never gets to name its own fish.
"name": "bite:willow", "include_aliases": { "external_id": ["…"] }, "contents": { "en": "Your rod is twitching at Willow Lake\n60s before it escapes." }, "app_url": "lunker://bite/willow?nid=…", "collapse_id": "bite:…", "ttl": 60
One mechanic, one loop. Every rule below is enforced by the server, and every chip names the exact call.
Every 15 minutes the cron asks who is due. A lake only bites in its own hours, never while you are asleep, and never twice within the hour.
may biteasleep — no lake bitesnever two bites within 1 hourrendered from isWithinCadence() · worker/src/lib/bite.ts
"60s before it escapes" fits Android's ~65-char budget, and OneSignal drops the push at 60 s. Two bites can never stack.
ttl: 60collapse_id/catch-resolved has no fish field. The server rolls from a seed it wrote before the push left.
Every grant and spend is a server-side RevenueCat Virtual Currency call; if RevenueCat does not confirm, the catch comes back settled: false. The HUD re-reads after invalidateVirtualCurrenciesCache().
Quarry Pool for 1,200 COIN, debited atomically. Deep Sea with the Angler’s Pass entitlement. Each unlock fires a OneSignal event a Journey can enter on.
anglers_passlake_unlocked
“When this section was written (2026-09-26) the ledger held no rows, and a figure here would be a guess.”
— README · Engineering Rigor
“Which is why OneSignal had sent zero notifications. CI was green because the suite stubbed
— README · What we got wrongfetchwith arrow functions.”
“Unanswered bites stay in the denominator … ignoring a bite counts against us.”
— README · Engineering Rigor
No — OneSignal has sent 0 notifications. Until 2026-09-26 the Worker called fetch with the wrong this, and the Workers runtime threw Illegal invocation on every request. It is fixed, and tests/fetch-binding.test.js fails on the old code.
Because the live ledger has no rows yet. The number is read from /verify on every visit and is never typed in; an empty ledger renders as “no data yet”, not as a guess.
The server path is built and tested: grants, spends, the atomic debit and the settled: false refusal. The COIN currency has not yet been created in RevenueCat, so no grant has settled. When it is, the balance you see is RevenueCat’s word, not the phone’s.
No. The client never sends a fish. The server rolls against the lake’s committed table with a seed derived from HMAC-SHA256(notification_id, ROLL_SERVER_SECRET), written before the push leaves.
The fish escapes and your streak survives. The bite still counts: its telemetry row is written at send time, so an unanswered bite stays in the denominator of the number we lead with.
Not from a store yet — the Google Play listing is not live, and there is no demo video yet. The buttons say so instead of 404-ing. The code, the tests and the ledger are public now.