mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
games: gogobee learns to poll Pete for money it has to move
Pete holds the chips; we hold the euros and are the only one who can move them. This is the loop that turns an escrow row on games.parodia.dev into a real balance change here. It is a poll because Pete cannot call us and isn't going to be able to. Every step is keyed on the escrow guid, because every step can be interrupted in the worst possible place: die after DebitIdem and before the verdict is queued, and Pete re-offers the row, we claim it again, the debit replays as a no-op and reports the same answer. The player is charged once. That is the only property here that really matters, and there is a test that kills us three times to prove it. The verdict rides the queue that already carries adventure facts — it wants the same durability, backoff and parking, so the row now says where it's going rather than the queue growing a twin. Flush sends it at once instead of after a 15s sender tick, because there's a person at the other end watching a spinner. First GET gogobee has ever made to Pete.
This commit is contained in:
356
gogobee_mischief_plan.md
Normal file
356
gogobee_mischief_plan.md
Normal file
@@ -0,0 +1,356 @@
|
||||
# Mischief Makers — paid monster hits on live expeditions
|
||||
|
||||
Working-tree plan doc. Do not commit.
|
||||
|
||||
## The pitch (restated)
|
||||
|
||||
Parodia members spend euros to send a monster — grunt / mob / elite / boss — at a
|
||||
player who is out on an expedition. Orders come from Pete's Adventure web page
|
||||
(and from Matrix). The games room hears that a contract is out and gets a window
|
||||
to help the target… or "help" them. Surviving an attempt pays the target.
|
||||
|
||||
## What the codebase already gives us (verified 2026-07-13)
|
||||
|
||||
| Need | Existing machinery |
|
||||
|---|---|
|
||||
| Currency + escrow | `EuroPlugin.Debit/Credit` (`euro.go:364,:395`), duel escrow pattern (`adventure_duel.go:422,:529,:667`), community pot (`adventure_rival.go:193-241`) |
|
||||
| "Is X out right now?" | `getActiveExpedition` (`dnd_expedition.go:215`), roster already pushed to Pete every 2 min |
|
||||
| Mid-run combat injection | `runHarvestInterrupt` (`dnd_expedition_combat.go:121`) — picks enemy, surprise nick, `runZoneCombatRoster`, full win/retreat/death close-out. **This is the template.** |
|
||||
| Safe-delivery gating | ambient ticker's CAS claim + `hasActiveCombatSession` + quiet-window checks (`expedition_ambient.go:97,:109,:156`) |
|
||||
| Grunt→boss ladder | Arena tiers 1–5 (`adventure_arena_monsters.go`), `arenaMonsterToTemplate` (`:251`); zone pools tag `IsElite` |
|
||||
| Games-room announce | `gamesRoom()` + world-boss announce helpers (`adventure_worldboss.go:528-566`) |
|
||||
| Respond-within-window | duel challenge window + atomic single-winner claim (`adventure_duel.go:43,:85`) |
|
||||
| Survival payout kit | `euro.Credit`, `rollAdvTreasureDrop`, `consumableCache`, renown, world-boss payout bundle (`adventure_worldboss.go:466-493`) |
|
||||
| Pete pipe | durable fact queue, bearer auth, event-type taxonomy; **strictly one-way gogobee→Pete today** |
|
||||
|
||||
Hard constraint (Pete `internal/web/roster.go:23-25`): *"Pete has no route back
|
||||
into the game box's network and we are not opening one."* We honor that: the web
|
||||
storefront stores orders in Pete's DB and **gogobee polls Pete** (gogobee stays
|
||||
the only initiator, same tailnet + bearer pattern as ingest, just a GET).
|
||||
|
||||
## Design
|
||||
|
||||
### Contract lifecycle
|
||||
|
||||
```
|
||||
placed ──announce──▶ open (help/"help" window, default 60 min)
|
||||
│ │
|
||||
│ ├──▶ delivered (next safe tick: target active, no combat,
|
||||
│ │ not in briefing/recap quiet window)
|
||||
│ │ ├── target survives → SURVIVED payout + unseal buyer
|
||||
│ │ └── target defeated → DOWNED consequences
|
||||
│ └──▶ fizzled (expedition ended first → refund minus rake)
|
||||
└── rejected at claim time (funds/eligibility) → never announced
|
||||
```
|
||||
|
||||
- New table `mischief_contracts`: id, buyer_id, target_id, tier, fee, status
|
||||
(`pending|open|delivered|fizzled|rejected`), signed (bool), escalation_count,
|
||||
blessing json, source (`matrix|web`), created_at, window_ends_at, resolved_at.
|
||||
Status transitions via conditional UPDATE (CAS), mirroring `deliverAmbient`.
|
||||
- One live contract per target at a time; delivery fires from a sibling of the
|
||||
ambient ticker after `window_ends_at`.
|
||||
|
||||
### The four tiers — PRICED (M0 done 2026-07-13)
|
||||
|
||||
Monster strength is **relative to the target**: pull from the target's own
|
||||
bracket **zone pools** (grunt/mob = non-elite, elite = `IsElite`, boss = the
|
||||
zone boss). NOT the arena ladder — arena `BaseLethality` is a legacy death
|
||||
chance; arena T5 one-shots L20s and would make every tier an execution.
|
||||
|
||||
**M0 survival sweep** (n=2000/cell, `SimulateCombat` via the class-balance
|
||||
harness builds at full HP, zone-pool monsters, ambush nick on elite/boss;
|
||||
throwaway harness left skip-gated at
|
||||
`internal/plugin/mischief_pricing_sweep_test.go`, env `MISCHIEF_SWEEP=1`):
|
||||
|
||||
| target build | grunt | mob (3 chained) | elite | boss |
|
||||
|---|---|---|---|---|
|
||||
| paladin L1 | 99.8% | 98.2% | 57.6% | 15.3% |
|
||||
| mage L4 | 100% | 97.8% | 49.5% | 0.0% |
|
||||
| mage L8 evoc | 100% | 100% | 56.5% | 0.4% |
|
||||
| fighter L12 champ | 100% | 100% | 100% | 99.9% |
|
||||
| rogue L20 AT (prosolis) | 100% | 100% | 100% | 35.9% |
|
||||
| cleric L20 life (holymachina) | 100% | 100% | 100% | 14.1% |
|
||||
|
||||
Reading: grunt/mob are pure theater (+HP/supply attrition); elite is a real
|
||||
coin-flip for at-bracket targets and trivial for overleveled martials; boss is
|
||||
expedition-ending for casters at any level and a genuine 1-in-3 scare even for
|
||||
the L20 rogue. Caveats: harness builds ≠ real sheets, full-HP-at-delivery is
|
||||
optimistic (mid-run wounds raise real danger), no pets/party/consumables.
|
||||
|
||||
**Prod economy snapshot** (2026-07-13, 23 wallets): median balance ≈ €500;
|
||||
whales prosolis €180k / holymachina €149k / nonk €62k hold 89% of all money.
|
||||
Daily earn: whales ~€1.8–2.3k, mid-tier €20–500, casuals <€10. Reference
|
||||
sinks: lottery ticket €100, duel escrow €1.5–5k, daily share ~€455, endgame
|
||||
shop items €11–45k. Only 5 characters exist as targets (two L20s, L4, two
|
||||
L1s) — the whales are both the richest buyers *and* the only high-bracket
|
||||
targets, so boss-tier is effectively their PvP toy; casuals buy theater.
|
||||
|
||||
| Tier | Fee | Signed (+25%) | Survival payout (of fee) | Extras on survival |
|
||||
|---|---|---|---|---|
|
||||
| grunt | €40 | €50 | 40% → €16 | flavor line |
|
||||
| mob | €100 | €125 | 50% → €50 | treasure roll (standard) |
|
||||
| elite | €350 | €438 | 65% → €228 | treasure roll (elite) + renown |
|
||||
| boss | €1,200 | €1,500 | 75% → €900 | elite treasure + consumable cache + renown + Pete milestone |
|
||||
|
||||
Rationale: grunt under the lottery-ticket impulse line so anyone can play;
|
||||
mob = exactly one lottery ticket; elite ≈ one active day of mid-tier earnings
|
||||
(a considered purchase, and its ~50% at-bracket survival odds are honest
|
||||
stakes); boss in duel-stake territory — whale money, priced like the "end an
|
||||
expedition" button it is against caster targets. Escalation cost = the
|
||||
tier-delta fee; blessings €25 flat.
|
||||
|
||||
Anti-collusion invariant: **total payout (fee + escalations) capped at 75%**
|
||||
in every case, so collusion is strictly dominated by `!baltransfer`, which is
|
||||
free. No danger multiplier needed — the cap does all the work.
|
||||
|
||||
Money flow: survival payout to target, remainder → community pot. Defeat →
|
||||
entire fee to community pot (never back to the buyer — glory only). Fizzle →
|
||||
90% refund, 10% rake to pot.
|
||||
|
||||
Boss-tier friction (0–14% caster survival means "boss ≈ certain maiming"):
|
||||
max 1 boss contract per target per week, on top of the global limits below.
|
||||
|
||||
### Death policy (recommendation: mischief maims, it doesn't murder)
|
||||
|
||||
`runHarvestInterrupt`'s loss path perma-kills (`abandonZoneRun` +
|
||||
`forcedExtractExpedition` + mark dead). For a *purchased* attack that lands
|
||||
while the victim is offline, perma-death feels like paid murder, not mischief.
|
||||
Recommended: on defeat, apply the world-boss HP floor (`worldBossFloorHP`
|
||||
pattern), force-extract the expedition (run-loss seam already exists), drop a
|
||||
chunk of un-banked expedition coins/supplies, +threat. Character lives.
|
||||
**Open decision** — could make boss tier lethal for stakes, but then it needs an
|
||||
opt-in (hardcore flag) or it's a griefing lever.
|
||||
|
||||
### Anonymity + the unseal twist
|
||||
|
||||
Default: contracts are anonymous — "someone in town has put coin on
|
||||
<target>'s head." Buyer can pay +25% to *sign* it (taunt rights).
|
||||
**If the target survives, the contract is unsealed** — Pete names the buyer in
|
||||
the survival bulletin. Risk of exposure is the natural brake on casual griefing,
|
||||
and it makes survival announcements delicious.
|
||||
|
||||
### The games-room window (help or "help")
|
||||
|
||||
On placement, announce in `GAMES_ROOM` (world-boss style) + Pete priority beat.
|
||||
During the window (default 60 min):
|
||||
|
||||
- `!mischief bless @target` — pay a small fee (~€15) for temp HP / +AC on the
|
||||
incoming fight (well-rested-style bundle). Stacks, capped (say 3 blessings).
|
||||
- `!mischief escalate @target` — pay ~half the next-tier delta to bump the
|
||||
contract one tier, max one step total, boss can't escalate. Escalation money
|
||||
joins the payout escrow — piling on raises the target's survival jackpot.
|
||||
- Victim gets a TwinBee DM (first person, per voice rules): word has reached
|
||||
them that someone wants them dead. Pure flavor — expeditions are autonomous —
|
||||
but it lets them rally blessings.
|
||||
|
||||
Blessing/escalation state lives on the contract row (real rows, no phantom
|
||||
per-fight state — lesson from the companion free-lunch bugs).
|
||||
|
||||
### Delivery mechanics
|
||||
|
||||
New `runMischiefInterrupt`, modeled line-for-line on `runHarvestInterrupt`:
|
||||
pick monster per tier table → apply blessings to the roster → surprise nick
|
||||
(attacker's privilege) → `runZoneCombatRoster(fightRoster(target), …)` → custom
|
||||
close-out. Do **not** call `recordZoneKill`/advance zone state — the fight is
|
||||
extrinsic to the dungeon. Party seats fight together and earn seat XP as usual;
|
||||
the survival purse goes to the target (leader).
|
||||
|
||||
Fizzle: if the expedition ends before delivery, refund fee minus 10% rake
|
||||
(deadpan Pete line: the monster arrived to an empty dungeon).
|
||||
|
||||
### Rate limits & eligibility (anti-grief)
|
||||
|
||||
- Target must have an active expedition **and** be level ≥ 3 (or similar floor).
|
||||
- One live contract per target; per-target cooldown 24 h after resolution.
|
||||
- Per-buyer cap: 2 contracts/day. Can't target yourself. Escrow debited at
|
||||
placement via `euro.Debit` (respects the debt floor for free).
|
||||
- Boredom-ticker auto-expeditions: **targetable** (they're real runs and it's
|
||||
funny), but revisit if it feels bad in practice.
|
||||
- Opt-out: none for v1 — being in the world means being in the world — but keep
|
||||
the decision explicitly revisitable. News anonymization (`!news optout`)
|
||||
still applies to Pete-facing names as it does today.
|
||||
|
||||
### Pete web storefront (the reverse pipe)
|
||||
|
||||
**Decision (2026-07-13): mischief UI requires Authentik sign-in on Pete.**
|
||||
Pete's OIDC layer already exists (`[web.auth]`, disabled by default) and the
|
||||
callback already parses `preferred_username` from the ID token
|
||||
(`auth.go:236-250`) — it just isn't persisted into the `pete_session` cookie.
|
||||
**VERIFIED 2026-07-13** on parodia.dev (`matrix-mas-1`,
|
||||
`/home/reala/matrix/compose/mas/config.yaml`): MAS imports localpart with
|
||||
`action: require, template: '{{ user.preferred_username }}'` — so
|
||||
**Authentik username == Matrix localpart**, guaranteed. Identity is free:
|
||||
add PreferredUsername to `SessionUser`, gogobee maps it to
|
||||
`@<username>:<server>` (lowercase it — Matrix localparts must be lowercase,
|
||||
Authentik usernames may not be). No link codes.
|
||||
|
||||
The one-way *network* rule (`roster.go:23-25`) stays intact — both new data
|
||||
flows are gogobee-initiated:
|
||||
|
||||
- **Balances out (push):** gogobee already pushes a roster snapshot every
|
||||
2 min; add euro-balance entries for linked users on the same tick. Pete
|
||||
renders "~€X" and greys out unaffordable tiers. Advisory only; up to 2 min
|
||||
stale is fine.
|
||||
- **Orders in (poll):** signed-in buyer POSTs the order form → `mischief_orders`
|
||||
row in pete.db keyed by preferred_username, status `pending`. gogobee's
|
||||
peteclient grows a poll loop: `GET /api/mischief/pending` (bearer) every
|
||||
30 s, claims each order (`POST /api/mischief/claim`, idempotent on order
|
||||
GUID), then validates in-game.
|
||||
- **Money truth lives only in gogobee:** the authoritative check is
|
||||
`euro.Debit` at claim time. A stale web balance just means an occasional
|
||||
"order bounced — insufficient funds" status (rendered from a fact) + TwinBee
|
||||
DM to the buyer. Pete never writes a balance, so no double-spend surface.
|
||||
|
||||
Order targets: roster board (already live on `/adventure`) grows a "send
|
||||
trouble" button per entry (roster token identifies the target — already
|
||||
stable + non-deanonymizing). Pete renders order status from resulting facts,
|
||||
never from live game state.
|
||||
|
||||
Ops note: enabling `[web.auth]` on prod Pete needs the Authentik OAuth client
|
||||
provisioned + config.toml edit on the box (config is box-only).
|
||||
|
||||
### New Pete event types (deploy Pete FIRST — unknown types 400 → park forever)
|
||||
|
||||
`mischief_contract` (priority: a hit is out, tier, anonymous-or-signed),
|
||||
`mischief_survived` (priority: target thwarted it — unseal buyer here),
|
||||
`mischief_downed` (priority), `mischief_fizzled` (bulletin),
|
||||
`mischief_link` (internal, no render — or handle out-of-band). Deadpan voice
|
||||
throughout; check `adventure_flavor_*.go` for reusable lines before writing new
|
||||
flavor (standing rule).
|
||||
|
||||
## Phases
|
||||
|
||||
- **M0 — price the tiers. DONE 2026-07-13** (single-fight sweep + prod
|
||||
economy snapshot; see tier table above). Follow-up for M1 close-out: re-run
|
||||
the sweep through the *real* delivery path once `runMischiefInterrupt`
|
||||
exists, since full-HP single fights understate mid-run danger.
|
||||
- **M1 — core engine, Matrix-only. DONE 2026-07-13 (uncommitted, not deployed).**
|
||||
`adventure_mischief.go` (tiers/pricing/persistence/eligibility/`!mischief`),
|
||||
`adventure_mischief_deliver.go` (ticker + `runMischiefInterrupt` + close-outs),
|
||||
`mischief_contracts` table, Pete's 4 `mischief_*` event types. 17 tests,
|
||||
4 of them end-to-end through the real fight + euro + extraction.
|
||||
|
||||
Decisions taken during implementation (all inside the plan's intent):
|
||||
- **Bracket monster-selection promoted out of the M0 test** into prod
|
||||
(`mischiefBracketZone`/`mischiefMonsters`), so the sweep and the live
|
||||
delivery now drive *the same* selection code — the fee table can't drift
|
||||
away from the fight it priced.
|
||||
- **Survival is read off the target's HP, not `PlayerWon`.** The engine's
|
||||
timeout is a retreat, not a lethal blow: a target who ran out the clock
|
||||
with HP left held the thing off, and a bought monster that merely outlasted
|
||||
them hasn't earned a maiming. (M0 counted `!PlayerWon` as death, so real
|
||||
survival rates are a touch *better* than priced. Pricing stands.)
|
||||
- **No-perma-death extended to the whole party** (`floorMischiefRoster`, run
|
||||
on BOTH outcomes). A leader can win a fight their friend went down in, and
|
||||
the delivery skips `closeOutZoneWin` — without the floor, the member is left
|
||||
alive at 0 HP, which every `HPCurrent <= 0` gate reads as broken.
|
||||
- **One-live-contract-per-target is a partial UNIQUE INDEX**, not a read-then-
|
||||
write check: placement holds only the *buyer's* lock, so two buyers racing at
|
||||
one victim would both pass an in-code check. The loser is refunded.
|
||||
- **Stale-delivery sweep** (`delivering` + 15 min grace → full refund). Without
|
||||
it a crash mid-fight strands the row: target permanently un-targetable,
|
||||
buyer's money gone.
|
||||
- All contract timestamps bind as Go `time.Time` — never `CURRENT_TIMESTAMP`.
|
||||
The driver stores RFC3339 (`2026-07-14T03:48:52Z`); a SQL-side stamp writes
|
||||
`2026-07-14 03:48:52`, and the two compare lexicographically wrong.
|
||||
- Fizzle DMs + rake, 90% refund; unstageable/stranded contracts refund 100%
|
||||
(our fault, not a bet they lost).
|
||||
|
||||
**M1 close-out sweep DONE 2026-07-13** (`mischief_delivery_sweep_test.go`,
|
||||
`MISCHIEF_SWEEP=1`, n=400/cell, 108 cells through the REAL delivery path:
|
||||
`runMischiefInterrupt` → `runZoneCombatRoster`). Arms: entry HP 100/70/40%
|
||||
(100% = control, reproduces M0 through the new path) × wards 0/3 on elite+boss.
|
||||
|
||||
| target | tier | 100% HP | 70% HP | 40% HP | 70% +3 wards |
|
||||
|---|---|---|---|---|---|
|
||||
| paladin L3 | elite | 84.8% | 73.2% | 62.7% | 80.5% |
|
||||
| paladin L3 | boss | 87.8% | 45.8% | 8.0% | 88.0% |
|
||||
| mage L4 | elite | 66.0% | 29.5% | 4.0% | 64.0% |
|
||||
| mage L8 evoc | elite | 90.2% | 72.2% | 15.0% | 84.2% |
|
||||
| mage L8 evoc | boss | 6.5% | 2.0% | 0.2% | 7.8% |
|
||||
| fighter L12 | boss | 88.2% | 48.0% | 6.8% | 89.8% |
|
||||
| rogue L20 | boss | 19.2% | 2.0% | 0.0% | 18.5% |
|
||||
| cleric L20 | boss | 42.0% | 6.0% | 0.0% | 42.8% |
|
||||
|
||||
(grunt/mob: 100% for everyone except mage L4, who can lose a wounded mob.)
|
||||
|
||||
Three findings:
|
||||
- **The M0 table was priced on a fight we don't deliver.** The control arm
|
||||
diverges from M0 in BOTH directions: up where an engine timeout now counts as
|
||||
survival (paladin boss 15→88%), and *down* where the turn engine loops a
|
||||
boss's full multiattack profile and `SimulateCombat` doesn't (fighter boss
|
||||
99.9→88%, rogue boss 36→19%) — see [[project_sim_multiattack_gap]]. Fees kept
|
||||
(the tiers still do what they were priced to do); this table is now the
|
||||
reference.
|
||||
- **Wounding is the dominant variable, and M0 was blind to it.** Boss at a
|
||||
realistic mid-run 70% HP collapses across the board (fighter 88→48, cleric
|
||||
42→6, rogue 19→2); at 40% it is near-zero for everyone. Boss really is the
|
||||
"end their expedition" button, as priced.
|
||||
- **Wards were too cheap** (see M2 below): 3 of them buy ~+40pp.
|
||||
- **M2 — the window. DONE 2026-07-13.** `!mischief bless @user` (€25, cap 3,
|
||||
+10% MaxHP temp HP each) · `!mischief escalate @user` (pays the tier delta,
|
||||
one step, boss is the ceiling) · victim DM on placement · escalator unsealed
|
||||
alongside the buyer on survival. 6 new tests (22 total).
|
||||
|
||||
Decisions taken during implementation:
|
||||
- **Blessing fee is a pure sink** (community pot), never a purse top-up. If a
|
||||
ward raised the payout basis, warding a friend would become a money-routing
|
||||
move; buying them HP can't be.
|
||||
- **Ward price scales with the contract** — `max(€25, 10% of fee)`: grunt/mob
|
||||
€25, elite €35, boss €120 (€360 to cover someone completely). The close-out
|
||||
sweep measured 3 wards at ~+40pp (fighter vs boss at 70% HP: 48→90%), so a
|
||||
flat €25 let the room halve a €1,200 boss contract for €75 — pocket change
|
||||
against the tier the whole economy rests on. Reads the contract's *current*
|
||||
basis, so an escalation raises the price of saving its target too. The €25
|
||||
floor is knowingly above-market at grunt (3 wards > the €40 fee): nothing
|
||||
needs warding against theatre, and the floor exists to keep a ward an impulse.
|
||||
- **Escalation raises `fee` (the payout basis) AND `paid` together**, so the
|
||||
75% cap and "purse < outlay" survive an escalation untouched — asserted.
|
||||
- **Escalating into boss answers to the boss-per-target-per-week cap.**
|
||||
Otherwise the cap is bought around for the price of an elite plus the delta.
|
||||
- **Both window commands are CAS-then-refund**, like placement: the ward cap and
|
||||
the one-step limit live in the UPDATE's WHERE clause, because a scramble is
|
||||
exactly when four people press the button in the same second.
|
||||
- **The delivery re-reads the contract AFTER claiming it.** The due-sweep's copy
|
||||
is stale by construction — the window keeps writing to an open row up to the
|
||||
claim, so a last-second escalation was paid for and then delivered the *old*
|
||||
tier's monster. The claim is the fence. (Fixed + regression test.)
|
||||
- **The ward is temp HP** (the well-rested mechanism), added to whatever cushion
|
||||
the target already carried and subtracted again after the fight — a bought ward
|
||||
must not eat a home long rest. It refreshes per link of the mob chain; that only
|
||||
affects the one tier the sweep priced as theatre.
|
||||
- Blessers are named on the spot (helping is proud); escalators are anonymous
|
||||
until the survival unseals them, exactly like the buyer.
|
||||
- Fizzled/unstageable contracts do **not** refund blessers. Their €25 was charity,
|
||||
and it went to the pot. Revisit if it stings in practice.
|
||||
- Fixed a pre-existing wall-clock flake in the M1 fizzle test: it drove
|
||||
`fireMischiefDeliveries` with `time.Now()`, which refuses to run inside the
|
||||
±1 h quiet windows around the 06:00 briefing and 21:00 recap.
|
||||
- **M3 — Pete storefront (1 session, cross-repo).** Enable + extend OIDC
|
||||
(persist preferred_username; provision Authentik client), orders table +
|
||||
form + status rendering, balance push, gogobee poll/claim loop. First
|
||||
visitor-facing write path on Pete — auth-gated + rate-limited per user.
|
||||
(MAS localpart == Authentik preferred_username: verified 2026-07-13.)
|
||||
- **M4 — polish.** Notoriety/renown for buyers, survival streaks → milestones,
|
||||
digest lines, boredom-expedition policy review, tune fees from live data.
|
||||
|
||||
## Decisions — ALL CONFIRMED by reala 2026-07-13
|
||||
|
||||
1. **No perma-death anywhere** — mischief maims: HP floor + force-extract +
|
||||
un-banked loot/supply loss + threat bump. Boss tier included.
|
||||
2. **Anonymous by default; survival unseals the buyer** in Pete's bulletin;
|
||||
+25% to sign openly.
|
||||
3. **Fizzle refund 90%**, 10% rake to community pot.
|
||||
4. **Targets: level ≥ 3 with an active expedition**; boredom auto-runs are
|
||||
fair game. One live contract per target, 24 h post-resolution cooldown,
|
||||
2 contracts/day per buyer, 1 boss/target/week.
|
||||
5. **Fee/payout table as priced above** (M0 sweep + prod economy).
|
||||
|
||||
## Standing-rule checklist
|
||||
|
||||
- No new `dnd_`-prefixed files/tables → `adventure_mischief*.go`, `mischief_*` tables.
|
||||
- TwinBee first-person; Pete deadpan third-person announcer.
|
||||
- Plan doc stays working-tree only.
|
||||
- Pete deploys before gogobee whenever event types change.
|
||||
- Sim sweeps: control arm + n≥750; run heavy sweeps on Parodia/millenia.
|
||||
- Any loader/bootstrap added must keep its backfill as a one-shot bootstrap.
|
||||
Reference in New Issue
Block a user