Archived on 2026-10-24. The Godot rewrite is cancelled. This handoff is historical evidence, not an active Unity task or verified runtime behavior. See the current documentation.
Phase 8 import notes¶
Confirmed facts¶
- Soul A resolves exactly from GUID
9e97f22f7bbceca4ebb0ab669cf65789toAssets/Prefab/Items/GeneralItem/Soul A.prefab. It isItems.ItemType.Coin; no separate item-data ScriptableObject is referenced. - Slime 1 (
KILL_SLIME_1) guarantees one Soul A on normal death.Enemy.DeathAnimationcallsRewardingimmediately after setting the death animation trigger, then waits 0.8 scaled seconds before returning the enemy. The pickup therefore spawns at the enemy transform at the start of the death animation, not at pool return. - Enemy reward count and pickup values are separate. Count is random inclusive
[dropMin, dropMax]; then every spawned Coin receives the enemy's fullexpandgold. Slime 1 uses count 1, XP 1, gold 1. - Gold is stored and collected through the same Soul A item (
goldValue-> sharedGameManager.soulPoint). XP is likewise run-shared inPlayerManager, not player-owned. - Nominal drop scatter chooses independent x/y components in
[-0.7,0.7], does not normalize the vector, and translates at 2 units/second for 0.7 scaled seconds. There is no Rigidbody velocity. Because pool activation precedes final transform assignment, the first coroutine movement step can occur before the death position is assigned. - Pooling is lazy with no prewarm. The normalized object-name key is
Soul A. A returned pickup is enqueued then deactivated. New or reused objects activate before PoolManager assigns the requested position/rotation. - The collider is a trigger circle, serialized radius 0.3 and offset
(0,-0.3)on scale 0.7, yielding radius 0.21 and offset(0,-0.21)in an otherwise unscaled world. The actual XP collection path is distance-driven (<=0.25) and has no Item/Player trigger callback. - Attraction becomes eligible after 0.5 scaled seconds. Base radius is 1.5 plus the evaluating player's shop magnet range. Target assignment is first-claim by script update order, not a nearest-player comparison.
- The serialized
magnetSpeed=15on PlayerCollectItem is unused. Items computes its own curve. BothUpdateandCollectingincrementspeedIncrease, producing 6 scaled seconds of accumulator per real gameplay second before the easing formula. - The clean Stage 1 scene starts shared level 0, XP 0, threshold 10. Thresholds multiply by float
1.13f. XP carry is retained. - One check completes only one level. Large carry is checked again only after a reward selection starts
ResumeGame, waits one frame, and callsEvaluatePendingLevel. - A normal level-up requests
LevelUpReward. The notification occurs before level increment, carry subtraction, and threshold multiplication. - In one-player play, player 0 receives the selected upgrade. In two-player play, both active players must lock different cards, then each selected card applies to its corresponding player. If one player is inactive, only the active player's choice is required/applied.
Upgrade_ASP 1is the deterministic winner because it is qualifying pool index 0 in the Bootloader's stable serializedbasicUpgradeslist. It adds raw0.15to the selecting player'sbonusAttackSpeed; the English description also presents+0.15, not a percentage.
XP formulas and ownership concerns¶
For a Coin with base item value V, PlayerCollectItem sends:
collectorGrant = V + V * difficulty.EXP_BONUS / 100 + V * shopExpBonus / 100
PlayerManager then stores:
storedGrant = collectorGrant + collectorGrant * currentLevel * 2 / 100
Stage 1 Easy has difficulty bonus 0. With clean shop XP bonus 0 and level 0, Slime 1 grants exactly 1 XP. A non-clean profile can change the shop term and is deliberately not exported as a permanent-progression value.
Both local collectors hold the same neutral pickup in their lists. The first collector update within radius calls IsCollecting and permanently claims the target until reuse. This is update-order dependent, not closest-player selection. The item awards shared XP and gold, but any player-specific ability checks (for example Super Magnet statistics) use the claiming player. Two overlapping players cannot both receive the reward through the normal path: one target wins, and isTriggered plus pool deactivation prevents the next award. Note that reward handling happens before Items.Collect sets isTriggered, so this protection is ordering-sensitive even though the single-target item update makes a duplicate unlikely.
There is no active/dead-target fallback. An inactive target remains referenced. Reproduce or intentionally improve this in Godot only after deciding desired co-op behavior.
Pool and reset semantics¶
On every enable, reset IsActive, acceleration, following, trigger guard, target, and sprite color; start activation, lifetime, scatter, and merge checks. Enemy spawn must then assign XP and gold. itemCount remains 1 unless a merger creates a condensed item. Collider enable is set after the delay but is never explicitly disabled in Reset; IsActive is the actual activation gate.
Release occurs on collection, lifetime completion, or merger consumption. The lifetime is 90 scaled seconds followed by five scaled one-second blink intervals. Pause stops scatter, attraction, activation, and expiry because these use scaled time; the item's Update still runs, so an item already within 0.25 can invoke collection during a paused frame.
Border timeout bypasses Death, DeathAnimation, and Rewarding, so it makes no pickup. End-game fleeing itself only moves the enemy; a later border timeout also makes no pickup.
Upgrade selection proof and operation¶
Eligible candidates are all pure Stage-1-imported ASP, CCH, CDMG, DMG, HP, MS, RS, SAMP/SPELL, and STM entries listed in basic_stat_upgrade.json. They have no ability, weapon, evolution, summon, character, achievement, or required-upgrade dependency. The winner does not require a lexicographic tie-break because ASP1 is already serialized index 0.
Rejected candidates:
ASPX,CCH5,CDMG5,DMGX,DR4,DR5,HPX,MS_5,OMNI_X,STMX, andVMPC: empty stage-appearance array, so the basic importer excludes them.REROLL_1andREROLL_2: reroll-count utility, not a player stat.REVIVE: revive feature/ability, excluded and ineligible while all configured players are active.WPEV: weapon evolution/level feature, excluded by the request.
Applying ASP1 runs UpgradeFunction.SelectUpgrade for the selecting player, optionally multiplies the application by 1.5 only if that player already has AdeptLearner, then adds it to runtime PlayerModifier.bonusAttackSpeed. Normal clean-run ASP1 is therefore a flat +0.15 rate term. PlayerModifier.UpdateValue continuously recalculates the inverse attack interval. There is no paired removal method.
ASP1 has maxUpgrade=0, meaning unlimited. Selection therefore does not increment ASP1's shared ScriptableObject upgradeTaken; only runtime player state changes for this selected asset. Initialization still writes upgradeTaken=0 across the shared reward assets as a general run reset.
Reward and pause behavior¶
The intended state model says isRewarding should pause time, but the actual normal reward path only calls SetRewarding(true). It does not call UpdateTimeScale or PauseGame. Meanwhile navigation and confirmation require GameManager.isPaused=true. This is a direct source conflict: absent an inspector-only caller not found here, a normal reward can become visible but unselectable while gameplay continues.
Reward visuals themselves are pause-safe: realtime waits, unscaled input timers, pause-safe audio, and DOTween SetUpdate(true) are used. Reroll has the opposite gate from selection: it refuses to run when isPaused is true. Thus externally pausing to make selection work disables reroll.
The FIFO reward queue processes one entry only when phase is None. Confirmation applies upgrades, removes/hides cards, starts resume (which clears pause and time scale synchronously), resets selection state, clears rewarding/phase, then checks carried XP one frame later. The next reward is processed on a later update.
Pause-menu requests are rejected after a reward has set isRewarding. However, if settings was already open when a queued reward begins, reward processing does not check the settings stack. The reward can open while settings owns pause; the back handler then refuses to close settings because rewarding blocks pause actions. Treat this as a potential input soft lock.
Unresolved and manual verification¶
physics_maskremainsnullin JSON: the pickup has no per-collider include/exclude override, and its proven collection is not collision-callback-driven. Confirm the SoulPoint/Player project-matrix bit only if Godot collision parity is desired beyond the actual runtime collection path.- The pickup has no serialized stable ID besides the prefab identity/name and pool key.
pickup_idexplicitly records that limitation. - Verify in a clean Stage 1 play-mode run whether any inspector event or animation invokes
PauseGameat reward opening. No such source caller was found. - Trace a large XP grant through two or more reward selections to confirm the one-level-per-resume cadence and UI queue behavior.
- Test simultaneous two-player magnet entry to observe the update-order target winner and behavior when that target dies/deactivates.
- Test reward arrival while settings is already open and reroll during reward pause; both source paths have credible soft-lock/gating conflicts.
- Final art, sprite, animation, shadow, material, sound, and merge VFX may remain placeholders for this data-only handoff.