Shipaton 2026 · Keep Them Coming Back

The lake texts you when a fish is biting.

worker/src/lib/onesignal.tssendBite()
"name": "bite:willow",
"app_url": "lunker://bite/willow?nid=…",
"ttl": 60
Lunker — the hook mark
Lunkernow
Your rod is twitching at Willow Lake
60s before it escapes.
37
The hook

Sixty seconds. Then it’s gone.

ttl: 60

One line in the OneSignal request. After a minute, the push drops itself.

worker/src/lib/onesignal.ts
The problem · Wina

Every game push gets muted.

Wina mutes every game within a week. Not one push was worth it.

any game · mutedCome back for your daily reward!
Lunker — the hook mark
Lunker · nowYour rod is twitching at Willow Lake
60s before it escapes.

Top: any game (README). Bottom: Lunker.

The reel · one thumb

The push is the play.

  1. A bite arrives while the app is closed.
  2. Tap it: straight into the reel. lunker://bite/…
  3. Hold six seconds. Miss, and it escapes.
Lunker — the reel-tension minigame at Willow Lake, 0:37 left
The line, in order

Seed first, then the push.

cron · */15 minisDue() · isWithinCadence() D1 sent.roll_seedHMAC-SHA256(notification_id) OneSignal RESTttl: 60 · collapse_id lock screentap → lunker://bite/… reel · hold 6 soutcome only, no fish POST /catch-resolvedserver rolls from the seed RevenueCat VC REST v2grant or settled: false HUD re-readgetVirtualCurrencies() seed written before the push leaves RevenueCat webhook (HMAC) → purchase_events → GET /verify — the public ledger a judge can read.
The demo · release APK on an emulator, 2026-09-26

Three real screens.

Lunker — The Water: four lakes, Quarry Pool 1,200, Deep Sea Angler's Pass
11,200 COIN2Angler’s Pass
Lunker — reel at Willow Lake, 0:37 left, hold 0.0 of 6 seconds
30:37 left4hold 0.0s / 6s
Lunker — reel with 0:03 left, ring amber
50:03 · amber
When it may speak · rendered from isWithinCadence()

Only when it matters.

Willow Lakefree · 08–19 · 3–5 a day · up to rare
Reed Shallowsfree · 08–23 · 2–4 a day · up to uncommon
Quarry Pool1,200 COIN · 08–23 · 2–3 a day · up to rare
Deep SeaAngler’s Pass · 18–23 · 1–2 a day · up to legendary

may biteasleep — no lake bitesnever two bites within 1 hourrendered from isWithinCadence() · worker/src/lib/bite.ts

RevenueCat · the economy

The phone decides nothing.

It never names its fish.

POST /catch-resolvedbody
{ app_user_id, lake_id,
  notification_id, outcome }
// no fish · no rarity · no coins

It never moves its balance.

RevenueCat VC REST v2not confirmed
502  { settled: false }
// never a balance the phone made up
The one number · /verify

A number we won’t fake.

Bites answered within 60 s of send, live:

no data yet
  • measured from send, not open
  • unanswered bites stay in
  • fabricated ids rejected
  • clock skew flagged
npm run benchfixture
VALIDATION DATA — NOT SUBMITTABLE
Answered within window:  17 / 39  (43.6%)
Open latency (ms):       p50=9,000  p95=104,000
Fabricated ids rejected: 3
Never-opened (denominator only): 12
Status · 2026-09-26

What isn’t live yet.

The ask

Answer the first bite.

scan → https://lunker.edycu.dev/

Keep Them Coming Back · Best Game
01 / 11
Lunker · v1.2.0