Rewards, pickups and progression contract¶
Status: This is a dated source and design baseline, not proof that reward paths work in the new ability system. Preserve the documented progression IDs and behavior, but use the roadmap and source for current work.
R0 design/source audit, 2026-10-26, batch 32. Follow the work-order plan, input, player capabilities, combat and stage/enemy/event contracts. This does not implement new reward systems or change gameplay. Preserve current one/two-player formulas, effect order, eligibility, random calls and progression IDs. Known defects get explicit dispositions rather than silent balance changes.
The owner confirmed during this batch: keep a shared row of cards, require distinct picks, and offer at least one extra card beyond the number of living players. Retain a larger existing offer count. This is approved target behavior for the four-player migration, not implemented capacity. Reward-card redesign, Epic VFX, loadouts, runes, new shop progression and other proposed features remain deferred.
Evidence and limits¶
Reference is the working source at HEAD 026941da, including earlier stabilization and uncommitted planning through batch 31; it is not certified Steam source. No Unity/player/save test runs in this batch.
- The existing 177-upgrade route inventory is expanded in place with IDs, weights, limits/taken counts, flags, values, prerequisite/evolution/achievement references and source hashes. Its previous columns/rows remain intact. Catalog membership and current enum interpretation are not proof of acquisition or correct content.
- Item baseline records 21 Items prefab components and authored payload/timing/magnet references.
- Bindings records 40 direct/stripped component references for collection, selection, item merging, evolution, counters and results. Embedded GameManager/PlayerManager and reward-object wiring also remain in the stage component map; do not create a second authoritative copy of that inventory.
- Effect dispatch maps all 45 UpgradeList members to current source cases, line/hash and selected API mentions. DRONEMK2 has no case; GKARMOR is empty. Their earlier reachability limits remain GAME-11, not requests for new effects.
- Callers contains 27 lexical category rows across 21 scripts. Persistent callbacks contains nine method-name candidates with actual target metadata; receiver, dynamic arguments and prefab overrides still require validation. Eight candidates resolve to local GameManager components; one Tutorial reroll callback has fileID0. Its active-route impact is unverified and remains parked with Tutorial integration.
- Comparison cases contains 23 source-derived cases, two owner-confirmed target cases and one unresolved scarcity-safety scenario. None is a passed gameplay test.
The serialized scan covers 1,256 asset/prefab/scene files. Focused review covers PlayerManager, RewardRoller, UpgradeSelection/Button/Function, delayed evolution, Items/ItemsMerger/ICollectible/PlayerCollectItem, reward-table preparation, GameManager result/unlock boundaries, GameDataCount and relevant profile data. Existing player/combat contracts retain detailed stat/ability formulas. Persistence/leaderboard internals and every upgrade combination remain further integration work. Batch 18's actual one-player pause/select/application evidence is retained; it does not certify every pool, chest, repeated reward or co-op path.
Ownership and extraction¶
| Owner | State / lifetime | Current disposition |
|---|---|---|
| Run progression | One party level, XP/threshold/carry, score/currencies, counters and pending level admission. | Retain PlayerManager arithmetic and GameDataCount key semantics; extract direct view/profile calls and make reset explicit. One actor pickup does not create independent XP for every player. |
| Reward definitions and availability | Immutable Upgrade/Item/evolution authoring; profile unlock projection and stage/catalog eligibility inputs. | Retain asset GUIDs, IDs and serialized callbacks. Move modifiedWeight, upgradeTaken and acquired/runtime state out of shared assets. |
| Reward queue/session | Run/offer generation, type, eligible-recipient snapshot, weighted offer, distinct card reservations, lock/commit state and owned pause reason. | Separate RewardRoller's simulation from card/chest/audio orchestration. Replace fixed two-slot UpgradeSelection authority with per-slot state and explicit input context. |
| Upgrade application | Validate and apply one selected card to its player, preserving ordered effects and run-shared acquisition limits. | UpgradeButton becomes a view/intent adapter; UpgradeFunction dispatch migrates behind stat/ability/evolution owners. UI does not supply authoritative effect arrays or grant content twice. |
| Pickup and merge | Item lease, payload, activation, target reservation, consumed/merged/expired outcome and item registry. | Separate Items lifecycle from feedback and PlayerCollectItem application. Merge reservations and collection share one claim boundary; preserve actual values and magnet behavior. |
| Evolution | Player/run-owned pending trigger/timer and applied outcome. | Retain thresholds, scaled timing, value arrays and callback order; remove global stale jobs and cancel expired-run work. |
| Result/progression handoff | Immutable run result with roster/player records, once-only outcome admission and profile/result-sink commands. | Extract GameManager payout/unlock/result view work. Keep existing Save keys and offline behavior through adapters; complete durability/migration in the persistence contract. |
Acquisition limits currently use shared Upgrade.upgradeTaken across the run, incremented for accepted applications. Moving that count into a per-player dictionary would change co-op availability. Preserve run-wide counting separately from each player's acquired abilities and independent cooldown/effect state. Profile is also distinct from player slot: local co-op currently writes one profile, not four Steam accounts or currencies.
Current XP, eligibility and weighted offers¶
PlayerManager.AddExperience adds givenExp + givenExp * level * 2 / 100. A threshold check admits only one level: publish OnRewardReady, apply FuturePills, apply RPGBuff, increment level, subtract the old threshold and multiply it by1.13. ResumeGame later calls EvaluatePendingLevel. A while-loop draining all earned levels immediately would change buff/random/queue timing. The three arena snapshots initialize level0, XP0 and threshold10; explicit preparation must preserve authored/default values instead of depending on construction or scene leftovers.
FuturePills/RPG loops visit nonnull array entries without a joined/living filter. Preserve outcomes while defining the roster boundary; array capacity is not participation. RPG's integer Random.Range(1,4) reaches branches1–3, while later branches4/5 are unreachable from that roll. Do not expand the range as incidental cleanup.
Chest/BossChest enqueues its reward before adding XP, so any resulting level reward follows it in the FIFO. Coin/chest XP applies difficulty and shop bonuses before the level-based adjustment. Gold accumulates once in the shared run. Opportunist adds shared rerolls from the collector's ability. Potions/gems/fruits affect the collecting actor with their current costs/caps/perks and random choices. AlchemistTouch doubles the ordinary potion loop; fruit handlers accept that argument without applying the same loop. Existing potion timer-isolation problems are governed by the combat contract, not a new stacking rule here.
Reward-table preparation uses selected stageIndex flags, catalog lists, special-chest membership and Mythic routing, then resets imported taken counts. Roll admission currently excludes Revive for solo/both-active first slots; WPEV/BLOODBAG and required-upgrade checks contain one/two-player assumptions. Special caps/unlocks/prerequisites use shared data and either first-slot ability list, including inactive holders. Offer eligibility is distinct from whether a selected recipient gets a useful effect: max-level weapon cards explicitly describe a no-op. Do not silently disable previously selectable no-op/prerequisite cases without a documented correction.
Level offers request three cards, then four above level20 and five above40; initial source/serialized buttonCount is three. Special/boss offers loop Players.Length and repeatedly assign three/four from each Quartet check, so the last examined slot controls the final value. Quartet has no authored Ability asset in the existing inventory; do not invent that feature. Preserve current reachable behavior and disposition ordering/unused-slot defects explicitly.
Weights are integer: reset modifiedWeight to weight, cast luck before Epic×1/Legendary×3/Mythic×10 integer percentage adjustments, accumulate totalWeight, draw, instantiate a selected card, subtract its weight and remove that entry. Draws are without replacement. Empty/zero-weight/insufficient pools can produce fewer cards than buttonCount while presentation indexes the requested count. Existing reward state tests with empty pools did not establish correct real pool generation. No implicit fallback to a different rarity/pool is approved.
Approved shared-card policy and session protocol¶
For N eligible living joined players, request max(existing reward/level count, N + 1) cards. Thus a base count3 gives3/3/4/5 cards for one/two/three/four players; an existing late-level count5 remains five. Each participant chooses one distinct offered card. Participation is based on the living joined roster, not reduced merely because a card is unsuitable for someone or a controller disconnects. Stable slot/offer identities replace display-index authority. The rule does not grant a card to every allocated scene slot or independently duplicate a shared reward queue.
- Accept the queued reward under the run/state owner; reject ended/disposed runs. Snapshot the intended living joined recipients and stage/eligibility inputs. Device disconnect does not silently mean death or forfeit; the input-recovery contract must define how a pending participant regains control.
- Build and validate the actual offer, including enough unique eligible content for the approved count. An insufficient pool is an explicit unresolved fallback/content decision: never fabricate upgrades, allow duplicate picks, skip players or claim a successful session. Normal input/failure recovery must remain usable; production scarcity handling needs a decision before that path ships.
- Acquire the reward pause reason and present the offer. Preserve realtime display/confirmation and unscaled navigation while gameplay is paused. Marker/card views read session state; confirmations use the slot's UI context and cannot also trigger a gameplay dash.
- Reserve distinct cards; allow the existing unlock/reselect interaction before commit. Validate offer generation, participant and card on every intent. Reroll uses the shared budget, invalidates the previous offer and clears its reservations; no stale confirm callback may apply the replaced payload. One owner resolves simultaneous reservations deterministically.
- Admit commit once after required participants lock. Prevalidate recipients and ordered effect payloads before mutation. Track application by run/reward/offer/slot ID; a repeated command returns the recorded outcome rather than applying twice. Keep legacy P1→P2 order for equivalent cases and explicitly define further-slot ordering.
- Apply only the admitted recipients' confirmed choices. A player revived during an earlier application does not receive an unconfirmed selection from this session; it can participate in the next offer. Unused slots never become revived participants. Actor/resource/ability effects use their existing owners and formulas.
- Complete the offer, release its views/registrations and only its own pause reason, then evaluate pending XP/rewards through the run coordinator. Another pause/end owner remains authoritative. Reset/exit invalidates waits/jobs and drops old-run intents before a new run can reuse actors/UI.
Validation and a per-recipient application ledger do not provide magical rollback for spawned weapons, counters, revive or external writes. A partial application failure must report the affected outcome and retain a recoverable state; do not blindly replay all effects or auto-resume as though every recipient succeeded. Define that recovery in the implementation slice and profile sink design.
Source comparisons must cover the0.5s confirmation window: ConfirmSelect applies whichever actors are active when each branch executes, so P1 Revive can activate P2 before the second branch and apply an unconfirmed card. Reroll guards phase/budget/pause/end but not this pending commit. These are source-backed GAME-09/GAME-02 risks requiring focused reproduction and explicit correctness changes, not proof the rebuilt transaction already exists.
Upgrade, pickup and evolution boundaries¶
UpgradeFunction increments a capped taken count when below the cap but does not reject every direct call already at the cap. It dispatches operation/value arrays sequentially. Validate lengths, operation support, references, target and level bounds before applying; preserve order, units, casts, early returns and side effects from the player/combat contracts. A generic stat multiplier table must not replace distinct mutation methods. WEAPONLEVELUP reads finalWeaponLevel around bonus mutations and serialized EvoFunction callbacks; compare preview/application indices, multi-level values and derived-stat refresh rather than normalizing them by assumption.
Delayed evolution uses shared run counters (SuperMagnet300, CImpact100, ThornArmor500, polling each scaled second) or120-second waits for timed evolutions. It applies the evolved upgrade before removing the old ability. Keep trigger semantics and authored IDs; make pending jobs per player/run with duplicate/reset/removal policies. Reuse the existing callback map for weapon evolution instead of duplicating its428 persistent candidates.
Items.OnEnable publishes its registry event before resetting target/flags and starting lifetime/drop/merge jobs. Payload fields, item count, some visual/collider state and other values are not all reset. PlayerCollectItem.ItemTrigger grants effects before Collect sets isTriggered; Collect's later pause/active check cannot undo those grants. The target acquires one valid item-lease claim before any XP/currency/personal effect, then emits consumed/merged/expired facts once. Preserve target/magnet/easing, cap restrictions, drop motion, scaled activation/lifetime and random ordering while fixing ownership.
ItemsMerger first checks raw overlap count, then reserves eligible active coins and totals itemCount/XP/gold. It later returns originals, activates a condensed prefab and assigns totals/material afterward. De-duplicate actual item leases, coordinate actor/merger claims and cancel old jobs; conserve all three totals. Do not reinterpret the raw threshold or merge timing without comparison. Death/despawn drops from the stage contract feed this same prepared item boundary.
Results, saves and remaining player rules¶
GameOver currently unlocks content, stops all clocks, accumulates profile currency/counters and builds UI with scaled waits before its final pause. Preserve the stage contract's distinct clocks: soul payout uses currentTime, displayed/stat play time uses currentGameTime. Currency is soulPoint * (10 + int(difficulty soul bonus) + int(shop soul bonus)) / 100, then adds int(currentTime / GAME_TIME_BONUS). Chaos fragments accumulate separately. Best-level and score separation remains the verified batch-01 fix. Preserve counter mappings and units too: GameDataCount currently maps TotalSoulAccumulated from InGamePerSoulCollected, while the payout publishes SoulAccumulated separately. Similar names are not permission to substitute one counter for another. Easy's clear title and boss-gated unlock path remain distinct; Medium/Hard/Insane unlock existing next difficulty/stage/character IDs through catalog identity.
GAME-25 records a separate source defect: mastery casts .5/.8/1/1.2/1.5 difficulty multipliers to0/0/1/1/1 before multiplication. With score2500 and level10, the current base is12 and awards0/0/12/12/12 before the two-player divide. Correcting that cast, changing score/1000 integer division, and choosing an extra-player split are separate decisions. Do not silently choose a new formula during extraction. Current division occurs only for exactly two players; P3/P4 mastery, rounding and participation need explicit resolution before production acceptance.
The result model must represent ordered joined slots/characters and accepted per-player upgrades/stats independently of the current two-panel view. Shared currency is credited once per profile/run, not once per participant. Exclude unused slots from result/achievement evaluation and define dead/disconnected participation without substituting array length. Revive targets joined/downed actors; retain half-resource restoration, existing upgrades and supported reset behavior from player capabilities. Required confirmation recovery, scarcity fallback and mastery policy remain RP-01–RP-04 below.
Leaderboard payload currently exposes only statsp1/statsp2 and backend source is absent. Do not silently omit P3/P4 or claim compatible online results. The persistence/UI/service contract must define a versioned adapter/unsupported-submission policy while keeping local gameplay independent. This batch does not implement new backend authentication or redesign scores.
Authoring, migration and open decisions¶
An upgrade needs stable identity, catalog/stage/rarity/weight/limit/unlock inputs, supported ordered operations with aligned values, required/evolved ability references and valid localized/view resources. An item needs its exact payload units, pooling/reset fields, activation/lifetime, collider/layer, optional merge configuration and recipient policy. Validate positive/selectable pools and enough distinct cards at supported participant counts, not merely that every asset imports. A copied UI hierarchy or new asset does not prove a reward can be offered, selected and applied.
| Follow-up | Required resolution / evidence |
|---|---|
| RP-01 · Eligibility and application | Reproduce inactive-slot caps/prerequisites/no-op cards, Revive during commit and repeated/malformed dispatch. Define eligible snapshot and once-only partial-failure recovery through the roster/player APIs. |
| RP-02 · Participation/recovery | Finish device-loss/late-actor/roster membership and dead-player reward/revive behavior with input/lifecycle owners. Never infer forfeiture or introduce mid-run joining by accident. |
| RP-03 · Payout and profile boundary | GAME-25 correction/rounding and three/four-player mastery participation require explicit decisions and numerical cases. Complete once-only result/profile application, key/schema compatibility and representation without changing unrelated currency math. |
| RP-04 · Scarce offers and pending work | Owner/content decision for insufficient eligible unique cards; verify empty/zero-weight pools, reroll during confirm, queued levels and cancellation. The approved N+1 rule alone does not supply fallback content. |
| RP-05 · Import/content closure | Resolve inherited scene/prefab wiring, callbacks, all effect/value/reference combinations, special/evolution route reachability and required service/view adapters before retiring legacy paths. |
R0 designs these boundaries. R1–R5 establish run/input/player/ability/attack/enemy/stage owners, with minimal adapters beside each slice. R6 migrates shared XP/queue and one real reward, then frozen-recipient selection/application, pickups/merges/evolution and the supported one-to-four-player cases. R7 completes profile/result/UI/services, R8 remaining Tutorial/content, and R9 production validation/profiling. Do not resume card redesign or old Tutorial patches first. This completes the bounded reward core contract and records remaining decisions; it does not close R0 or claim runtime equivalence. Next: persistence/input schema and UI/service integration, followed by the exact first implementation slice.