# Skill Upgrade Plan — impressive unique VFX + powerful AoE forged skills **Goals (from the ask):** 1. Forged skills get **impressive graphics when used** in combat (today they look plain). 2. The **coding model makes each skill visually unique** (not just mechanically). 3. **Most skills are powerful and hit all units in their range** (AoE). ## Why forged skills look plain today (grounded findings) - VFX is config-driven: `combatRenderer.buildSkillPlay(id, def)` reads `def.skillFx[skillId] = { animUrl, effects:[{url,cell}] }`. **Canon** skills get this from the sandbox's skill configs; **forged** skills (id 9000+) have **no `skillFx` entry**, so on `cast` the renderer falls through to `playAttack()` — a **basic sword swing** — and `spawnEffects(undefined)` is a no-op. - AoE is **target-centric**: `resolveScope()` supports `self/party/nearby/area/target_and_adjacent/ adjacent_to_target`, but `area`/`nearby` = **target + units within `ADJACENT_GW=140` of the TARGET**. There is **no "all enemies within the caster's range" scope.** Forged skills also don't set any `scope`, so each effect hits the single auto-target. - Asset palette is rich: `web/assets/effects.json` (228 entries) has element sheets — **fire, ice, nature, poison, shock, petrification**, plus `attack` arcs (slash/pierce/shot) and `hit` pops — all minifantasy strips with `{cell, rows, cols}`. Great raw material for element-themed VFX. - The Game wires VFX per actor: `comboBattler.spawnHero` builds `defsById.P0 = {name, profession, ...sheets}` and passes it to `createCombatRenderer`. **Adding `defsById.P0.skillFx` is the hook** for the hero's forged-skill VFX. `spawnEffects` positions effects at the **caster's body centre**. --- ## Phase A — Visual identity in the forge IR (model makes each unique) Extend the Skill-Forge output so the coding model emits a **`visual` block** alongside the mechanics. Cosmetic, so a bad value just falls back (never crashes the resolver). **New IR fields** (`skillSchema.js` → `validateSkill`): ```js visual: { element: 'fire'|'ice'|'lightning'|'nature'|'poison'|'shadow'|'holy'|'arcane', // → tint + sprite sheet shape: 'slash'|'nova'|'ring'|'beam'|'pillar'|'projectile'|'shockwave', // procedural motion color: '#rrggbb', // accent (validated hex) intensity: 1..3, // particle count / scale / flash } ``` All whitelisted/clamped. `element` maps to an `effects.json` sheet; `shape`+`color`+`intensity` drive a procedural layer (Phase B). The model picks them to match the flavor → **every skill is visually distinct** (element × shape × color × intensity is a huge space), and consistent with its name/lore. **Prompt (`FORGE_SYSTEM`)** gains: "Also choose a `visual` that fits the skill's theme: an element, a shape (how it manifests), an accent color, and intensity 1-3." Keep the JSON-only contract. ## Phase B — Render impressive, unique VFX Two composited layers, wired by giving the hero's forged skills a `skillFx` entry at spawn. **B1 — Element sprite layer (reuse existing pipeline).** - Build `ELEMENT_FX` map: element → an `effects.json` URL (fire→fire sheet, lightning→shock, etc.). - In `comboBattler.spawnHero`, construct `defsById.P0.skillFx` from the equipped forged skills: `skillFx[skill.id] = { animUrl: null, effects: [ ELEMENT_FX[skill.visual.element] ] }`. `buildSkillPlay` already loads these; `processLog` 'cast' already calls `spawnEffects`. So forged skills immediately stop looking like a basic swing. **B2 — Procedural Pixi VFX layer (new `src/render/skillVfx.js`).** A small, asset-free effect generator drawn into the combat root (world space), parameterised by `visual`: an **expanding tinted ring/nova** (shape `nova/ring/shockwave`, radius = the skill's AoE range → reads as "everything in here is hit"), a **beam/line** to the farthest target, a **pillar**, or a **projectile** burst. Color = `visual.color`; particle count/scale/duration scale with `intensity`. Big skills also trigger a brief **screen flash + shake** (reuse the existing `addShake`/juice hooks). This guarantees uniqueness + scale even when no sprite matches. **B3 — Hit feedback on every struck unit.** When an AoE skill resolves, pop a small element `hit` sprite (from `effects.json` `hit` category) + the yellow skill-name float on each affected enemy — so the player sees the whole area get hit. **Wiring:** comboBattler's `processLog` 'cast' hook (it already runs per cast) calls `skillVfx.play(caster, skill.visual, rangeRadius, targets)`. The renderer change is additive. ## Phase C — Powerful + hit all units in range (AoE) **C1 — New engine scope (`teamBattle.resolveScope`):** ```js case 'area_around_caster': { const r = e.range || DEFAULT_AOE // effect-supplied radius (field units) return b.actors.filter(x => x.alive && x.team !== src.team && dist(src, x) <= r) } ``` Allow effects to carry a clamped `range`. (Also extend `damage/heal/...` op validators to accept `scope` + `range`, not just `apply_condition`.) **C2 — Forge defaults to powerful AoE:** - Schema: when the model omits scope, **default most forged skills to `area_around_caster`** (a config flag, e.g. attack/spell categories get AoE by default; `self`/`ally` targets don't). - Prompt: "Most skills should be POWERFUL and hit ALL foes in range — prefer an area scope and meaningful damage/conditions." - Numbers: raise the forged damage clamp (these are a hero's signature ultimate-ish skills), with a balance ceiling. AoE radius derived from the skill's reach/element (clamped to ~the on-screen ranged reach so it's readable, not whole-map). **C3 — Readability tie-in:** the AoE radius from C1 drives the Phase-B nova scale, so the visual ring exactly covers the hit area — the player *sees* who's caught. --- ## Files to touch | File | Change | |---|---| | `tiny-army/web/skillSchema.js` | `visual` block + validation; `scope`/`range` on more ops; AoE-by-default; higher damage clamp | | `tiny-army/web/skillForge.js` | `FORGE_SYSTEM` prompt: emit `visual` + prefer powerful AoE | | `auto-battler/src/engine/teamBattle.js` | `area_around_caster` scope + `range` on effects | | `auto-battler/src/render/skillVfx.js` (new) | procedural element VFX (nova/ring/beam/pillar/projectile) | | `auto-battler/src/render/comboBattler.js` | build `defsById.P0.skillFx` from forged visuals; call `skillVfx.play` on cast; AoE-scaled nova + flash | | `auto-battler/src/render/combatRenderer.js` | (light) expose a per-target hit-effect helper for AoE pops | ## Sequencing 1. **C (engine AoE + power)** first — biggest gameplay impact, smallest surface; verify in-engine that an `area_around_caster` skill damages all foes in radius. 2. **A (visual schema + prompt)** — model emits `visual`; validate; re-forge produces visual params. 3. **B1 (element sprites)** — forged skills stop looking plain (quick win, reuses pipeline). 4. **B2/B3 (procedural VFX + AoE nova + hit pops)** — the "impressive + unique" payoff. ## Decisions (locked 2026-06-13) - **Power/AoE:** signature-strong, **capped** — most forged attacks/spells are AoE + high damage, ceiling so they can't trivially one-shot a Champion boss. - **VFX:** **both** element sprites + procedural nova/beam scaled to the AoE. ## Open decisions 1. **VFX approach** — element sprites only, procedural only, or **both composited** (recommended: both — sprites give theme, procedural gives unique scale/motion + guaranteed coverage). 2. **AoE default** — make *most* forged attacks/spells AoE (recommended), or only when the model opts in. 3. **Power level** — how strong? Recommend "signature strong": higher damage + AoE, but capped so a forged skill can't trivially one-shot a Champion boss. 4. **Should canon (non-forged) skills also get the new procedural VFX?** Optional follow-up — they already have authored sprites.