SNKRX Merging and Inventory
Opus 4.8
Summary
Built the SNKRX party merge system end-to-end on the SNKRX-update project (the secret SNKRX-on-Anchor-2 rebuild) — the level/reserve logic, the ×2/×4 damage+HP scaling, and the full F6 inventory visual it drives — then iterated the inventory through several rounds of feedback into a live, always-on overlay with per-sub-cell hover cards and a faithful SNKRX merge effect + sound. Roster is still 5/28 ported (archer, swordsman, magician, outlaw, cannoneer); this session added the merge mechanic those units' ability-ladder cards were built in anticipation of. Ended by picking the next direction: resume porting more units, redesigning Lv.3 passives per-unit as each lands.
Orientation:
- Confirmed state from memory + the vault logs: last SNKRX-update session was nda-project-6 "SNKRX UI Cards and Keywords" (the 2-tier ability-ladder cards + glossary-hover). Repo clean at
94ea604,test_unit = 'cannoneer'. (Noted the vault interleaves two secret projects — nda-project-5/7 are 062026 sessions.) - Laid out next-step options (merge system / class set-bonuses / a subsystem to unblock units / name polish); user picked the merge system, specifically wanting it "working logically but also physically" — the SNKRX F6 snake visual that shows merge progress intuitively.
Merge research (SNKRX-ref):
- Merge logic lives in
buy_screen.lua(the shop), not amergefunction. A party unit is{character, level, reserve={r1,r2}}(line 327).BuyScreen:buy(286-310) is a purchase accumulator: at Lv.1 each dupreserve[1]++,>1→ reset, level=2; at Lv.2reserve[1]++,>2→ bank areserve[2]or (if one banked) → level=3. So 3×Lv.1→Lv.2, 3×Lv.2→Lv.3, 9 total→Lv.3, cap 3. - The visual (
CharacterPart, 1142-1248): each party cell is a colored square with its level number; the reserve is drawn as smaller mini-unit "parts" beside it (a banked Lv.2 part at full size + a 0.9-scale Lv.1 part perreserve[1]) — the partial-merge progress made physical. A landed merge firesSpawnEffect. - Damage/HP law (
stat_port_proposal.mdPart 8, authoritative): merge 1/2/3 = base / 2× / 4×, integer doubling; merge couples both HP tier and the attack's damage tier. Cooldown/range do NOT scale.self.level == 3gates the Lv.3 passive.
Merge logic (snake.lua + units.lua):
snake_unitgainedreserve = {0,0}and a level-coupledmax_hp.unit_merge_add(u)— SNKRX's reserve roll verbatim; returns true on a level-up.snake_unit_set_level(u, lv)sets level + re-couples HP (tops to full).merge_dmg_mult(level)+unit_damage(u, base)(units.lua) scale damage; wired into all 5 attack fns (archer, magician, cannoneer direct+splash, outlaw, swordsman incl. its flat crowd bonus).- The card (
unit_card_v2) scales via a thin__indexoverride-proxysdef(damage/direct_damage/damage_per_enemy × mult) so the DMG chip AND the yellow description numbers reflect the hovered unit's level with zero per-unit edits.
Design-brief workflow: every non-trivial change went through a brief-then-approve loop per the project's ⛔ rule (verified SNKRX behavior before coding each time). User approved each with "Go ahead" / "Yes".
Iteration round 1 (user feedback):
- "Change C so that it adds a unit randomly to one of the existing ones." → the
cdebug key is now hover-independent: picks a random eligible unit (ability-ladder, level<3) and runsunit_merge_add. - "The visual for the mini-units looks wrong as the number is bigger than the unit itself." → reserve parts now draw a FULL cell under a
layer_pushscale so the level digit shrinks WITH the square (SNKRX scales the whole part the same way).draw_reserve_partadded. - "The effects you added are also not the same as in SNKRX, you created new ones yourself, make sure to match SNKRX perfectly, even when it comes to sound." → removed the invented white-flash; researched + ported the real effect/sound (below).
SNKRX-faithful merge effect + sound:
- Visual = a paused-safe port of SNKRX's
SpawnEffect(shared.lua:367): white circle pops radius 0→6 (0.1s cubic_in_out) then 6→0 (0.25s), bleeding white→unit-color at 0.15s, bursting 6–8 unit-color particles (reusingfx_particle, the HitParticle port). The circle is hand-rolled (ourfx_hit_circleonly shrinks). Held in a localinv_fx_circles/inv_fx_particleslist ticked byinventory_update(dt)and drawn onui_layerat the cell, so it animates even though F6 paused at the time. Fires only on a level-up. - Sound = SNKRX's
ShopCardbuy stack (buy_screen.lua:1619):ui_switch1(Switch.ogg) + a randomcoins1/2/3(Coins 7/8/9.ogg) on every accepted acquire;error1(Error 2.ogg) on a refused one. The merge SpawnEffect itself is silent in SNKRX. - Sourcing (per project rule — source from packs, not the SNKRX folder): found the files in
E:/sound packs/Ultimate SFX Bundle (2020)/Magic Sounds Pro/Magical UI Sounds/(durations matched SNKRX-ref's oggs exactly — the pack SNKRX drew from).ffmpeg -c:a libvorbis -q:a 10→assets/, loaded in main.lua's GAME SOUNDS block beforevolumes_apply_overrides(), pitched ±5% / vol 0.5.
Live always-on inventory (de-pause):
- "To make things simpler, just have the inventory UI visible without the game being paused, so I don't have to press F6." → dropped the early-out at main.lua:273 (
if inventory_update(dt) then return end→inventory_update(dt));inventory_visibledefaults true;inventory_updatereturns false (no pause signal). F6 kept as a non-pausing show/hide toggle. - Spring juice: confirmed cells already pop on hover/press/merge (
ui_juice_hover/pull/scale). The missing SNKRX piece was the entrance pop — added a cascade (INV_ENTRANCE_STAGGER) that pops cells in one-after-another on the hidden→visible edge (incl. startup), mirroringCharacterPart:init's spring pull.
Spring-on-buy correction:
- First made a buy pop the WHOLE party. User: "You made every single one spring, I mean every single one for that unit only." → scoped back to only the acquired unit, whose big cell + small reserve parts pop together.
- Verified the original: SNKRX's
set_party_and_sets()(called on every buy) destroys + recreates allCharacterParts, so each main + reserve part re-runsCharacterPart:init→spring:pull(0.2). Implemented per-sub-cell springs (each sub-cell scales by its own key's juice); a buy pulls all of the acquired unit's sub-cell keys (pull_unit); entrance/drag pops do the same.
Uniform reserve size: "let's have all Lv.1 and Lv.2 units be the size Lv.2 units are now, instead of having 3 different sizes." → collapsed the three sizes (main 1.0 / Lv.2-reserve 0.8 / Lv.1-reserve 0.65) to two via a single inv_reserve_s = 0.8. Flagged the divergence: SNKRX actually draws Lv.2-reserve at full size + Lv.1 at 0.9.
Per-sub-cell hover cards: "The card hover should work individually for each unit, including the small ones." → generalized the card target from a unit entity-id to any sub-cell. New unit_cells(u, r) = the shared geometry (main + reserve parts, each with a stable key that doubles as juice id + card id), iterated by both draw and the hover hit-test. Each frame builds a targets list; the square under the cursor pops individually and shows its card AT THAT PART'S LEVEL. Card state keyed by inv_card_key (was inv_card_id), re-resolved each frame via find_target; keep-corridor changed to the direction-agnostic union bounding box of source + card (reserve parts can sit past where the card pops). Drag-reorder + 1/2/3 level-jump stay main-only.
Integer display fix: doubled/tripled values showed as "24.0" — 2^(level-1) returns a Lua 5.4 float. Changed merge_dmg_mult to 1 << (level-1) (integer 1/2/4) and routed snake.lua's HP scaling through it, so all scaled damage + HP render as integers (12/24/48).
Wrap-up: updated the project memory (project_snkrx_update.md) to record the merge system as BUILT (it had been marked unbuilt) plus the next-session plan. Next session (owner's call): resume porting more units one at a time per porting.md, redesigning quite a few of the Lv.3 passives per-unit as each is added — NOT as a separate up-front pass. Shop/economy, class set-bonuses, and a buff subsystem stay deferred behind that. Session sealed as an NDA log.
🔒 Only the summary of this log is public. Private because this is SNKRX update.