SNKRX Swordsman, Magician, Outlaw, Cannoneer
Fable 5
Opus 4.8
Summary
Ported the four remaining mini-looper-survivor units (swordsman, magician, outlaw, cannoneer) into full SNKRX-faithful units — completing all 5 direct-hit units / the "easy tier" (5/28). Along the way: distilled the per-unit porting process into a general recipe, synced SNKRX-update up to snkrx-template, rebuilt the unit card header, added a random-targeting mode and the projectile+splash two-damage pattern, diverged the F5 spec lab with a unit picker, copied 062026's calmer unit-hit feedback, and added a global fixed-gradient-angle rendering option.
Session alignment + unit selection:
- Read
project_snkrx_updatememory +SNKRX-update/porting.md(archer-baseline process). State coming in: archer done (1/28), porting infra in place (F1 arena dial, F5 spec lab, F6 inventory, the locked unit-gradient visual language). - Research pass on all 4 mini-looper survivors against
E:/a327ex/SNKRX-ref/player.lua: swordsman (self-centered AoEattack(96), sensor 48, literal 3.0/warrior 0.9 = 3.33s, dmg 11 +15%/enemy), magician (attack(32)at random enemy, range 96, 2.0/mage 1.0 = 2.0s, dmg 18), outlaw (5-knife fan, sensor 96, literal 3.0/(warrior 0.9×rogue 1.1)=3.03s, dmg 14), cannoneer (projectile+explosion, sensor 128, 6.0/(ranger 1.5×nuker 0.75)=5.33s, hit 12/splash 36). Recommended swordsman; user picked it.
Swordsman port (units.lua):
- User ruling: the swordsman is ALSO live in
062026, finished there — 062026 is the behavior reference where it differs from SNKRX. Only the SNKRX identity layer (damage, cooldown, card fields) was ported on top. Kept 062026's offset-square Cleave (64px square, 32 ahead, aimed at target) over SNKRX's self-centered circle; kept the sword_slash v3+v2 sound stack + carrier juice. - Numbers:
damage = 11(Part 8) + newdamage_per_enemy = 2flat crowd bonus (replaces SNKRX's +15%/enemy — flat additions always, project-wide rule; +2 gives 1-target 13 matching Part 8's "≈13").cooldown = 3.33,range = 64. Spec/glyph_spec copied from 062026's F5-tuned literal (snkrx_yellow→yellow gradient).effect_name = 'Cleave'(SNKRX's merge effect shares the attack name), grayed. - Deleted
swordsman_cooldownknob +swordsman_specglobal; added swordsman tounit_type_order. Fixed a porting.md error: outlaw's pre-derived cooldown literal was wrong (said 2.0; the real literal at player.lua:237 is 3.0 → 3.03s).
Card header redesign (inventory.lua) — iterated several times:
card_meta_runsrebuilt from the bracketed[class][attack_type](Awesome-16 bracket glyphs, read title-sized) into a bare Ark-10 comma list (warrior, area). Tier tag[tier N]moved to small Ark, baseline-aligned (tier_dy).- User feedback rounds: (1) tier down a bit + smaller tags; (2) tier moved up (
tier_dy = -3), tags flat (dropped per-tag class colors → all muted), tags moved up (meta_gap = -2); (3) tier 1px down (tier_dy = -2), tag line + Lv.3 block to faintergrey(vs description'stext_muted) as auxiliary info; (4) Lv.3 description auto-wraps (wrap_textgreedy word-wrap) so a long effect string folds instead of stretching the card width.
Magician port (units.lua):
- Added a
targeting = 'random'mode: newunit_random_enemy(u, range)helper + a 3-way branch inunit_attack_update(random / densest-packaoe_radius/ nearest). SNKRX's erratic random aim is the unit's character. damage = 18,cooldown = 2.0,range = 96, rect AoE. Square downsized from SNKRX's derived 57.6 (1.5×1.2×32) to 48 (radius 24) — "too big at our scale," dropped the mage ×1.2 (magician precedent for SNKRX-size-is-a-starting-point). Kept ourmagic_impactcast sound (SNKRX magician is silent on cast). Spec F5-tuned + baked snkrx_blue→light_blue. Lv.3 'Quick Cast' grayed.
Template sync (the "D" task) — SNKRX-update up to snkrx-template:
- Diffed toolkit dirs: anchor/, anchor.exe, draw_shader.frag all already identical. Real deltas:
snkrx/ui/primitives.lua(+ui_status_tagalpha),snkrx/ui/widgets.lua(+ui_barkick/fill_spec, +ui_hud_barval_w) — both strict supersets, copied verbatim.snkrx/spec_lab.lua— new generic registry core copied in. - Refactored root
spec_lab.luafrom ~290 lines to a thin wrapper (require core + register test_unit's spec/glyph_spec + unit preview). Madecard_meta_runsglobal so the F5 preview and the real card share one run-builder. Kept the user's request to retain the tag-line preview (drawing the new flat-grey line). Updated.claude/CLAUDE.mdto the template's current edition (preserving one local working-style line).
Process distillation (porting.md rewritten):
- Turned the archer case-study into the general RECIPE after 3 baselines. Promoted recurring rulings to §0 standing rules: flat damage additions always; re-read the cooldown literal at port time (the outlaw trap); keep-ours is the sound/juice default; SNKRX sizes are starting points (retune at our scale); the 062026-sibling reference option. Added §6 Bookkeeping (the mechanical tail: knob/spec deletion, unit_type_order, test_unit flip, rulings table, roster + memory, syntax checks). Encoded the trigger-body→targeting-mode mapping and the Area-is-a-rotated-square (side 1.5×w) geometry rule.
Outlaw port (units.lua) — first full-recipe run:
{warrior, rogue}, tier 2,damage = 14,cooldown = 3.03(literal 3.0 confirmed),range = 96, no pierce (knives die on first hit). Fan formula was already algebraically SNKRX's; tunedspreadthrough 90° (SNKRXmath.pi/8) → 34° (0.15) → final 60° (math.pi/12) per user feel ("90° read too sparse single-target") — a rare ground-truth override. Speed 250. Staggered knife-throw sounds + single fanshake_pushkept. Spec F5-baked snkrx_yellow→red — user waived the "avoid red for units" rule for the outlaw explicitly.
snake_unit:hit feedback copied from 062026 player:hit (snake.lua):
- User: "the shake this one has is a bit too much." The old
snake_unit:hitfiredshake_shake(main_camera.shake, 4, 0.5)on every non-lethal contact = constant swarm jitter. Replaced with 062026's feedback: NO camera shake; gentler 0.2 spring pop + 0.1s flash + a contact hit_circle + 1-2 spec-mixed particles. Signature →(damage, hit_x, hit_y, source); main.lua contact handler passes the contact midpoint + the enemy. Caught a gotcha: 062026 pulls a'hit'spring key but the snake's draw readsself.spring.main.xwith no'hit'registered — mapped the 0.2 force onto the visible'main'spring so it actually shows.
Cannoneer port (units.lua) — first two-damage unit:
- SNKRX mechanic confirmed at player.lua:2332 (
other:hitfires on the struck enemy) AND player.lua:2208 (die() spawns the explosion Area) — directly-hit enemy takes both = 48, others take the splash. Modeled withdamage = 36(the SPLASH — the card headline + description number, matching SNKRX's card) and a newdirect_damage = 12field (the projectile hit). Attack:spawn_projectile(damage=direct_damage, speed=250)+on_death→spawn_area(damage=def.damage)atcannon_blast_radius = 48(SNKRX's ~144-216px shrunk per magician precedent).cooldown = 5.33,range = 128. Kept densest-pack aim (aoe_radius) over SNKRX's closest (user call — better for an AoE nuke). Kept cannon boom + strong trauma blast shake. Spec F5-baked lime→pink (steered off the seeded nuker-orange, since orange = enemy family). The porting.md def template now documentsdirect_damagefor projectile+splash units.
Mixed roster snake (main.lua SCENE):
- Added
ported_units = {archer, swordsman, magician, outlaw, cannoneer}+snake_mode('roster' = one of each, the default mixed showcase / 'solo' = test_unit copies for porting).snake_buildalready cycled a roster list per-index, so n=5 lays out one of each head-first.
Spec lab: unit picker + dropped shape axis (spec_lab.lua) — DIVERGED:
- User chose to diverge ("different projects need different tools; this one makes units faster, the template focuses one-at-a-time"). Rewrote root
spec_lab.luaself-contained (no longer requires the generic core, which stays in the toolkit unused): leads with a UNIT picker (cyclesported_units) → TARGET (spec | glyph_spec) → pattern/color/dither cyclers → swatches/ramp editor → sliders → DUMP (<unit> <target> = {...}+ clipboard). Shape axis removed (user won't use it). Edits remain live against the realunit_defs[...]tables. - Magician + outlaw spec re-bakes: both →
bayer2_4dither; outlaw'spattern_param2(offset) set to 0.412 (copied from cannoneer per user, overriding the dumped 0.4345).
Fixed gradient angle (effect.lua + 3 draw sites) — global, on by default:
- User wanted local linear gradients to respect the spec-lab (screen) angle regardless of object rotation, as a single global toggle (all-or-nothing). Root cause:
pattern_linear_gradientprojects in quad-local space (c = (uv-0.5)*quad_px), so a rotatedlayer_pushspins the gradient with the body. - Implemented as Lua-side angle compensation (no shader/engine change): global
effect_fixed_gradient_angle = true;effect_set(layer, spec, rotation)gained an optional rotation arg — when on + pattern is linear_gradient (kind 10), sendspattern_param - rotation/(2π)so the body's spin adds it back to the screen angle. Wired the rotating unit-spec draws:snake_unit:draw(self.r),projectile:draw(self.r),area:draw(rect shapes pass self.r, circle/polygon pass 0). Exact for square quads; backwards-compatible + generic → candidate to propagate to snkrx-template. flicker/prop/beam unwired (no ported unit uses them).
Next session (planned, saved to memory): stop porting new units — instead deepen the 5 we have into a full Lv.1 / Lv.2 / Lv.3 ability ladder per unit (a deliberate change from old SNKRX's Lv.3-only merge effect): every unit gets three abilities (current attack + a new Lv.2 + a redesigned-then-coded Lv.3), a mix of active and passive. This forces a unit_card redesign to show all three tiers AND fit on screen.
🔒 Only the summary of this log is public. Private because this is SNKRX update.