Compare commits

...
19 Commits
Author SHA1 Message Date
phenom ab1434a3b8 Release Healer Man 0.1.59 2026-09-27 10:16:42 -04:00
phenom 11934a3979 Update Healer Man server 2026-09-26 22:02:18 -04:00
phenom 8a98797fcf Update Healer Man server 2026-09-26 20:27:07 -04:00
phenom 4957b69e86 Update Healer Man server 2026-09-26 19:55:08 -04:00
phenom 83eac6d476 Update Healer Man server 2026-09-26 18:57:44 -04:00
phenom bd073fb00f Update Healer Man server 2026-09-26 17:51:56 -04:00
phenom 9c5fb4c614 Update Healer Man server 2026-09-25 21:37:10 -04:00
phenom 48a678dcc5 Update Healer Man server 2026-09-25 18:46:09 -04:00
phenom a868401ebc Update Healer Man server 2026-09-24 18:15:45 -04:00
phenom 71246e5b75 Update Healer Man server 2026-09-24 16:42:43 -04:00
phenom a64b2cb575 Update Healer Man server 2026-09-23 22:25:39 -04:00
phenom d869d6b51c Update Healer Man server 2026-09-23 21:58:28 -04:00
phenom a641a6e948 Update Healer Man server 2026-09-23 21:14:46 -04:00
phenom 7449abdad2 Honor RoM off-hand and ranged equipment slots 2026-09-23 19:59:21 -04:00
phenom 0206fda9e2 Complete RoM pair elite mechanics and creature family rules 2026-09-23 19:38:13 -04:00
phenom c8b3a3d95a Validate RoM primary five hybrid stats and pair mechanics 2026-09-23 19:08:44 -04:00
phenom 8e95480d4f Use hybrid WoW-stat gear for five RoM builds 2026-09-23 18:23:31 -04:00
phenom f4a7d33f74 Add source-backed RoM heirloom reference curves 2026-09-23 16:56:48 -04:00
phenom d3cc27a54a Catalog RoM leveling gear package milestones 2026-09-23 16:21:55 -04:00
913 changed files with 175658 additions and 930 deletions

No files matched your search

+323
View File
@@ -4,6 +4,21 @@ This file is the durable record of runtime behavior, regressions, and the
evidence required before calling a visual bug fixed. Update it whenever scene,
camera, input, physics, mob-rendering, or asset-loading code changes.
## 2026-09-26 RuneWaker dungeon minimap and M-key map
- Created a fresh RoM Rogue, downloaded Forsaken Abbey, entered as damage, and
waited for the boss preview to finish. The minimap displayed the extracted
client map; moving backward changed the live player-marker coordinates.
- Pressing M opened the full map with the same artwork, player heading, entrance,
and eight numbered encounters. Pressing M again closed it. No browser page
errors were reported.
- Inspected `artifacts/playtest/runewaker-maps/forsaken-minimap.png` and
`forsaken-full-map.png`. The map was readable and did not cover the normal
playfield until opened. Navigation floorplans were checked by generated-image
inspection and catalog tests, not by live dungeon entry.
- Validation: 24 focused map/UI tests, TypeScript, production build, content
publication, and release candidate check passed.
## 2026-09-15 Reported Warder Stilgiss and Zereketh issues
- Reproduced the two submitted production reports in isolated Boss Testing
@@ -3764,3 +3779,311 @@ Vite's existing large-chunk advisory remains informational.
assignments, including Durnik as a dwarf, are covered by
`src/game/romFiveValidation.test.ts`; this level-1 scene does not exercise
the level-10 secondary-class unlock or elite-skill mechanics.
## RoM leveling loot and package catalog (2026-09-23)
- Rebuilt the client and reran `node scripts/playtest-rom-five.cjs` after the
leveling boss-loot rule and source package catalog changes. The live Rogue
roster preview showed the complete body and starter blade silhouette;
the in-dungeon scene showed the player and companion bodies with weapon,
shield, and staff silhouettes. No browser page errors occurred. Evidence:
`artifacts/playtest/rom-five/roster-preview.png` and
`artifacts/playtest/rom-five/dungeon-rogue.png`.
- This level-1 scene checks rendering. Focused boss-card, SQL loot, RuneWaker
loot, and RoM class tests check that leveling boss item drops are empty,
cards remain granted, and secondary stat growth uses the primary level.
Source package contents are cataloged as unverified and were not granted.
## RoM heirloom reference atlas (2026-09-23)
- Rebuilt the client and reran `node scripts/playtest-rom-five.cjs` after adding
the source-backed 1–79 native-stat references. The live Rogue roster showed
its body and starter blade; the Dwarf Mage roster showed its body and staff.
Wailing Caverns showed the Rogue player and companion bodies with equipped
blade, shield, and staff silhouettes. No browser page errors occurred.
Evidence: `artifacts/playtest/rom-five/roster-preview.png`,
`dwarf-mage-roster.png`, and `dungeon-rogue.png` in the same directory.
- These references are not equipped by the runtime, so this level-1 visual check
confirms the existing character path still renders. The reference suite
verifies the five class curves and source IDs at every level 1–79.
## Five-class RoM hybrid equipment (2026-09-23)
- Rebuilt and reran `node scripts/playtest-rom-five.cjs` after changing the five
active heirloom layouts to legal RoM weapon combinations with WoW combat
stats. The Rogue and Dwarf Mage roster previews showed complete bodies and
starter blade/staff silhouettes. Wailing Caverns showed the player and
companion bodies with visible blade, mace, shield, and staff silhouettes;
there were no browser page errors. Evidence: `artifacts/playtest/rom-five/`
`roster-preview.png`, `dwarf-mage-roster.png`, and `dungeon-rogue.png`.
- At level 1, the screenshot does not confirm each member's full inventory or
the level-10 secondary unlock. `src/game/romFiveValidation.test.ts` checks
every hybrid weapon slot for legality and stat growth at levels 1, 20, 40,
60, and 79. Targeted Shadoj reports at levels 20, 60, and 79 are under
`artifacts/balance-lab/rom-hybrid-l*-shadoj/`; they exposed low Mage damage
and a level-79 Priest death in each seed, so balance remains Needs Testing.
## RoM independent weapon slots (2026-09-23)
- Rebuilt and ran `node scripts/playtest-rom-five.cjs` after adding the trained
off-hand and ranged heirlooms. The Rogue roster preview and Wailing Caverns
scene showed a complete character body and starter blade; companion bodies,
shields, and weapons remained visible. The Dwarf Mage roster still showed its
body and two-hand staff. The playtest reported no browser page errors.
Evidence: `artifacts/playtest/rom-five/roster-preview.png`,
`dungeon-rogue.png`, and `dwarf-mage-roster.png` in the same directory.
- These level-1 screenshots do not show the later training unlocks. Focused
equipment tests verify the Knight/Scout bow at level 10, Rogue projectile at
level 12, and Scout/Rogue and Rogue off-hand weapons at level 16.
## RuneWaker sprite particle pilot (2026-09-24)
- Opened `scripts/combat-effects/rom-preview.html` in the in-app browser against
Vite and inspected frozen 250 ms frames after regenerating the archive. Knight
Charge showed a blue upward spray with orange sparks (3 layers), Whirlwind
Shield showed a small gold circular impact (3 layers), and Mage Electric Bolt
showed a blue-white projectile with streaks (3 layers). Browser console had
no errors or warnings. The preview tab is retained for inspection.
- These screenshots verify the sprite renderer and packaged textures at the
preview camera scale. A level-18 Knight and level-10 Mage were then run in
Wailing Caverns with Original visuals on using disposable local QA saves.
Whirlwind Shield's impact graph loaded at the caster position, but its
33 ms emission burst could disappear when a frame skipped past the last
rate key. Integrating the source rate over each frame restored the burst.
A 2.5 presentation scale made the gold shield ring visible around the
Knight at the dungeon camera distance during an isolated cast. Electric
Bolt cast and resolved in combat with no missing-original warning, though
the fast projectile was not visually captured in a still frame. Charge
remains to be checked in live movement. The next visual decoder target is
the Priest's holy `act_casting_c_hand.ros`: its only unsupported controller
is `CRuController_EventTrigger`, which the source uses for action callbacks.
## RuneWaker Priest holy cast (2026-09-24)
- The native exporter now records the `EventTrigger` payload. Source event 43
in holy `act_casting_c_hand.ros` requests looping 3D playback of
`sound/fx/skill/holy/act_casting_c_hand.wav`; it adds no visual layer. The
particle archive validates this exact event and marks the partial graph
renderer-ready. The shared combat manifest attaches its three sprite layers
and looped cast sound to Priest Urgent Heal, Heal, and Group Heal only.
- The in-app browser preview rendered the holy model as a gold star and swirling
particles with three loaded textures. In a disposable level-14 Priest save in
Wailing Caverns, Urgent Heal (2), Heal (6), and Group Heal (Shift+6) each
showed the gold cast effect on the isolated player during the cast. Group
Heal's longer cast kept the effect visible across successive frames. The
browser reported no original-effect load errors; its only warning was an
unrelated dependency deprecation warning. Audio loop and release/cancel
behavior passed focused unit tests, but the browser pass did not assess sound
by listening. The generated map is `docs/ROM_SPELL_MEDIA_MAPPING.md`.
## RoM Priest target and Rising Tide media (2026-09-24)
- Traced Urgent Heal's MagicObject component 501543 to ImageObject 573048 and
its target `act_benedictory_heal_t_down.ros`. Its `explode` motion requests a
one-shot 3D `act_benedictory_heal_t_down.mp3` at the healed target. The
browser graph renders two original sprite layers, the emitted 72-triangle
rune cylinder, and the animated gold ground decal with source color and
alpha tracks. It remains marked partial because the decal is placed on a
level ground plane rather than projected onto nearby terrain triangles.
- Traced Rising Tide's component 620408 to ImageObject 572062 and target
`act_riptide_t_freeze_hit.ros`. Its five-sprite target graph and the
`act_icecone01_c_hand.ros` cast and `act_icecone01_f.ros` projectile graphs
are attached to their respective phases. ROS direct-audio events identify
`act_icecone01_f.ros.mp3` on projectile release and
`act_riptide_t_freeze_hit.mp3` on target impact. Impact audio is positioned
at the target; release audio remains at the caster.
- The focused combat audio suite passed 12 tests, including distinct release
and target panning. The manifest test checked all three new MP3 paths against
the source ROS EventTrigger ID 43 payloads and packaged files.
- In the in-app RuneWaker preview, Urgent Heal's impact showed gold stars, a
green rune cylinder, and a gold target-ground ring at the frozen 250 ms frame.
Rising Tide's cast showed a blue ring, its release a bright blue flash, and
its target impact a blue and magenta burst. All four graphs loaded without
browser errors or warnings. Audio mapping was source and test verified; this
browser pass did not assess the sound by listening.
- Live Priest combat in Wailing Caverns confirmed Urgent Heal's original gold
rune circle and sparkles around the selected party target Veyra. In Boss
Testing against Verdan the Everliving, Rising Tide showed blue source
particles around the Priest during the cast, a blue projectile on release,
and a bright blue and magenta burst across Verdan's visible lower body at
arrival. The impact presentation now follows the projectile's 180–550 ms
travel time; large-target attachment uses visual bounds so the burst remains
in view. The browser reported no original-media or WebGL errors. Audio
timing and target panning are source and test verified, not listening tested.
## RoM primary-five media batch preview (2026-09-24)
- The batch now follows direct MagicCollect component ImageObjects to their
target `ExtendACT` models, packages 170 unique ROS graphs and source textures,
and attaches 74 renderer-ready graphs plus Urgent Heal's partial target graph.
The generated class audit counts 84 castable skills with at least one playable
original phase. Source audio is packaged for 125 of 129 cohort spell entries.
- In the in-app RuneWaker preview, frozen at 250 ms, Priest Ice Fog showed a
bright ice spray, Knight Holy Seal a blue sigil, Scout Snipe an orange target
ring, Rogue Sneak Attack a blue arc, and Mage Electric Explosion a layered
blue burst. All five graphs loaded and rendered; each status showed two or
three source sprite layers with matching textures.
- The first exporter pass falsely marked seven graphs ready despite emitter
geometry or sprite billboard modes the WebGL renderer rejects. The packager
now blocks those modes, and the parser test loads every ready graph. This
preview pass did not assess sound by listening or cast the five spells in
live combat; those are still needed to confirm placement and phase timing.
- A shared source alpha track rule then enabled four additional graphs whose
alpha anchor covers every sprite layer in the motion. The Mage Fireball
cast graph rendered a bright red and gold burst in the preview at 250 ms.
- RuneWaker audio phases now play all distinct packaged source cues together;
the previous weighted selector could drop one of several cast or impact
layers. A focused audio test confirms two impact files create two voices.
Listening QA is still needed for the balance of these layers.
## RoM Priest channel and stacked status icons (2026-09-25)
- In a disposable level-30 Priest/Knight Wailing Caverns run,
`scripts/playtest-rom-status-effects.cjs` cast Chain of Light at a nearby
Deviate Ravager. The cast bar showed Channeling and the rendered scene showed
a sustained white-gold beam between the Priest and target. The browser
reported no page errors. Evidence: `artifacts/playtest/rom-status-effects/chain-of-light-and-stacked-dot.png`.
- Three same-spell damage-over-time applications on that target appeared as one
icon with a small `3`; the browser assertion also checked the icon count.
This exercise checked HUD grouping, while focused combat tests checked four
separate channel damage pulses. Source audio was not assessed by listening.
## Raksha Temple collision performance (2026-09-26)
- A production-build offline playtest in headless Edge measured about 0.6 FPS
after Raksha Temple loaded. A CPU profile placed nearly all frame time in
Rapier character movement and physics. Its collision GLBs use interleaved
position buffers; passing the packed float array as XYZ vertices created
malformed trimeshes.
- After unpacking positions, the same 15-second sample recorded 600 frames at
16.67 ms average frame time (60 FPS). The entrance's WDB collision lacks a
walkable floor, so Raksha now uses its navigation mesh as physical ground.
The final browser pass retained 60 FPS, showed the party at 5/5 instead of
`ROUTE BLOCKED`, and kept the player at the entrance. An eight-second forward
movement sample explored 2 m and still held 60 FPS. Screenshots:
`playtest-artifacts/raksha-final-ground.png` and
`playtest-artifacts/raksha-moved.png`. The scene remains very dark;
this pass verified frame rate and floor support, not lighting quality.
## Scholomance rendering and population performance (2026-09-26)
- In a production-build offline run in headless Edge, Scholomance's entrance
averaged 31.78 ms per frame (about 31 FPS) over 15 seconds. A CPU profile
was dominated by Three.js render traversal and matrix updates, while the
visual asset audit counted 5,140 raw draw calls and 3,175 mesh nodes.
WebGL instrumentation saw about 867 draw calls per rendered frame before
the visual batching change.
- Scholomance had 191 static mob spawns within its former 95 m activation
radius at the entrance, across stacked indoor floors. Its 48 m radius and
global opaque instancing reduced the 15-second average to about 20.1 ms
per frame (about 50 FPS). Holding forward for 12 seconds explored 30 m;
the subsequent sample remained near 20.1 ms per frame with the party at
5/5. Entrance and movement screenshots showed intact geometry and no page
errors: `playtest-artifacts/scholomance-after.png` and
`playtest-artifacts/scholomance-moved.png`.
## Bite-Sized Dungeon Race (2026-09-26)
- `scripts/playtest-dungeon-race.cjs` used headless Edge with a fresh offline
character. Practice entered a Bite-Sized dungeon with five player-party seats,
displayed the top clock and NPC progress, and kept the game scene active
after a forced NPC win showed the “You lose” toast. The NPC roster contained
five members. Desktop and 960×540 Thor-size captures are in
`playtest-artifacts/dungeon-race/`.
- `scripts/playtest-dungeon-race-online.cjs` created two online accounts in
separate browser contexts. Both entered the same human match, dungeon, and
seed with each other as opponents. A third account queued alone and received
a five-member NPC team after the five-second fallback. Both scripts reported
no browser page errors.
- The practice screenshot check used a time jump to trigger the loss toast,
then injected a defeated boss-card fixture into the active combat store. The
normal automatic loot action added one Bazil Thredd card after the loss and
showed its reward window (`05-card-after-loss-fixture.png`). It did not
traverse and defeat the final boss through live combat. Server tests verify
that a losing clear is accepted for the dungeon time board.
## Dungeon Race exit regression (2026-09-26)
- `src/app/dungeonRaceStore.test.ts` covers immediate cleanup of active and
practice races, cancellation during an in-flight queue request, ordered
server leave before requeue, and an old preparation response after start.
- `scripts/playtest-dungeon-race-exit.cjs` used headless Edge with a fresh
online account. It left an NPC race through the pause menu, entered an
ordinary Bite-Sized dungeon without a race HUD, and requeued into a new
online match without an “already in a race” error. Screenshots are in
`playtest-artifacts/dungeon-race-exit/`. The offline practice playtest also
left after a modeled loss, confirmed the local race state cleared, and
started practice again. Browser page errors were absent in both passes.
## Bite-Sized spawn floor regression (2026-09-26)
- Fresh offline Dungeon Race practice entries in Wailing Caverns and Utgarde
Pinnacle previously settled about 0.5 m below the visible floor. During the
race start gate, Rapier physics was paused while PlayerRig accumulated
downward movement. Holding the player controller for that gate removed the
stored fall. Wailing keeps its original entrance; Utgarde now starts on the
connected Skadi wave corridor with three source first-wave guards.
- `scripts/playtest-bite-sized-spawns.cjs` entered both dungeons through the
menu, waited for content, intro, and race gate completion, then sampled five
seconds of play. Wailing was grounded at y=-31.016 from a -30.92 spawn;
Utgarde was grounded at y=104.812 from a 104.8 spawn. Both kept their entry
X/Z position, and there were no browser page errors. Before/after screenshots
and `result.json` are in `playtest-artifacts/bite-sized-spawns/`. The player
stands visibly on the floor in both after screenshots. Utgarde corridor samples
at z=370, 390, 410, 430, 445, and 460 also stood on collision in the live
client; the source guard points and boss position have collision and
navigation surfaces.
## Paused browser memory (2026-09-26)
- A fresh offline Wailing Caverns Bite-Sized run in headless Edge entered the
dungeon, opened the pause menu, and held it for 60 seconds. The paused scene
used R3F's `demand` frame loop and reported no browser page errors. Before
the classic talent lookup change, forced-GC JavaScript heap remained at
266 MiB across 0, 15, 30, and 60 seconds; DOM node count also stayed flat.
- `src/game/wow335AbilityCatalog.ts` now validates classic talent IDs through
the already loaded compact runtime talent catalog. The built classic ability
chunk fell from 41.4 MiB to 2.1 MiB, because it no longer includes the
36.6 MiB CoA source catalog. The same paused browser run measured 155, 155,
155, and 156 MiB after forced GC at 0, 15, 30, and 60 seconds. The paired
screenshots and samples are in `playtest-artifacts/pause-memory/`.
- The one-minute sample did not show a growing JavaScript heap. It does not
measure GPU allocations or rule out growth in a different dungeon or over
a longer idle period.
## Character-specific catalog loading (2026-09-26)
- The production build now imports `src/game/romBaseStatGrowth.generated.json`
(4.5 KB) for shared stat calculations. The full 1.7 MB
`romPlayerCatalog.generated.json` remains in the RoM ability chunk and is
requested only when a RoM class needs its abilities.
- `src/game/talentRuntimeCatalog.ts` loads its generated WoW/CoA talent data
after a non-RoM class requests combat catalogs. A fresh RoM Rogue main-menu
session fetched `romAbilityCatalog-*.js` without fetching either the
`wow335AbilityCatalog-*.js` or the talent-data chunk. A fresh WoW Priest
fetched the classic ability and talent-data chunks without fetching the
RoM ability chunk. Both sessions had no browser page errors.
- The RoM Rogue roster and Wailing Caverns dungeon smoke test passed with the
WoW/CoA talent data absent from the initial RoM page load. Browser request
results and menu screenshots are in `playtest-artifacts/catalog-loading/`;
dungeon screenshots are in `artifacts/playtest/rom-five/`.
## Browser memory reset at shell boundaries (2026-09-26)
- Browser ES modules cannot be unloaded individually from an active page.
Changing to a different character now reloads the page with a one-use
`sessionStorage` handoff to restore the selected character and destination.
Leaving a gameplay mode for the roster or main menu also reloads after the
run's normal save path. A live online group activity suppresses this reset
to avoid re-entering the same activity on reconnect.
- The main-menu catalog gate no longer reloads the full gameplay runtime or
retains talent art up front; it preloads those resources when gameplay starts.
- In a production Edge playtest, switching from a WoW Priest to a RoM Rogue
produced one reload and returned to the RoM main menu. The WoW talent editor
opened on demand before that switch. The new RoM page loaded
`romAbilityCatalog-*.js` without a WoW ability or `GameRuntime` chunk.
Leaving a RoM Wailing Caverns run produced a second reload and returned to
the same character's main menu without a `GameRuntime` chunk. Switching to
another RoM character also reloaded. The one-use handoffs were consumed,
and the page reported no errors. Results and screenshots are in
`playtest-artifacts/memory-reset/`.
+24 -8
View File
@@ -1,10 +1,10 @@
# RoM five class validation
Status: **Needs Testing**. This is a targeted diagnostic for Priest healer, Knight tank, Scout DPS, Rogue DPS, and Mage DPS. Elite-skill mechanics remain outside this pass.
Status: **Needs Testing**. This is a targeted diagnostic for Priest healer, Knight tank, Scout DPS, Rogue DPS, and Mage DPS. The accepted pair elite skills have focused source and runtime checks, with specific unresolved mechanics listed below.
## Cohort and rules
The five named builds and live NPCs use these race-legal pairings. Their secondary classes activate at level 10. RoM has no talent allocation; ordinary skills unlock by level, and the generated source's internal level variants do not create a player-facing spell-rank choice.
The five named builds and live NPCs use these race-legal pairings. Their secondary classes activate at level 10 and always share their primary class's level. RoM has no talent allocation; ordinary skills unlock by level, and the generated source's internal level variants do not create a player-facing spell-rank choice.
| Role | Class pair | NPC name | Race |
| --- | --- | --- | --- |
@@ -14,9 +14,9 @@ The five named builds and live NPCs use these race-legal pairings. Their seconda
| DPS | Scout / Rogue | Caelan | Elf |
| DPS | Mage / Warlock | Durnik | Dwarf |
`src/game/romFiveIdentities.ts` owns these identities for the live NPC templates and targeted canonical builds. The manual party uses Priest as the active healer and the other four classes as companions. This explicit cohort does not change Ready-only random parties, bracket certification, or the companion readiness registry. Elite skills can appear in a paired kit through the existing RoM catalog, but their mechanics have not yet been validated for this cohort.
`src/game/romFiveIdentities.ts` owns these identities for the live NPC templates and targeted canonical builds. The manual party uses Priest as the active healer and the other four classes as companions. This explicit cohort does not change Ready-only random parties, bracket certification, or the companion readiness registry. Elite skills can appear in a paired kit through the existing RoM catalog; focused checks now cover their translated effects, selected live casts, and source-conditional procs.
`npm run test:rom-five` checks the named pairings, legal races, level-10 unlock, role-specific heirloom layouts, ordinary ability coverage, and focused runtime mechanics. Knight is a tank seat in RoM generated parties. Rogue uses dual daggers, Knight a weapon and shield, and Scout a bow.
`npm run test:rom-five` checks the named pairings, legal races, level-10 unlock, role-specific hybrid heirloom layouts and level growth, ordinary and pair-elite ability coverage, and focused runtime mechanics. Knight is a tank seat in RoM generated parties. The equipment model has separate main-hand, off-hand, and ranged positions. Knight/Scout uses mace, shield, and bow from level 10; Priest/Knight uses mace and shield; Scout/Rogue uses dagger and bow, gaining an off-hand dagger at level 16; Rogue/Warrior gains a projectile at level 12 and an off-hand dagger at level 16; Mage/Warlock uses a two-handed staff. Priest and Mage retain empty ranged positions because their accepted pairings do not grant a ranged weapon. Their equipment uses the same level budget and WoW-shaped fields as the WoW heirlooms, allocated by RoM role. `src/game/wotlkStats.ts` gives these five WoW-equivalent base attribute, HP, and Mana curves with a 10% same-level secondary contribution from level 10. Other RoM classes retain native growth. Mage/Warlock is a RoM dwarf with the Human Mage Wrath curve as its base reference because Dwarf Mage has no Wrath 3.3.5 row.
## Findings and fixes in this pass
@@ -29,6 +29,20 @@ The five named builds and live NPCs use these race-legal pairings. Their seconda
- Priest Regenerate has one heal-over-time effect. Holy Candle and Scout Blood Arrow drain the caster's HP on their periodic ticks. Mage Flame and Fireball execute one hit, Meteor Shower five, and Thunderstorm three. Source helper summons and zero-damage effects no longer create extra combat actions for these skills.
- Scout Lasso, Rogue Numbing Dagger, and Mage Static Field apply one break-on-damage control effect to one target at a time. Scout Joint Blow only slows; Mage Discharge applies its stun without unrelated control effects. Knight Shield of Discipline absorbs the documented damage types and reflects a spell hit after the caster is struck, not on cast. Shield-dependent Knight skills require an equipped shield.
## Accepted pair skill checks
`src/game/romPriestKnightOverrides.ts`, `src/game/romScoutRogueOverrides.ts`, and `src/game/romMageWarlockOverrides.ts` specialize the five accepted pairs from source components. `src/game/romCombatMechanics.ts` selects their conditional cast effects; `src/game/combatStore.ts` handles proc actions, damage schools, conditional cooldowns, line and chain targeting, and timed effects. `src/game/partyRuntime.ts` installs learned NPC passive auras on their first cast and passes passive and target tags into the shared selector. The three pair validation files are part of `npm run test:rom-five`; the focused run passed 228 tests across eleven files on 2026-09-23. These include direct healing, area healing and damage, taunts, resource changes, offensive and defensive auras, crowd control, source-conditional skill checks, gear-slot unlocks, and selected live player/companion casts. Pure effect tests are evidence for translation and selection, not proof that every encounter timing is correct.
The hidden source references are now executable through their parent skills where RuneWaker treats them as replacements: Priest/Knight Wave Armor `499967`, Knight/Scout Holy Chains `499925` and Blood Arrow `499924`, Scout/Rogue Reflected Shot `499535` and Wrist Attack `499537`, Rogue/Warrior Poison Spray `499477` and Whirlwind `499481`, and Mage/Warlock Soul Pain `498746`, Plasma Arrow `498749`, Elements of Pride `498755`/`498756`, and Elemental Extraction `499812`/`499851`. Priest/Knight Devotion Halo `499966` is a separate level-70 button: it gives the whole party Wisdom and reduces other members' actual Mana spending. Poisonous Spit supplies its own Poison Powder and Poison Spray consumes the selected target's Infection poison before spreading its upgraded poison. Toxic Splash can grant Rage on Infection. The source-only Holy Chains trigger `499910` has no measurable player-facing payload in the extracted data, so no extra effect is invented.
The RuneWaker NPC object database now supplies Undead and Demon family labels through `src/game/romCreatureFamilies.ts`. Base Holy Chains targets those families; Evil Shackles opens living targets with a shorter root. Numbing Dagger rejects known Undead targets. Unknown or unregistered NPC families remain permissive. Ammo, Poison Powder, Rogue Throw projectiles, and other required bag reagents are treated as an unlimited, level-appropriate supply; casts do not consume an inventory stack.
Remaining source and runtime gaps:
1. Some source proc chances and magnitudes still use explicit hybrid estimates where the extracted source does not provide a direct WoW-stat conversion. NPC proc execution and resource side effects need more end-to-end coverage across encounters.
2. Rogue/Warrior One-Hand Axe Mastery does not benefit the current dual-dagger loadout. The newly equipped bow, projectile, and off-hand slots change the level-12-and-up gear budget, so previous balance diagnostics need repeating before promotion.
3. Mage/Warlock damage remains low relative to Scout and Rogue. Check its AI rotation, cast cadence, and WoW-equivalent scaling before adjusting encounter tuning or promoting RoM.
## Combat diagnostic
The following historical runs predate secondary-class assignment. They used `scripts/balance_lab.py` with `--source rom --party-mode manual`, exactly the five canonical build IDs for that level, and 180-second fights. Reports are under `artifacts/balance-lab/rom-five-primary-*`.
@@ -48,11 +62,13 @@ After the mechanics corrections, the targeted Shadoj run at level 60 with the sa
## Before Ready promotion
1. Resolve the persistent damage spread: Rogue is now far ahead of Scout and Mage, while Knight tank damage remains above both. Review source formulas, resource cadence, and rotations before tuning coefficients.
2. Finish source-specific restrictions that have no current inventory or creature-family authority. Numbing Dagger's projectile consumption and living/Humanoid target rule still need those systems. A source-row audit should continue beyond the corrected conditional skills; basic semantic coverage does not prove full tooltip parity.
2. Expand source-row and live NPC-cast audits beyond these corrected skills. Unknown NPC family IDs are permissive until their source records are classified; basic semantic coverage does not prove full tooltip parity.
3. Exercise behind-target requirements in a live spatial scene. The headless balance runner has no spatial target provider, so its Rogue damage can include casts that a live scene would reject.
4. Validate Priest throughput against encounters that actually demand sustained healing, and run RoM NPC seat tests after the mechanics above are resolved. The current manual run leaves Priest player-controlled.
5. Verify both Rogue daggers visually from an unobstructed angle. The live roster and dungeon screenshots in `artifacts/playtest/rom-five/` confirm rendered bodies and weapon silhouettes, but the off-hand dagger is unclear at their camera angles. Keep RoM entries at Needs Testing until the full class mechanics and role checks pass.
5. Verify the level-16 Rogue off-hand dagger visually from an unobstructed angle. The level-1 roster and dungeon screenshots in `artifacts/playtest/rom-five/` confirm rendered bodies and starter weapon silhouettes; they cannot show the later off-hand unlock. Keep RoM entries at Needs Testing until the full class mechanics and role checks pass.
The RoM source catalog and generated data remain authoritative for ability descriptions and components. The recorded combat results cover primary-only builds; rerun targeted diagnostics before drawing conclusions about the new pairings. Elite skill validation is deferred.
The RoM source catalog and generated data remain authoritative for ability descriptions and components. The older `rom-five-primary-*` results predate paired builds. The later `rom-hybrid-*` and `rom-hybrid-wowbase-*` manual reports contain the named secondary classes even though the report's specialization column reads "Primary" for the primary orientation.
RoM stat and equipment fidelity is a separate prerequisite for balance certification. `docs/ROM_STATS_AND_GEARING.md` records the source conversion coefficients, current WoW-shaped gear adapter, native item import gaps, and the players-only RoM online group boundary.
The chosen gameplay model uses a WoW-stat hybrid instead of requiring native RoM equipment conversion for Ready promotion. `docs/ROM_STATS_AND_GEARING.md` records the source coefficients, active hybrid rules, remaining balance risks, and the players-only RoM online group boundary. Initial three-seed Shadoj screens at levels 20, 60, and 79 defeated the boss without a wipe, but Mage damage was far below the other two DPS builds and the Priest died once in each level-79 run. Those one-encounter results do not establish balance. Leveling bosses award cards but no items. `docs/ROM_LEVELING_GEAR_PACKAGES.md` records the source-confirmed package families; exact historical contents remain unverified and would need a WoW-stat translation before grant.
After the WoW-equivalent base change, one-seed manual Shadoj screens at levels 20, 60, and 79 defeated the boss with zero deaths. At level 60, Mage dealt about 185 DPS versus Scout 971 and Rogue 1,026; at level 79 the corresponding values were about 242, 1,418, and 1,538. Reports are under `artifacts/balance-lab/rom-hybrid-wowbase-l20-shadoj`, `rom-hybrid-wowbase-l60-shadoj`, and `rom-hybrid-wowbase-l79-shadoj`. These narrow screens do not certify class or dungeon balance.
+41
View File
@@ -0,0 +1,41 @@
# RoM level 1–79 heirloom reference
Status: **source-backed design data, separate from equipped hybrid heirlooms**. `scripts/runewaker-pipeline/export-rom-heirloom-reference.mjs` generates `src/game/generated/romHeirloomReference.json` from the sibling RuneWaker client snapshot, or from `RUNEWAKER_ROOT`. Run `node scripts/runewaker-pipeline/export-rom-heirloom-reference.mjs` after refreshing `src/game/generated/romLevelingGearPackages.json`. `src/game/romHeirloomReference.ts` provides typed level and item lookups. The 5 × 79 curves cover the five selected primary classes; their secondary class always shares the primary level.
## Evidence and scope
The [RoM Item Sets index](https://runesofmagic.fandom.com/wiki/Category:Item_Sets) and [Leveling Packages index](https://runesofmagic.fandom.com/wiki/Category:Leveling_Packages) identify leveling families. The [Issac](https://runesofmagic.fandom.com/wiki/Issac%27s_%28Item_Set%29), [Savage Lands](https://runesofmagic.fandom.com/wiki/Savage_Lands_%28Item_Set%29), [Lance's Support](https://runesofmagic.fandom.com/wiki/Lance%27s_Support), and [Cloth Armor Package (Multi)](https://runesofmagic.fandom.com/wiki/Cloth_Armor_Package_%28Multi%29) pages connect sets with the level-50, 55, 60, and 65 rewards. `docs/ROM_LEVELING_GEAR_PACKAGES.md` records the quest/package IDs and distinguishes wiki claims from the client package links that remain undecoded.
The local client provides **3,026 Good-quality (blue) armor and weapon records** with required level 1–79. Their IDs, names, levels, armor/weapon types, positions, suit IDs, base effects, deterministic fixed attributes, and decoded native stats are retained as source references. Of those, 727 have attribute IDs absent from this snapshot's `addpowerobject.db`, and 98 have effects outside the mapped core stat model; they remain visible but are excluded from curve candidates. Source file SHA-256 values are embedded for reproducibility. The generated output is design evidence, not a claim that all 3,026 items are normally obtainable at their required level.
The parser uses the local `ObjectStruct.h` `GameObjectWeaponENUM`, `GameObjectArmorENUM`, and `WearEqTypeENUM`, and fixed record layouts from `armorobject.db`, `weaponobject.db`, and `addpowerobject.db`. Numeric quality 2 is the client's Good/blue tier, consistent with the [wiki quality legend](https://runesofmagic.fandom.com/wiki/Category:Items). An effect with type 7 adds its value to all five primary attributes. Only 100% fixed item attributes are summed; random attribute rolls are listed by ID without being assumed.
## Design rules
| Primary class | Armor reference | Main weapon reference | Other weapon reference |
| --- | --- | --- | --- |
| Knight/Scout | Plate | One-hand mace | Shield |
| Priest/Knight | Cloth | Two-hand staff | None in this curve |
| Scout/Rogue | Leather | Dagger | Bow |
| Rogue/Warrior | Leather | Dagger | Off-hand dagger |
| Mage/Warlock | Cloth | Two-hand staff | None |
These source-reference choices follow the [official class equipment guide](https://archive.runesofmagic.gameforge.com/en/classes.html). Scout is **leather**, not chain. Priest can also legally use a wand or mace with a shield, but this client has only one complete blue wand record at level 57. The native Priest reference curve uses the well-populated staff series; the active WoW-stat hybrid equips a mace and shield to support the Priest/Knight pairing. A shield must not be combined with a two-hand staff. The Mage/Warlock remains dwarf; race has no effect on the equipment reference table itself.
The generator chooses a median item at each observed required level using role-oriented native-stat scores, then substitutes matching 50/55/60/65 package-suit armor pieces at those milestone levels. For levels between source points it interpolates each stat; below the first point or above the last it scales from the nearest source point. It rounds positive values to at least one and retains the prior level's value when the next candidate would lower a stat. `referenceItemIds` are the points behind that level, while `packageId` identifies a matched package-suit milestone. A curve row can therefore combine retained prior stats with current source values and **is not an exact copy of its listed item**. These scoring coefficients are selection heuristics, not certified combat stat weights.
All five profiles cover head, hands, feet, chest, legs, back, waist, shoulder, neck, finger, trinket, and main hand. Off hand exists for Knight and Rogue; Scout has ranged. Source earrings are represented by the game's `trinket` reference slot. The source catalog has no matching wrist slot for this export. The game must resolve its final slot design before equipping the sets.
## Numeric cross-checks
| Source item | Required level | Confirmed decoded effects |
| --- | ---: | --- |
| Magic Robe of the Lake, ID 229164 | 65 | 1,029 DEF, 1,655 MDEF, 273 HP, 290 MATK, 100 Stamina, 67 Intelligence |
| Gale Staff, ID 213416 | 60 | 903 weapon damage, 1,094 magical weapon damage, 172 MATK, 20 Stamina, 56 Intelligence, 56 Wisdom |
| Rescuer's Blessing, ID 212999 | 50 | 440 Heal Point, 660 MATK, 1,717 magical weapon damage, 50 Wisdom; a useful healing reference but an unusually strong source item, so it is not a median staff anchor |
The [Gale Staff wiki entry](https://runesofmagic.fandom.com/wiki/Gale_Staff) provides an independent level-60 cross-check. More source IDs, including each 65 package chest, are queryable through `romSourceReferenceItem` and each curve's `referenceItemIds`.
## Use in the hybrid runtime
The active RoM runtime uses WoW-shaped heirloom stats and WoW combat formulas by design; see `docs/ROM_STATS_AND_GEARING.md`. These native source curves remain references for level progression and class identity. Feeding native values directly into the WoW path would produce meaningless balance. The five active hybrid loadouts use legal weapon layouts, a WoW-stat level budget, and no comparison against WoW boss drops. Tune Knight survival, Priest healing/sustain, and Scout/Rogue/Mage damage across levels 1–79. Keep dungeon bosses on the existing cards-only leveling policy; heirlooms and package gear should come from progression, not dungeon equipment drops. Elite skills remain out of scope.
+44
View File
@@ -0,0 +1,44 @@
# RoM leveling gear package catalog
Status: **quest choices and several armor-set families verified; exact client package expansion unverified**. This is source evidence for a RoM leveling route, not a runtime gear grant. The generated inventory is `src/game/generated/romLevelingGearPackages.json`, owned by `scripts/runewaker-pipeline/export-leveling-gear-packages.mjs`. Refresh it with `node scripts/runewaker-pipeline/export-leveling-gear-packages.mjs` against the sibling RuneWaker client snapshot, or set `RUNEWAKER_ROOT` to another snapshot. Native item-stat references and 1–79 heirloom curves are in `docs/ROM_HEIRLOOM_REFERENCE.md`.
## Confirmed quest milestones
The client `Resource/data/questdetailobject.db` links each quest to the armor-category reward packages in the order below. `itemobject.db` confirms that the later parent packages and their similarly named weapons and armor subpackages exist. The client strings describe each later parent choice as giving one weapons package and one armor package; their exact item links have not been decoded. The generated catalog records matching seven-piece source suits from `suitobject.db` and `armorobject.db`; wiki pages independently identify several as package contents, but do not establish every weapon, accessory, quantity, or version in this client snapshot.
| Quest level and name | Quest ID | Plate package | Chain package | Leather package | Cloth package |
| --- | ---: | ---: | ---: | ---: | ---: |
| 5 Equip Yourself | 423783 | — | 209025 | 209024 | 209015 |
| 30 Key to Victory | 423771 | 208802* | 208803 | 208804 | 208805 |
| 50 Appropriate Support | 424596 | 240384 | 240387 | 240390 | 240393 |
| 55 Rewards for Students | 424597 | 240353 | 240356 | 240359 | 240362 |
| 60 Lance's Support | 424598 | 240341 | 240344 | 240347 | 240350 |
| 65 Return to Dalanis | 424213 | 240304 | 240307 | 240310 | 240313 |
*The level-30 source name is **World Battlefield Bonus Armor Package**, without the word “Plate.” Its plate classification is only a likely reading of its position beside chain, leather, and cloth; the generated catalog uses `armor` until verified. This is a battlefield reward, so it is an optional source candidate rather than a guaranteed leveling grant. The level-5 quest has only three choices, and its chain choice is named **Chainmail Armor Box**. Neither early tier has confirmed individual package contents. The level-65 rewards are the Redhill Mountains Pioneer packages. The source-linked subpackage IDs, source names, matching suit IDs, seven armor member IDs, and their actual required levels for levels 50–65 are in the JSON catalog. Some level-50 suit members have required levels below 50; the quest reward level is the milestone, not an assertion that every item requires that level.
## Wiki cross-checks
The [Item Sets index](https://runesofmagic.fandom.com/wiki/Category:Item_Sets) and [Leveling Packages index](https://runesofmagic.fandom.com/wiki/Category:Leveling_Packages) give additional reference families beyond the client quests. Their set pages support these specific connections:
| Milestone | Package or set evidence | Confidence for armor pieces |
| --- | --- | --- |
| 50 | [Issac's sets](https://runesofmagic.fandom.com/wiki/Issac%27s_%28Item_Set%29) identify four seven-piece sets in the level-50 armor packages. | Wiki-confirmed family; source suit member IDs in catalog. |
| 55 | [Savage Lands sets](https://runesofmagic.fandom.com/wiki/Savage_Lands_%28Item_Set%29) identify Haz, Eduth, Luzan, and Rufa with Bodo Treasure rewards. | Wiki-confirmed family; source suit member IDs in catalog. |
| 60 | [Lance's Support](https://runesofmagic.fandom.com/wiki/Lance%27s_Support) identifies Rh'anka, Sley, Tempest, and Croso package choices; the [Tempest set](https://runesofmagic.fandom.com/wiki/Tempest_Set_%28Item_Set%29) supplies a piece-level cross-check. | Wiki-confirmed family; source suit member IDs in catalog. |
| 65 | [Cloth Armor Package (Multi)](https://runesofmagic.fandom.com/wiki/Cloth_Armor_Package_%28Multi%29) names the Lake Energy cloth set at 65; the other armor categories currently have matching source suits but need their package pages checked. | Cloth wiki-confirmed; other categories matched by source name/type/level. |
The wiki is a reference for the RoM item families, while the local RuneWaker records are the authority for item IDs and numeric stats in this snapshot. Page descriptions alone do not prove that this snapshot's package-opening script grants the same items.
## Applying these to the five classes
The current game marks Knight as plate, Priest and Mage as cloth, and Rogue and Scout as leather in `src/app/characterCatalog.ts`. The [official class guide](https://archive.runesofmagic.gameforge.com/en/classes.html) confirms that Scout wears cloth or leather, while Knight can wear plate and Priest/Mage cloth. This suggests one plate, two leather, and two cloth choices for the five-class cohort; chain remains in the source catalog but is not a default choice for these primary classes. A secondary class shares the primary's level and does not require a second package progression.
During leveling, `src/game/lootPolicy.ts` suppresses all boss item drops. Boss cards are awarded separately. Package gear should come from a level or quest milestone, never from a dungeon boss loot table. A fallback starter kit can cover levels for which no verified source package exists.
## Gaps before playable package gear
1. Locate and verify package expansion records or scripts for every parent and subpackage, including weapon selections, accessories, exact armor items, and quantities. Matching a suit by name, category, and required level is not enough to grant its pieces as historical package contents.
2. The source-backed 1–79 reference now decodes native equipment slots, weapon/armor type, base stats, and deterministic fixed attributes. The active RoM path uses WoW-stat hybrid heirlooms; historical package pieces would need a defined translation into that stat budget and legal slot/weapon checks before they become equippable rewards. A full native item-instance and combat adapter is optional future fidelity work.
3. Find a complete, generally available source-backed route from 1–49 and above level 65. Level 5 has only cloth, leather, and chain choices; level 30 has four battlefield choices. Logar/Hope package names exist in the item table, but their acquisition route and contents are not confirmed. Avoid inventing missing plate or weapon contents.
4. Make the milestone grant idempotent and persistent, then verify class eligibility, package selection, leveling through each milestone, and native-stat combat outcomes for all five builds.
File diff suppressed because it is too large. Load diff
+73
View File
@@ -0,0 +1,73 @@
# RuneWaker media batch plan
Generated by `npm run effects:rom:plan` from the current primary-five spell manifest and decoded ROS archive. It groups remaining controller and timing work. The manifest generator already attaches source direct audio events at supported phase boundaries.
The archive has 170 unique models: 74 ready, 1 partial original, 0 fully decoded but unverified, 95 blocked, and 0 failed. 125 of 129 source spell entries have at least one packaged audio cue.
## Shared controller blockers
Counts overlap when a model has several controller families. A family should be ported and verified once against representative models before its other users are considered for promotion.
| Controller family | Models | Source spell IDs using those models |
| --- | ---: | ---: |
| CRuController_EventTrigger | 148 | 124 |
| CRuController_GlobalAlpha | 47 | 49 |
| CRuEntity_Particle_Visualizer_Entity | 35 | 44 |
| CRuController_Particle_Visualizer_Entity | 34 | 43 |
| CRuController_GlobalColor | 26 | 26 |
| RuneWaker browser emitter geometry mode | 18 | 21 |
| CRuController_Decal | 17 | 17 |
| CRuController_GhostTrail | 11 | 15 |
| CRuController_Hierarchy | 11 | 12 |
| CRuController_Entity | 5 | 4 |
| RuneWaker browser sprite mode | 5 | 5 |
| CRuController_UVFX | 3 | 3 |
| CRuController_MaterialTexture | 1 | 1 |
| CRuController_PolyLine | 1 | 1 |
## Fully decoded models awaiting visual review
| Source model | Spell-phase references | Sprite layers | Blockers |
| --- | ---: | ---: | --- |
## Event-only models still blocked
These models have sprite data but include an event callback outside the validated visual-only set. Effect-spawning and projectile-flight callbacks require browser behavior before their visuals can be promoted.
| Source model | Spell-phase references | Sprite layers | Blockers |
| --- | ---: | ---: | --- |
| model/fx/skill/ice/act_cone01_c_hand.ros | 3 | 12 | CRuController_EventTrigger |
| model/fx/skill/fire/act_fireball02_t_hit_point.ros | 1 | 5 | CRuController_EventTrigger |
| model/fx/skill/holy/act_armorup_c_top.ros | 1 | 8 | CRuController_EventTrigger |
| model/fx/skill/physical/act_concentration_t_top.ros | 1 | 3 | CRuController_EventTrigger |
| model/fx/skill/physical/act_focus_c_top.ros | 1 | 4 | CRuController_EventTrigger |
| model/fx/skill/physical/act_fracture01_t_freeze_hit.ros | 1 | 6 | CRuController_EventTrigger |
There are 6 event-only models. The archive also exposes 3 model/phase audio-event candidates missing from the current packaged manifest. They need delayed timing or a later lifecycle phase; an ROS motion name alone is insufficient. Full per-model evidence is in `docs/ROM_MEDIA_BATCH_PLAN.json`.
## Largest blocker combinations
| Controller signature | Models | Source spell IDs | Example model |
| --- | ---: | ---: | --- |
| CRuController_EventTrigger + CRuController_Particle_Visualizer_Entity + CRuEntity_Particle_Visualizer_Entity | 13 | 20 | model/fx/skill/earth/act_casting_c_hand.ros |
| CRuController_EventTrigger | 9 | 11 | model/fx/skill/fire/act_fireball02_t_hit_point.ros |
| CRuController_Decal + CRuController_EventTrigger + CRuController_GlobalAlpha + CRuController_GlobalColor | 8 | 8 | model/fx/skill/earth/act_vriusinfection_t_freeze_down_e.ros |
| CRuController_EventTrigger + CRuController_GlobalAlpha | 6 | 7 | model/fx/skill/fire/act_fireball01_c_hand_i.ros |
| CRuController_EventTrigger + CRuController_GlobalAlpha + CRuController_Particle_Visualizer_Entity + CRuEntity_Particle_Visualizer_Entity | 5 | 6 | model/fx/skill/holy/act_chain_t_down.ros |
| CRuController_EventTrigger + CRuController_Particle_Visualizer_Entity + CRuEntity_Particle_Visualizer_Entity + RuneWaker browser emitter geometry mode | 5 | 5 | model/fx/skill/holy/act_moist_t_down.ros |
| CRuController_EventTrigger + RuneWaker browser emitter geometry mode | 5 | 8 | model/fx/skill/physical/act_deep_thought_c_down_e.ros |
| CRuController_Particle_Visualizer_Entity + CRuEntity_Particle_Visualizer_Entity | 4 | 10 | model/fx/skill/dark/act_dark_hand_wave_c_hand_e.ros |
| CRuController_Decal + CRuController_Entity + CRuController_EventTrigger + CRuController_GlobalAlpha + CRuController_GlobalColor | 2 | 1 | model/fx/skill/holy/act_scrifice_healing_c_freeze_down_e.ros |
| CRuController_Entity + CRuController_EventTrigger | 2 | 2 | model/fx/skill/holy/act_judgement_t_hit_point.ros |
| CRuController_EventTrigger + CRuController_GhostTrail + CRuController_GlobalAlpha | 2 | 4 | model/fx/skill/fire/act_fireball02_f.ros |
| CRuController_EventTrigger + CRuController_GhostTrail + CRuController_GlobalAlpha + CRuController_GlobalColor + CRuController_Hierarchy | 2 | 2 | model/fx/skill/physical/act_deflecting_shot_f.ros |
| CRuController_EventTrigger + CRuController_GhostTrail + CRuController_Particle_Visualizer_Entity + CRuEntity_Particle_Visualizer_Entity | 2 | 2 | model/fx/skill/ice/act_icearrow_f.ros |
| CRuController_EventTrigger + RuneWaker browser emitter geometry mode + RuneWaker browser sprite mode | 2 | 2 | model/fx/skill/physical/act_attackspeedup_c_down.ros |
| CRuController_Decal + CRuController_Entity + CRuController_GlobalAlpha + CRuController_GlobalColor + RuneWaker browser emitter geometry mode + RuneWaker browser sprite mode | 1 | 1 | model/fx/skill/physical/act_beckon_holy_c_freeze_down_e.ros |
## Batch workflow
1. Run `npm run effects:rom:batch` to refresh source links, package audio and ROS graphs, attach verified phases, and rebuild this plan.
2. Review the remaining event timing candidates and largest shared blocker families above.
3. Port one controller family in the exporter/WebGL runtime and add a parser check that every promoted graph is renderable.
4. Regenerate the manifest and media audit, replay representative graphs in the RuneWaker preview, then spot-check distinct motion and attachment patterns in live combat.
+206
View File
@@ -0,0 +1,206 @@
# RuneWaker primary-five spell media mapping
Generated by `node scripts/combat-effects/audit-rom-media.mjs --write` from the player catalog and combat effect manifest. The five accepted pairings follow `src/game/romFiveIdentities.ts`. Castable skills are listed; passives have no cast media event.
The effect column records original client ROS/RAS references and their cast phase. 84 castable skills have browser-playable original phases; other source visuals use the game's generated runtime effect until their controller families are supported. A `partial original` phase renders verified source layers and declares its remaining fidelity limit. The sound column lists packaged client cues on their mapped phase. A dash means the source record or matching sound file did not provide that media.
| Primary class | Skill group | Castable skills | With source effect | With packaged sound | With playable original effect |
| --- | --- | ---: | ---: | ---: | ---: |
| Priest | Base | 25 | 23 | 23 | 18 |
| Priest | Accepted elite pair | 6 | 6 | 6 | 3 |
| Knight | Base | 19 | 19 | 18 | 13 |
| Knight | Accepted elite pair | 6 | 6 | 6 | 2 |
| Scout | Base | 20 | 20 | 19 | 13 |
| Scout | Accepted elite pair | 4 | 4 | 4 | 4 |
| Rogue | Base | 20 | 20 | 18 | 15 |
| Rogue | Accepted elite pair | 4 | 4 | 4 | 2 |
| Mage | Base | 21 | 20 | 20 | 13 |
| Mage | Accepted elite pair | 5 | 5 | 5 | 1 |
## Priest — base
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Soul Source | 490288 | impact: act_soulspring_t_down.ros | impact: act_soulspring_t_down.ros.mp3 |
| Wave Armor | 490273 | impact: act_holywater_armor_t_top.ros<br>aura-start: act_watershieldbuff_c_down_i.ros | impact: act_holywater_armor_t_top.mp3<br>aura-start: act_watershieldbuff_c_down_i.ros.mp3 |
| Heal | 490261 | cast-start: act_casting_c_hand.ros<br>impact: act_mainheal_t_down_e.ros | cast-start: act_casting_c_hand.wav<br>impact: act_friendly_t_down_i.mp3<br>impact: act_mainheal_t_down_e.ros 2.mp3 |
| Ice Fog | 490274 | cast-start: act_cone01_c_hand.ros<br>impact: act_icefog_t_freeze_hit_e.ros | cast-start: act_cone01_c_hand.ros.mp3<br>impact: act_icefog_t_freeze_hit_e.ros.mp3 |
| Bone Chill | 491165 | cast-start: act_ice_hand_wave_c_hand_e.ros<br>impact: act_icefog_t_freeze_hit_e.ros | cast-start: act_ice_hand_wave_c_hand_e.ros.mp3<br>impact: act_icefog_t_freeze_hit_e.ros.mp3 |
| Resurrection | 490280 | cast-start: act_casting_c_hand.ros<br>impact: act_anabiosis_t_down.ros | cast-start: act_casting_c_hand.wav<br>impact: act_anabiosis_t_down.mp3 |
| Soul Bond | 490297 | cast-start: act_casting_c_hand.ros<br>impact: act_holycross_t_top.ros | cast-start: act_casting_c_hand.wav<br>impact: act_holycross_t_top.mp3 |
| Grace of Life | 490290 | impact: act_rainpray_t_down.ros | impact: act_rainpray_t_down.mp3 |
| Group Heal | 490281 | cast-start: act_casting_c_hand.ros<br>impact: act_anima_t_name_title.ros | cast-start: act_casting_c_hand.wav<br>impact: act_anima_t_name_title.mp3 |
| Chain of Light | 491164 | cast-start: act_light_chain_c_hand_i.ros<br>impact: act_light_chain_t_down_i.ros<br>impact: act_light_chainhit_t_down_e.ros | cast-start: act_powergather_c_right_hand.mp3<br>impact: act_light_chainhit_t_down_e.ros.mp3 |
| Amplified Attack | 490491 | cast-start: act_casting_c_hand.ros<br>impact: act_power_up_t_down.ros | cast-start: act_casting_c_hand.wav<br>impact: act_power_up_t_down.mp3 |
| Ice Fog | 490275 | cast-start: act_cone01_c_hand.ros<br>impact: act_icefog_t_freeze_hit_e.ros | cast-start: act_cone01_c_hand.ros.mp3<br>impact: act_icefog_t_freeze_hit_e.ros.mp3 |
| Healing Salve | 490294 | impact: act_moist_t_down.ros | impact: act_moist_t_down.mp3 |
| Blessing of Humility | 490301 | — | — |
| Ice Fog | 490276 | cast-start: act_cone01_c_hand.ros<br>impact: act_icefog_t_freeze_hit_e.ros | cast-start: act_cone01_c_hand.ros.mp3<br>impact: act_icefog_t_freeze_hit_e.ros.mp3 |
| Holy Candle | 490304 | — | — |
| Cleanse | 491175 | impact: act_dispersion_t_freeze_hit_e.ros | impact: act_dispersion_t_freeze_hit_e.mp3 |
| Advanced Resurrection | 491176 | cast-start: act_casting_c_hand.ros<br>impact: act_anabiosis_t_down.ros | cast-start: act_casting_c_hand.wav<br>impact: act_anabiosis_t_down.mp3 |
| Supreme Resurrection | 491177 | cast-start: act_casting_c_hand.ros<br>impact: act_anabiosis_t_down.ros | cast-start: act_casting_c_hand.wav<br>impact: act_anabiosis_t_down.mp3 |
| Urgent Heal | 491147 | cast-start: act_casting_c_hand.ros<br>impact: act_benedictory_heal_t_down.ros (partial original) | cast-start: act_casting_c_hand.wav<br>impact: act_benedictory_heal_t_down.mp3 |
| Rising Tide | 490256 | cast-start: act_icecone01_c_hand.ros<br>release: act_icecone01_f.ros<br>impact: act_riptide_t_freeze_hit.ros | cast-start: act_cone01_c_hand.mp3<br>release: act_icecone01_f.ros.mp3<br>impact: act_riptide_t_freeze_hit.mp3 |
| Regenerate | 490269 | cast-start: act_casting_c_hand.ros<br>cast-start: act_holy_hand_wave_c_hand_e.ros<br>impact: act_blessed_heal_t_down.ros | cast-start: act_casting_c_hand.wav<br>impact: act_blessed_heal_t_down.mp3 |
| Holy Aura | 490300 | cast-start: act_casting_c_hand.ros<br>cast-start: act_flash_candle_c_down_e.ros<br>impact: act_flash_candle_t_freeze_hit.ros | cast-start: act_casting_c_hand.wav<br>cast-start: act_flash_candle_c_down_e.ros.mp3<br>impact: act_flash_candle_t_freeze_hit.mp3 |
| Magic Barrier | 491166 | impact: act_magicdefence_t_down_e.ros | impact: act_anima_t_name_title.mp3 |
| Blessed Spring Water | 490298 | cast-start: act_icecone01_c_hand.ros<br>impact: act_springblessing_t_top.ros | cast-start: act_cone01_c_hand.mp3<br>impact: act_springblessing_t_top.mp3 |
## Priest — accepted elite pair
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Last Prayer | 491648 | impact: act_benedictory_heal_t_down.ros | impact: act_benedictory_heal_t_down.mp3 |
| Divine Incarnation | 491366 | cast-start: act_sacredincarnation_c_down.ros | cast-start: act_sacredincarnation_c_down.mp3 |
| Calm Heart | 492639 | cast-start: act_casting_c_hand.ros<br>impact: act_fearless_t_down_e.ros | cast-start: act_casting_c_hand.wav<br>impact: skill_fearless_t_down_e.mp3 |
| Sacrificial Cure | 492934 | cast-start: act_scrifice_healing_c_freeze_down_e.ros<br>impact: act_scrifice_healing_t_down_e.ros | cast-start: skill_scrifice_healing_c_freeze_down_e.mp3<br>impact: skill_scrifice_healing_t_down_e.mp3 |
| Holy Power | 493044 | cast-start: act_holyenergy_c_down_e.ros<br>cast-start: act_holyenergy_c_down_i.ros | cast-start: skill_holyenergy_c_down_e.ros.mp3<br>cast-start: skill_holyenergy_c_down_i.mp3 |
| Light Fairy | 493271 | cast-start: act_casting_c_hand.ros<br>cast-start: act_beckon_holy_c_freeze_down_e.ros | cast-start: act_casting_c_hand.wav |
## Knight — base
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Holy Shield | 490495 | cast-start: act_godshield_c_down.ros | cast-start: act_godshield_c_down.mp3 |
| Holy Seal | 490149 | impact: act_sanink_c_hit_point_e.ros | impact: act_sanink_c_hit_point_e.mp3 |
| Holy Power Explosion | 490172 | cast-start: act_physical_casting_c_hand.ros<br>cast-start: act_vengeance_c_down.ros | cast-start: act_physical_casting_c_hand.mp3<br>cast-start: act_vengeance_c_down.mp3 |
| Whirlwind Shield | 490175 | cast-start: act_shieldstorm_c_down_e.ros<br>impact: act_shieldstorm_t_freeze_hit_e.ros | — |
| Shackles of Light | 490189 | cast-start: act_chain_c_shoot02.ros | cast-start: act_chain_c_shoot02.mp3 |
| Resolution | 490168 | cast-start: act_physical_casting_c_hand.ros<br>cast-start: act_responsibility_c_down.ros | cast-start: act_physical_casting_c_hand.mp3<br>cast-start: act_responsibility_c_down.mp3 |
| Charge | 490150 | cast-start: act_charge02_c_down.ros<br>impact: act_strikeback_t_hit_point.ros | cast-start: act_charge02_c_down.mp3<br>impact: act_strikeback_t_hit_point.mp3 |
| Threaten | 490153 | impact: act_intimidation_t_freeze_hit_e.ros | impact: act_intimidation_t_freeze_hit_e.mp3 |
| Shield of Valor | 490468 | cast-start: act_courage_shield_c_top.ros | cast-start: act_courage_shield_c_top.mp3 |
| Truth Shield Bash | 490186 | cast-start: act_truth_shield_c_top.ros<br>impact: act_judge_shield_t_hit_point.ros | cast-start: act_truth_shield_c_top.mp3<br>impact: act_judge_shield_t_hit_point.mp3 |
| Shock | 490154 | cast-start: act_holy_hand_wave_c_hand_e.ros<br>impact: act_shock_t_hit_point.ros | impact: act_shock_t_hit_point.mp3 |
| Holy Strength | 491321 | cast-start: act_manafreeback_c_down_e.ros | cast-start: act_manafreeback_c_down_e.mp3 |
| Hall of Dead Heroes | 495724 | aura-start: act_herosoul_buff_c_top.ros | aura-start: act_herosoul_buff_c_top.mp3 |
| Punishment | 490151 | cast-start: act_holy_hand_wave_c_hand_e.ros<br>impact: act_judgement_t_hit_point.ros | impact: act_judgement_t_hit_point.mp3 |
| Holy Strike | 490148 | impact: act_san_sword_t_down_e.ros | impact: act_san_sword_t_down_e.mp3 |
| Disarmament | 490159 | impact: act_disarm01_t_hit_point.ros | impact: act_disarm01_t_hit_point.mp3 |
| Strike of Punishment | 490179 | impact: act_brokenhit_t_hit_point.ros | impact: act_brokenhit_t_hit_point.mp3 |
| Enhanced Armor | 490152 | cast-start: act_armorup_c_top.ros | cast-start: act_armorup_c_top.mp3 |
| Shield of Discipline | 490192 | cast-start: act_discipline_shield_c_top.ros<br>impact: act_discipline_shield_t_hit_point.ros | cast-start: act_discipline_shield_c_top.mp3<br>impact: act_capacity_crash_c_freeze_hit_e.ros.mp3 |
## Knight — accepted elite pair
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Arrow of Vengeance | 491616 | release: act_revengearrow_f_i.ros<br>impact: act_revengearrow_t_hit_point_e.ros | impact: act_revengearrow_t_hit_point_e.mp3 |
| Holy Chains | 491613 | impact: act_chain_t_down.ros | impact: act_chain_t_down.mp3 |
| Heavenly Arrow | 491354 | cast-start: act_holy_hand_wave_c_hand_e.ros<br>impact: act_heavenarrow_t_down_e.ros | impact: act_heavenarrow_t_down_e.mp3 |
| Sacred Resistance | 492939 | cast-start: act_holyresist_c_down_e.ros | cast-start: act_acheignore_c_down_e.mp3 |
| Sacred Protection | 493046 | cast-start: act_holyprotect_c_down_e.ros<br>impact: act_ingoreself_t_down_e.ros | cast-start: skill_holyprotect_c_down_e.mp3<br>impact: skill_ingoreself_t_down_e.mp3 |
| Disregard Danger | 493273 | cast-start: act_fire_hand_wave_c_hand_e.ros<br>impact: act_ingoreself_t_down_e.ros | cast-start: act_fire_hand_wave_c_hand_e.ros.mp3<br>impact: skill_ingoreself_t_down_e.mp3 |
## Scout — base
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Arrow of Essence | 490494 | impact: act_mentalarrow_t_freeze_hit.ros | impact: act_mentalarrow_t_freeze_hit.mp3 |
| Wind Arrows | 491128 | release: act_gustwindarrow_f_hit_point_i.ros<br>impact: act_gustwindarrow_t_hit_point_e.ros<br>impact: act_icearrow_t_hit_point.ros | release: act_gustwindarrow_f_hit_point_i.ros.mp3<br>impact: act_gustwindarrow_t_hit_point_e.mp3<br>impact: act_gustwindarrow_f_hit_point_e.mp3<br>impact: act_icearrow_t_hit_point.mp3 |
| Mana Drain Shot | 490441 | release: act_sorcerersting_f.ros | release: act_sorcerersting_f.mp3 |
| Lasso | 490454 | cast-start: act_casting_c_hand.ros | cast-start: act_casting_c_hand.wav |
| Piercing Arrow | 490400 | release: act_immolatingshot_f.ros<br>impact: act_immolatingshot_t_hit_point.ros | release: act_immolatingshot_f.mp3<br>impact: act_immolatingshot_t_hit_point.mp3 |
| Snipe | 490450 | release: act_snipe_f.ros<br>impact: act_snipe_t_freeze_hit.ros | release: act_snipe_f.mp3<br>impact: act_snipe_t_freeze_hit.mp3 |
| Detection | 490463 | cast-start: act_focus_c_top.ros<br>cast-start: act_focus_c_down.ros | cast-start: act_focus_c_top.mp3 |
| Combo Shot | 490424 | release: act_arrow_f.ros<br>impact: act_arrow_t_hit_point.ros | release: act_arrow_f.mp3<br>impact: act_arrow_t_hit_point.mp3 |
| Neck Strike | 490438 | impact: act_neckattack_t_mouth.ros | impact: act_neckattack_t_mouth.mp3 |
| Reflected Shot | 490457 | release: act_deflecting_shot_f.ros<br>impact: act_deflecting_shot_t_freeze_hit.ros | release: act_deflecting_shot_f.mp3<br>impact: act_deflecting_shot_t_freeze_hit.mp3 |
| Concentration | 490460 | impact: act_concentration_t_top.ros | impact: act_concentration_t_top.mp3 |
| Target Area | 490464 | cast-start: act_physical_casting_c_hand.ros<br>cast-start: act_shooterexpert_c_down.ros | cast-start: act_physical_casting_c_hand.mp3<br>cast-start: act_shooterexpert_c_down.mp3 |
| Frost Arrow | 491163 | release: act_icearrow_f.ros<br>impact: act_icearrow_t_hit_point.ros | release: act_icearrow_f.mp3<br>impact: act_icearrow_t_hit_point.mp3 |
| Autoshot | 492589 | release: act_normalarrow_f_i.ros<br>impact: act_arrow_t_hit_point.ros | impact: act_arrow_t_hit_point.mp3 |
| Shot | 490423 | release: act_arrow_f.ros<br>impact: act_arrow_t_hit_point.ros | release: act_arrow_f.mp3<br>impact: act_arrow_t_hit_point.mp3 |
| Vampire Arrows | 491292 | release: act_bitesbloodarrow_f_hit_point_i.ros<br>impact: act_bitesbloodarrow_t_hit_point_e.ros<br>impact: act_bloodstain_t_freeze_down.ros | impact: act_bitesbloodarrow_t_hit_point_e.mp3<br>impact: act_bloodstain_t_freeze_down.mp3 |
| Joint Blow | 490420 | impact: act_jointattack_t_freeze_hit.ros | impact: act_jointattack_t_freeze_hit.mp3 |
| Blood Arrow | 490434 | cast-start: act_fear_c_hand.ros | — |
| Throat Attack | 490428 | impact: act_throatattack_t_mouth.ros | impact: act_throatattack_t_mouth.mp3 |
| Wrist Attack | 490451 | impact: act_handoff_t_freeze_hit_e.ros | impact: act_handoff_t_freeze_hit_e.mp3 |
## Scout — accepted elite pair
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Weak Spot | 491493 | release: act_bitesbloodarrow_f_hit_point_i.ros<br>impact: act_lockbuff_t_top.ros | impact: act_lockbuff_t_top.mp3 |
| Blinding Powder | 491336 | impact: act_blindpowder_t_top_e.ros | impact: act_blindpowder_t_top_e.mp3 |
| Sapping Arrow | 491496 | release: act_weakarrow_f_i.ros<br>impact: act_weakarrow_t_freeze_hit_e.ros | impact: act_weakarrow_t_freeze_hit_e.mp3 |
| Deadly Poison Bite | 492621 | cast-start: act_poison_casting_c_hand.ros<br>release: act_deadlypoison_f_i.ros<br>impact: act_deadlypoison_t_hit_point_e.ros<br>impact: act_bitesbloodarrow_t_hit_point_e.ros | impact: skill_deadlypoison_t_hit_point_e.mp3<br>impact: act_bitesbloodarrow_t_hit_point_e.mp3 |
## Rogue — base
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Assassins Rage | 490506 | cast-start: act_absolute_strike_c_top.ros | cast-start: act_absolute_strike_c_top.mp3 |
| Low Blow | 490323 | impact: act_crow01_t_hit_point.ros | impact: act_crow01_t_hit_point.mp3 |
| Wound Attack | 490313 | impact: act_scotch_t_freeze_hit.ros | impact: act_scotch_t_freeze_hit.mp3 |
| Blind Spot | 490311 | impact: act_fracture01_t_freeze_hit.ros | impact: act_fracture01_t_freeze_hit.mp3 |
| Numbing Dagger | 490347 | cast-start: nor_dark_casting.ros<br>release: act_throw01_f.ros | release: act_throw.mp3 |
| Poison | 490329 | cast-start: act_apply_poison_c_hand.ros | cast-start: act_apply_poison_c_hand.mp3 |
| Hide | 490312 | cast-start: act_snug_c_freeze_down.ros | cast-start: act_snug_c_freeze_down.mp3 |
| Premeditation | 490507 | cast-start: act_premeditation_c_hand.ros | cast-start: act_premeditation_c_hand.mp3 |
| Sneak Attack | 490337 | impact: act_sneak_attack_t_freeze_hit.ros | impact: act_scotch_t_freeze_hit.mp3 |
| Vanish | 490354 | cast-start: act_hiding_c_freeze_down.ros | cast-start: act_hiding_c_freeze_down.mp3 |
| Sprint | 490341 | cast-start: act_speedup01_c_down.ros | cast-start: act_speedup01_c_down.mp3 |
| Evasion | 490355 | cast-start: act_illusionmoving_c_down.ros | cast-start: act_illusionmoving_c_down.mp3 |
| Combo Throw | 490338 | release: act_throw01_f.ros<br>impact: act_throw01_t_hit_point.ros | release: act_throw.mp3<br>impact: act_throw01_t_hit_point.mp3 |
| Shadow Prison | 490346 | cast-start: act_dark_hand_wave_c_hand_e.ros | — |
| Informer | 490350 | impact: nor_hitrateup_t_down_e.ros | — |
| Fervent Attack | 490333 | cast-start: act_attackspeedup_c_down.ros | cast-start: act_attackspeedup_c_down.mp3 |
| Shadowstab | 490306 | impact: act_serpentine_t_freeze_hit.ros | impact: act_serpentine_t_freeze_hit.mp3 |
| Throw | 490309 | release: act_throw01_f.ros<br>impact: act_throw01_t_hit_point.ros | release: act_throw.mp3<br>impact: act_throw01_t_hit_point.mp3 |
| Blind Stab | 490317 | impact: act_blind01_t_freeze_hit.ros | impact: act_blind01_t_freeze_hit.mp3 |
| Shadow Step | 490344 | cast-start: act_shadowmoving_c_down.ros<br>cast-start: act_disaster_appear_c_down_e.ros | cast-start: act_shadowmoving_c_down.mp3<br>cast-start: act_disaster_appear_c_down_e.mp3 |
## Rogue — accepted elite pair
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Death's Touch | 491339 | cast-start: act_dark_hand_wave_c_hand_e.ros<br>impact: act_deathtouch_t_down_e.ros<br>impact: act_serpentine_t_freeze_hit.ros | impact: act_deathtouch_t_down_e.mp3<br>impact: act_serpentine_t_freeze_hit.mp3 |
| Poisonous Infection | 492625 | impact: act_poisondot_t_down.ros | impact: act_poisondot_t_down.mp3 |
| Poison Spray | 492920 | cast-start: act_earth_hand_wave_c_hand_e.ros<br>impact: act_vriusinfection_t_freeze_down_e.ros | cast-start: act_earth_hand_wave_c_hand_e.mp3<br>impact: skill_vriusinfection_t_freeze_down_e.mp3 |
| Decay | 493017 | impact: act_rot_t_down_e.ros | impact: skill_rot_t_down_e.mp3 |
## Mage — base
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Elemental Catalysis | 490238, 497971 | cast-start: act_casting_c_hand.ros<br>cast-start: act_castup01_c_down.ros | cast-start: act_casting_c_hand.mp3<br>cast-start: act_castup01_c_down.mp3 |
| Flame | 491150 | cast-start: act_fireball01_c_hand_i.ros<br>cast-start: act_fireball02_c_hand_e.ros<br>release: act_fireball01_f_i.ros<br>impact: act_fireball01_t_freeze_hit_e.ros | cast-start: act_vengeance_c_hand.mp3<br>cast-start: act_fireball02_c_hand_e.ros.mp3<br>release: act_fireball02_f.mp3<br>impact: act_fireball01_t_freeze_hit_e.ros.mp3 |
| Electrostatic Charge | 491156 | cast-start: act_casting_c_hand.ros<br>cast-start: act_electricfield_c_top.ros<br>aura-start: act_electricfieldbuff_c_down_i.ros | cast-start: act_casting_c_hand.mp3<br>cast-start: act_electricfield_c_top.mp3<br>aura-start: act_electricfieldbuff2.mp3<br>aura-start: act_electricfieldbuff_c_down_i.ros.mp3 |
| Discharge | 490242 | cast-start: act_wind_hand_wave_c_hand_e.ros<br>cast-start: act_lightningstrike_c_shoot.ros<br>cast-start: act_deep_thought_c_down_e.ros | cast-start: act_lightningstrike_c_shoot.mp3 |
| Meteor Shower | 490252 | cast-start: act_noncasting_c_hand.ros<br>impact: act_shottingstars_t_down.ros | impact: act_shottingstars_t_down.mp3 |
| Phoenix | 490239 | cast-start: act_vengeance_c_hand.ros<br>cast-start: act_phoenix_c_freeze_down.ros<br>cast-start: act_deep_thought_c_down_e.ros<br>impact: act_firedot_t_down.ros | cast-start: act_vengeance_c_hand.mp3<br>cast-start: act_phoenix_c_freeze_down.ros.mp3<br>impact: act_firedot_t_down.mp3 |
| Purgatory Fire | 491153 | cast-start: act_fireball02_c_hand_e.ros<br>impact: act_fireball01_t_freeze_hit_e.ros<br>impact: act_firedot_t_down.ros | cast-start: act_fireball02_c_hand_e.ros.mp3<br>impact: act_fireball01_t_freeze_hit_e.ros.mp3<br>impact: act_firedot_t_down.mp3 |
| Energy Influx | 490218 | aura-start: act_magebuff01_c_top.ros | aura-start: act_magebuff01_c_top.ros.mp3 |
| Energy Well | 490251 | impact: act_manareduce01_c_hit_point.ros | impact: act_manareduce01_c_hit_point.mp3 |
| Elemental Weakness | 490247, 497977 | cast-start: act_casting_c_hand.ros<br>impact: act_elementdecay_t_down.ros | cast-start: act_dark_casting_c_hand.wav<br>impact: act_elementdecay_t_down.mp3 |
| Thunderstorm | 490244 | cast-start: act_casting_c_hand.ros<br>cast-start: act_torodo_c_freeze_down.ros<br>impact: act_rushlevel_fail_c_hit.ros | cast-start: act_casting_c_hand.mp3<br>cast-start: act_torodo_c_freeze_down.ros.mp3<br>impact: act_rushlevel_fail_c_hit.ros.mp3 |
| Plasma Arrow | 491169 | cast-start: act_electric_casting_c_hand_i.ros<br>release: act_electric_arrow_f_i.ros<br>impact: act_electric_explode_t_freeze_hit_e.ros | cast-start: act_electric_casting_c_hand_i_db.mp3<br>cast-start: act_electric_casting_c_hand_ifadein.mp3<br>release: act_electric_arrow_f_i.ros.mp3<br>impact: act_electric_explode_t_freeze_hit_e.ros.mp3 |
| Electric Bolt | 491168 | cast-start: act_electric_casting_c_hand_i.ros<br>release: act_electric_ball_f_i.ros<br>impact: act_electric_ball_t_freeze_hit_e.ros<br>impact: act_electric_dot_t_down_e.ros | cast-start: act_electric_casting_c_hand_i_db.mp3<br>cast-start: act_electric_casting_c_hand_ifadein.mp3<br>impact: act_electric_dot_t_down_e.ros.mp3 |
| Electric Explosion | 491170 | cast-start: act_electric_casting_c_hand_i.ros<br>impact: act_electric_explode_f_i.ros<br>impact: act_electric_explode_t_freeze_hit_e.ros | cast-start: act_electric_casting_c_hand_i_db.mp3<br>cast-start: act_electric_casting_c_hand_ifadein.mp3<br>impact: act_electric_explode_f_i.ros.mp3<br>impact: act_electric_explode_t_freeze_hit_e.ros.mp3 |
| Electric Compression | 491171 | cast-start: act_electricnet_recharge_c_down_e.ros | cast-start: act_electricnet_recharge_c_down_e.ros.mp3 |
| Static Field | 491172 | cast-start: act_electric_casting_c_hand_i.ros<br>cast-start: act_electric_net_c_down_e.ros<br>impact: act_electric_net_t_down_i.ros | cast-start: act_electric_casting_c_hand_i_db.mp3<br>cast-start: act_electric_casting_c_hand_ifadein.mp3<br>cast-start: act_electric_net_c_down_e.ros.mp3<br>impact: act_electric_net_t_down_i.ros.mp3 |
| Fireball | 490204 | cast-start: act_fire_hand_wave_c_hand_e.ros<br>cast-start: act_deep_thought_c_down_e.ros<br>release: act_fireball02_f.ros<br>impact: act_fireball02_t_hit_point.ros | cast-start: act_fire_hand_wave_c_hand_e.ros.mp3<br>release: act_fireball02_f.mp3<br>impact: act_fireball02_t_hit_point.mp3 |
| Lightning | 490212 | cast-start: act_wind_hand_wave_c_hand_e.ros<br>cast-start: act_deep_thought_c_down_e.ros<br>impact: act_summonlighting_t_hit_point.ros | impact: act_summonlighting_t_hit_point.mp3 |
| Intensification | 490225, 497976 | — | — |
| Silence | 490296 | cast-start: act_casting_c_hand.ros<br>cast-start: act_dark_hand_wave_c_hand_e.ros | cast-start: act_dark_casting_c_hand.wav |
| Fire Ward | 490248 | cast-start: act_vengeance_c_hand.ros<br>cast-start: act_fireresistance_c_top.ros | cast-start: act_vengeance_c_hand.mp3<br>cast-start: act_fireresistance_c_top.mp3 |
## Mage — accepted elite pair
| Skill | Source spell IDs | Effect phase and source model | Sound phase and source cue |
| --- | --- | --- | --- |
| Static Resonance | 498744 | cast-start: act_electricity-field_c_down_e.ros | cast-start: act_electricity-field_c_down_e.mp3 |
| Breath Erase | 498750 | cast-start: act_hide_c_down_e.ros | cast-start: act_hide_c_down_e.mp3 |
| Soul Stepping | 498757 | cast-start: act_soul_walker_c_down_e.ros | cast-start: act_soul_walker_c_down_e.mp3 |
| Deep Inspiration | 498758 | cast-start: act_deep_open_c_down_e.ros | cast-start: act_deep_open_c_down_e.mp3 |
| Fire Lightning Burst | 498759 | cast-start: act_fireball01_c_hand_i.ros<br>cast-start: act_electric_casting_c_hand_i.ros<br>release: act_fire-thunder_f.ros<br>impact: act_fire-thunder_t_hitpoint_e.ros | cast-start: act_vengeance_c_hand.mp3<br>cast-start: act_electric_casting_c_hand_i_db.mp3<br>cast-start: act_electric_casting_c_hand_ifadein.mp3<br>release: act_fire-thunder_f02.wav<br>release: act_fire-thunder_f01.mp3<br>impact: act_fire-thunder_t_hitpoint_e.mp3 |
## Decoded particle archive
The particle archive covers 170 referenced ROS models, with 9 fully decoded models and 161 partial models. 74 models are renderer-ready and 1 has a browser-playable partial original. The Priest holy cast's remaining EventTrigger is an audio callback; Urgent Heal's target renders its source sprites, emitted rune mesh, and animated ground decal. Its terrain-triangle decal projection remains approximated by a level plane. The archive exports 668 sprite controllers and 139 distinct PNG textures. See `public/assets/game/combat-effects/original/runewaker/manifest.json` for per-model details.
## Next conversion step
The WebGL sprite renderer plays Priest Urgent Heal, Heal, and Group Heal casting; Urgent Heal's target impact with its source rune mesh and decal; Rising Tide's cast, projectile release, and target impact; Knight Charge cast and Whirlwind Shield impact; and Mage Electric Bolt projectile. Urgent Heal's holy cast EventTrigger starts a looping 3D WAV, and its target impact triggers the source benedictory heal MP3. Rising Tide retains its packaged cast WAV and now plays its ROS-linked projectile release and target impact MP3s. Impact cues use the target position. Urgent Heal's decal uses a level plane in place of the client's terrain projection. The Direct3D source and packaged ROS data remain the reference for subsequent phases.
+15 -9
View File
@@ -1,12 +1,12 @@
# RoM stat weights and gearing audit
Status: **source model identified; runtime conversion and equipment import pending**. This audit covers the Priest/Knight healer, Knight/Scout tank, Scout/Rogue DPS, Rogue/Warrior DPS, and Mage/Warlock DPS. It does not assign balance weights to unimplemented RoM gear.
Status: **WoW-stat hybrid chosen for gameplay; native source model retained as reference**. This audit covers the Priest/Knight healer, Knight/Scout tank, Scout/Rogue DPS, Rogue/Warrior DPS, and Mage/Warlock DPS. The game keeps RoM abilities and class pairings. The five accepted builds now use WoW-equivalent base curves, hybrid heirlooms, and WoW-derived combat stats. See `docs/ROM_HEIRLOOM_REFERENCE.md` for the client-derived blue-item atlas and native-stat curves; those numbers are reference points and are not applied directly to these five builds.
## Current runtime
- `src/game/romStats.ts` reads primary-class level growth from `romPlayerCatalog.generated.json` and gives the secondary class 10% of its base stats **at the same level as the primary**. This shared level is the game's progression rule, even though the historical source allowed separate class levels. The runtime also omits source race and sex additions.
- `src/game/wotlkStats.ts` maps RoM Wisdom into WoW Spirit, then derives HP, Mana, attack power, spell power, avoidance, and ratings with WoW rules. This is a compatibility adapter, not RoM stat conversion. In particular, the global Mana multiplier, Stamina-to-HP rule, and `0.35 * Intellect + 0.15 * Spirit` spell-power rule are not source RoM formulas.
- `src/game/specializationCatalog.ts` selects one of the WoW gear archetype weight tables for RoM. `src/game/heirloomEquipment.ts` uses WoW budgets and compares against WoW boss gear to create RoM heirlooms. The Scout currently receives a two-hand mace alongside a bow, although source Scout equipment allows one-hand swords, daggers, bows, and crossbows. `src/game/equipment.ts` also lacks the source Priest shield permission.
- `src/game/wotlkStats.ts` gives the five accepted primaries their corresponding Wrath base attribute, HP, and Mana curves. At level 10 the secondary contributes 10% of its WoW-equivalent curve **at the primary's level**. Human curves cover Priest, Knight, Rogue, and Mage; Scout uses Night Elf. Durnik remains a RoM dwarf, but Human Mage is the base-curve fallback because Wrath 3.3.5 has no Dwarf Mage row. Other RoM classes still use `src/game/romStats.ts` native growth. The shared secondary level is the game's progression rule, even though the historical source allowed separate class levels.
- `src/game/wotlkStats.ts` maps native Wisdom into WoW Spirit only on the remaining native-growth path, then derives HP, Mana, attack power, spell power, avoidance, and ratings with WoW rules. This is the selected compatibility adapter, not a historical RoM conversion. In particular, the global Mana multiplier and Stamina-to-HP rule are not source RoM formulas.
- `src/game/specializationCatalog.ts` defines distinct hybrid gear profiles for the five builds. `src/game/heirloomEquipment.ts` gives these five WoW-stat heirlooms at levels 1–79 from a level budget without matching WoW boss drops. The equipment model has independent main-hand, off-hand, and ranged slots. Knight/Scout gains a bow at level 10 alongside mace/shield; Scout/Rogue gains an off-hand dagger at 16 alongside dagger/bow; Rogue/Warrior gains a projectile at 12 and an off-hand dagger at 16. Priest/Knight uses mace/shield and Mage/Warlock uses a staff; their ranged slots remain empty. The five hybrid loadouts omit the WoW wrist slot; `src/game/equipment.ts` permits Priest shields. These are active gameplay gear, while the native source curves remain references. Other RoM classes retain their existing fallback gear path until separately validated.
- `src/game/runewakerLootCatalog.ts` keeps imported RoM drops as non-equippable `misc` items with zero stats. The loot importer preserves names and probabilities but does not decode native equipment slots, base stats, or enhancements. `src/game/lootPolicy.ts` suppresses **all** boss item drops during leveling; boss cards are awarded through a separate path. At level 80, imported RoM boss items still lack usable equipment data.
## Source stat model
@@ -38,18 +38,24 @@ The table below shows **vocation terms only** from the player records of `voctab
Priest healing cannot be scored by MATK alone: the source healing path also uses magic-power rate and heal-point inputs. Critical chance, weapon damage, resistance, and encounter mix further change the value of a stat. These conversion coefficients are mechanics, not final DPS/HPS weights.
## Equipment model to implement
## Hybrid gameplay boundary
The five-class equipment path deliberately translates RoM roles into WoW combat inputs: Strength and Agility feed physical attack, Intelligence and Wisdom (as Spirit) feed spell and resource stats, defense/avoidance use armor and WoW ratings, and healing uses spell power and Mana sustain. The gear profile weights and heirloom budgets are design choices, not a claim that RoM native stats have the same numerical scale as WoW stats. The source item atlas can guide level progression and class identity without copying DEF, MDEF, MATK, Heal Point, or weapon damage into WoW fields one-for-one.
This reduces equipment implementation work but still requires targeted balance validation. The earlier three-seed Shadoj diagnostic with the paired cohort and active Priest defeated the boss at levels 20, 60, and 79; Mage damage remained well below Scout/Rogue, and Priest died in each level-79 run. After switching the base curves, a one-seed Shadoj screen at each of levels 20, 60, and 79 defeated the boss with zero deaths. At level 60, Mage dealt about 185 DPS versus Scout 971 and Rogue 1,026; Mage remains a large outlier. The current reports are under `artifacts/balance-lab/rom-hybrid-wowbase-l{20,60,79}-shadoj`. These are screening runs against one encounter, not balance certification. RoM remains Needs Testing; `docs/ROM_FIVE_VALIDATION.md` records the elite-skill checks and unresolved source gaps.
## Optional native-fidelity path
RoM equipment has base item properties plus additional attribute sources. [Gameforge's class guide](https://archive.runesofmagic.gameforge.com/en/classes.html) lists native class weapon/armor permissions. Its [rune guide](https://archive.runesofmagic.gameforge.com/us/runes.html) describes socketed runes and up to four rune slots; its [Arcane Transmutor guide](https://archive.runesofmagic.gameforge.com/en/arcanetransmutor.html) describes transferring attributes through Fusion and Mana Stones. These changes must be represented as item-instance bonuses rather than a WoW item-level budget. Source `RoleAttr.cpp` already distinguishes flat equipment contributions, attribute contributions, and multiplicative rates.
1. Decode `armorobject.db` and `weaponobject.db` into native slot, armor/weapon type, base defense/damage, attribute, and level fields. Preserve source object IDs and validate the binary layout against source structures and known items before allowing package equipment to equip.
1. Use `scripts/runewaker-pipeline/export-rom-heirloom-reference.mjs` and `src/game/generated/romHeirloomReference.json` as decoded references for native slot, armor/weapon type, base effects, deterministic fixed attributes, and required level. Preserve item IDs and investigate the 727 records with unresolved attribute IDs and any special effects before importing historical source items. The `src/game/romHeirloomReference.ts` lookup is design data and is not read by combat equipment.
2. Add a RoM item-instance bonus layer for native flat stats, transferred attributes, rune sockets, and later enhancement/tier data. Sum those bonuses into a native stat block. Keep WoW `ItemStats` and heirloom scoring unchanged for WoW characters.
3. Extend the generated class data with race and sex arguments and the vocation conversion coefficients. Keep secondary growth tied to the primary's level, including after the level-10 secondary unlock. Convert the complete RoM stat block to combat inputs through a RoM-specific adapter, including ATK, MATK, DEF, MDEF, and separate healing inputs.
4. Use legal starter equipment as a temporary bridge, then source-backed quest or item-shop equipment packages for leveling. The client snapshot confirms four armor-choice quest reward families at levels 50, 55, 60, and 65; see `docs/ROM_LEVELING_GEAR_PACKAGES.md`. Resolve the package expansion data and missing earlier/later tiers before granting source items. Dungeon bosses award cards during leveling, not equipment or miscellaneous items. Do not tune a RoM heirloom to a WoW drop score or apply WoW rating conversions to it.
4. If granting historical quest or item-shop packages later, resolve exact package expansion and translate their properties into the chosen WoW-stat budget or add a separate native item path. The client snapshot confirms armor-choice quest families at levels 5, 30, 50, 55, 60, and 65; see `docs/ROM_LEVELING_GEAR_PACKAGES.md`. Dungeon bosses award cards during leveling, not equipment or miscellaneous items. Do not copy native item numbers directly into WoW ratings.
5. Derive role-specific *upgrade scores* from the marginal combat effect of each native stat at the current level and gear state. Measure Knight survival/threat, Priest effective HPS and sustain, and each DPS build's damage on the same targeted encounter set. Source coefficients seed that model; targeted combat measurements establish practical weights and catch caps or nonlinear effects.
## Online and validation boundary
`server/database.mjs` rejects a RoM/WoW source mixture when a grouped player selects a character and checks again when an activity starts. Until RoM AI companions are separately ready, online RoM groups launch players-only; the WoW-only randomized Bite-Sized Stockades activity is unavailable to them. This does not change local manual RoM party diagnostics. The five RoM profiles remain **Needs Testing** and elite skills remain deferred.
`server/database.mjs` rejects a RoM/WoW source mixture when a grouped player selects a character and checks again when an activity starts. Until RoM AI companions are separately ready, online RoM groups launch players-only; the WoW-only randomized Bite-Sized Stockades activity is unavailable to them. This does not change local manual RoM party diagnostics. The five RoM profiles remain **Needs Testing** while live NPC-cast coverage, equipment compatibility, and the Mage damage outlier are investigated. The identified level-60/70 source replacement skills now execute through their parent abilities, and Priest/Knight Devotion Halo is a separate playable skill.
Validate each import with source-record fixture tests, legal slot/equip tests for all five builds, direct native-stat conversion cases, then `npm run test:rom-five` and targeted level/encounter diagnostics. Compare package upgrades by native stat deltas and combat outcomes before changing dungeon tuning. Keep a common encounter definition with source-specific stat translation and measured balance coefficients; no duplicate WoW/RoM dungeon catalogs are required by this audit.
Validate the hybrid with legal slot/equip tests for all five builds, stat growth across 1–79, `npm run test:rom-five`, and targeted level/encounter diagnostics. Check Knight threat and survival, Priest effective healing and sustain, and the three DPS roles separately before promotion. If package rewards are added, compare their translated upgrades and combat outcomes. A common encounter definition can use source-specific tuning coefficients; no duplicate WoW/RoM dungeon catalogs are required by this audit.
+39
View File
@@ -0,0 +1,39 @@
# RuneWaker dungeon maps
The RuneWaker minimap shows a cropped dungeon map centered on the live player
marker. Press **M** or click the minimap to open the full map. The player marker
and heading, entrance, and numbered encounters use the same coordinate projection
in both views. The full map closes with **M**. The existing map keybind and minimap
rotation preference apply to RuneWaker dungeons too.
All 30 imported RuneWaker dungeon definitions have maps. Fifteen use calibrated
minimap tiles from the extracted RuneWaker client. Fifteen use floorplans rendered
from each dungeon's imported navigation mesh because matching client tiles are
missing or cannot be aligned confidently. The floorplans still place live markers
and encounters at their runtime coordinates. The UI labels a floorplan as such.
## Reimporting
Run `python scripts/runewaker-pipeline/import-maps.py` from the HealerMan root with
Pillow and NumPy available. `RUNEWAKER_ROOT` can point to the extracted RuneWaker
client; the default is `../Runewaker`. The importer reads DDS tiles under
`Resource/texture/minimap`, the primary WDB placement in each dungeon's
`import-metadata.json`, and the navigation GLB. It measures alignment between the
tile artwork and navigation geometry, accepts client artwork at confidence 0.65
or higher, and creates a navigation floorplan otherwise.
The generated PNGs live at `public/assets/game/dungeons/<id>/maps/runewaker-map.png`.
The runtime projection and source checksums live in
`src/game/generated/runewakerDungeonMaps.generated.json`. The map loader rejects
catalog entries when the dungeon map ID, coordinate transform, or visual checksum
no longer matches the runtime definition. Reimport after changing those inputs.
Run `npm run content:publish` after reimporting so downloadable dungeon packs
contain their map PNGs, then `npm run build` to refresh the bundled app.
## Verification
Run `node node_modules/vitest/vitest.mjs run src/game/dungeonArtworkMap.test.ts src/ui/DungeonMapGraphic.test.ts src/ui/MapPanel.test.ts`.
`node scripts/playtest-runewaker-maps.cjs` checks Forsaken Abbey in a browser,
including its downloaded artwork, minimap, M-key full map, and marker projection.
Screenshots are written to `artifacts/playtest/runewaker-maps/`.
+110
View File
@@ -0,0 +1,110 @@
# WoW bite-sized dungeon slice audit
## Scope and decision rule
This is a first-pass design audit of the 51 standard WoW dungeons already imported by Healer Man (Classic through Wrath of the Lich King). The map atlas and campaign catalog are the project source of truth for the layouts and encounter names. Three additional imported definitions are Healer Man / Ascension custom dungeons, so they are not part of this WoW pass.
A good slice should feel like a complete short outing: a clear reason to enter, one strong visual space or route, a handful of combat beats, and one satisfying final encounter. The closest existing model is **Stormwind Stockade: Cellblock Rush**: keep the original environment, retain one route, wall off the side wings, and finish on one seeded boss. Some source dungeons have a scripted event that makes an even better slice, but that event also raises implementation cost.
Ratings:
- **A — prototype first:** a distinctive, self-contained route or set piece with a natural finish.
- **B — promising:** a useful slice exists, but topology, gates, optional routes, or event handling need more design.
- **C — keep whole / no clean cut:** the full dungeon is already compact or event-led, or a cut would lose what makes the instance work.
The three first-pass candidates below are registered as derived Bite-Sized dungeons. After the first play report, the Wailing escort was replaced with Verdan’s room fight, the Deadmines entry was moved onto Mr. Smite’s source spawn, and Skadi’s hidden start interaction was replaced with a guard clear. The Skadi entrance and guards now occupy the connected source wave corridor. Companion navigation, wipes, completion, and results still need an in-game pass.
| Prototype | Implemented slice | Open playtest question |
| --- | --- | --- |
| Wailing Caverns — Verdan’s Lair | Starts inside Verdan’s chamber at a navigation-surface point. Three source cave mobs spawn in the room; defeating all three activates Verdan. Naralex and Mutanus remain only in the full dungeon. | Confirm the room population, Verdan activation, party targeting, and completion in play. |
| The Deadmines — Take the Ship | Starts at Mr. Smite’s source floor position, clears the ship population, gates VanCleef behind Captain Greenskin, then ends in the cabin. | Confirm the new start is grounded and the ship route remains walkable. |
| Utgarde Pinnacle — Skadi’s Gauntlet | Starts on the grounded source wave corridor. Defeating three first-wave guards along that corridor activates Skadi at its far end. The long waves and harpoons are omitted from this short run. | Confirm Skadi appears after the guard clear and the fight completes without an interaction. |
Each derived ID resolves to its source dungeon’s content pack and map asset directory through `dungeonContentSourceId`; the original full-length dungeon remains unchanged. The new entries are labeled Bite-Sized and beta until those playtests are recorded.
## Best first prototypes
| Rank | Dungeon slice | Why it fits | Main implementation question |
| --- | --- | --- | --- |
| 1 | **Wailing Caverns — Verdan’s Lair:** clear the chamber mobs, then fight Verdan the Everliving. | One navigable cave room and a clear boss reveal keep the run short. The Naralex escort remains a future, more complex candidate. | Verify the room floor, combat spacing, and boss activation in play. [Dungeon overview](https://warcraft.wiki.gg/wiki/Wailing_Caverns) |
| 2 | **The Deadmines — Take the Ship:** enter at Mr. Smite’s deck position, clear the ship, fight Captain Greenskin, then finish Edwin VanCleef in the cabin. | One strong visual set piece, a straightforward forward route, and a natural boss finale. The optional Cookie detour can be left out. [Classic guide](https://warcraft.wiki.gg/wiki/Deadmines_%28Classic%29) |
| 3 | **Zul’Farrak — The Staircase:** start at the pyramid event, survive Sergeant Bly’s descending waves, and finish at Chief Ukorz’s chamber. | The stair defense is already a compact wave encounter with a clear spatial progression; it could be a bite-sized run by itself or a short prelude into a boss. | Choose whether Bly and his group are protected participants or replace the event with a controlled wave sequence; the event’s outcome opens the Ukorz route. [Instance map and event guide](https://ptgmedia.pearsoncmg.com/imprint_downloads/brady/wow/zulfarrak/zulfarrak_lr.pdf) |
| 4 | **The Blood Furnace — Broggok’s Breakout:** begin at the prison cells, release the waves, then push through the final corridor to Keli’dan. | A tight industrial route with a built-in escalation and a boss payoff. The map is compact and the three encounters already form a clean progression. [Dungeon overview](https://warcraft.wiki.gg/wiki/Blood_Furnace) |
| 5 | **Utgarde Pinnacle — Skadi’s Gauntlet:** defeat the guards at Eagle’s Eye, then fight Skadi on the approach. | The short implementation gives the route an immediate, visible boss payoff. A full moving-wave and harpoon event remains a separate candidate. [Encounter guide](https://www.icy-veins.com/wotlk-classic/utgarde-pinnacle-dungeon-guide) |
| 6 | **Pit of Saron — The Quarry Breakout:** free the enslaved workers, clear the ambushes and tunnel, then confront Tyrannus. | A linear outdoor quarry gives the route space to breathe, and the rescue objective gives a strong reason to advance. The original instance already frames the final boss around freeing the slaves. [Dungeon overview](https://warcraft.wiki.gg/wiki/Pit_of_Saron) |
| 7 | **The Shattered Halls — Kargath’s Gauntlet:** start around Warbringer O’Mrogg, push through the final gauntlet, and finish in Kargath’s arena. | A direct late-dungeon route with a clear arena payoff. It works best if the final hallway pressure is the feature, not just a boss rush. |
| 8 | **Shadow Labyrinth — The Screaming Hall:** begin around Grandmaster Vorpil’s chamber and advance along the final hall to Murmur. | The project map shows a long, distinct finale corridor, and the two-boss ending provides a natural short arc without needing to reproduce the whole labyrinth. [Dungeon overview](https://warcraft.wiki.gg/wiki/Shadow_Labyrinth) |
| 9 | **Halls of Lightning — Ionar to Loken:** use the storm overlook and the route into Loken’s sanctum as the final ascent. | Strong visual progression from open storm space to titan interior; Ionar already guards the entrance to Loken’s sanctum. [Ionar’s encounter context](https://warcraft.wiki.gg/wiki/Ionar) |
**Current first batch:** Verdan’s Lair, Take the Ship, and Skadi’s Gauntlet. These now use short room and route progressions with explicit boss activation.
## Full 51-dungeon scan
| Expansion | Dungeon | Best bite-sized slice to investigate | Rating |
| --- | --- | --- | :---: |
| Classic | Shadowfang Keep | Fenrus through Wolf Master Nandos and up to Arugal’s tower. | B |
| Classic | Stormwind Stockade | The existing central Cellblock Rush is already the reference design. | C |
| Classic | The Deadmines | Mr. Smite’s ship ramp through Greenskin to VanCleef’s cabin. | A |
| Classic | Wailing Caverns | Verdan’s chamber fight now; Naralex escort is deferred. | A |
| Classic | Razorfen Kraul | The deeper boar warrens and Charlga’s area; the open, branching route needs a firm start boundary. | B |
| Classic | Blackfathom Deeps | Kelris’s altar ritual and the opened route to Aku’mai. | B |
| Classic | Uldaman | The late titan-vault approach to Archaedas. | B |
| Classic | Gnomeregan | The Clean Zone and final approach to Thermaplugg. | B |
| Classic | Sunken Temple | The lower sanctum / Avatar of Hakkar ritual through Shade of Eranikus. | B |
| Classic | Razorfen Downs | The Spiral of Thorns and Amnennar’s crypt, with the optional Belnistrasz escort kept as a variant. | B |
| Classic | Scarlet Monastery | Use the Cathedral wing as a complete bite-sized outing; it is a wing-sized instance, not a clean internal cut. | B |
| Classic | Zul’Farrak | The staircase defense, optionally continuing to Ukorz. | A |
| Classic | Blackrock Spire | The Upper Spire final ascent from Rend’s arena toward Drakkisath. Strong setting, but multiple floors and gates. | B |
| Classic | Blackrock Depths | No first choice: the instance is a large connected maze of branches, keys, and optional bosses. A later pass could define one exact route. | C |
| Classic | Scholomance | The upper-school finale from the Barov wing to Ras Frostwhisper. | B |
| Classic | Stratholme | The undead-quarter approach and final Baron fight; keep the timed Gauntlet as the main pressure beat. | B |
| Classic | Maraudon | The inner cavern / waterfall route to Princess Theradras. | B |
| Classic | Ragefire Chasm | Keep the whole short instance; cutting it further would leave too little journey. | C |
| Classic | Dire Maul | Pick one wing only—especially the West Wing route through Immol’thar to Prince Tortheldrin. The combined map has too many cross-wing choices for a casual cut. | B |
| Burning Crusade | Hellfire Ramparts | Keep the full compact instance rather than extracting one corridor. | C |
| Burning Crusade | The Blood Furnace | Broggok’s prison event through Keli’dan. | A |
| Burning Crusade | The Shattered Halls | O’Mrogg through the final gauntlet into Kargath’s arena. | A |
| Burning Crusade | The Slave Pens | The lower tunnels from Rokmar to Quagmirran; very short, so include both encounters and the water route. | B |
| Burning Crusade | The Underbog | The late fungal/swamp route from Swamplord Musel’ek to the Black Stalker. | B |
| Burning Crusade | The Steamvault | The lower waterworks and final route to Kalithresh. | B |
| Burning Crusade | The Arcatraz | The last containment section and release sequence leading to Skyriss. | B |
| Burning Crusade | The Botanica | The final greenhouse route from Laj to Warp Splinter. | B |
| Burning Crusade | The Mechanar | The upper-platform route from Sepethrea to Pathaleon; elevators and crossing routes need to be kept legible. | B |
| Burning Crusade | Shadow Labyrinth | Vorpil through the Screaming Hall to Murmur. | A |
| Burning Crusade | Sethekk Halls | Keep the whole short two-boss instance. | C |
| Burning Crusade | Mana-Tombs | Keep the full three-boss route; no internal cut improves it much. | C |
| Burning Crusade | Auchenai Crypts | Keep the full compact two-boss run. | C |
| Burning Crusade | Magisters’ Terrace | Vexallus through the final Kael’thas chamber; a viable one-boss finale if the full instance is too long. | B |
| Burning Crusade | The Black Morass | Keep the full portal-defense event; splitting it would remove the core mechanic. | C |
| Burning Crusade | The Escape from Durnholde | Keep the full escort-and-escape story; it is already a self-contained event run. | C |
| Wrath | Utgarde Keep | Keep the full short three-boss route. | C |
| Wrath | Utgarde Pinnacle | Eagle’s Eye guard clear and Skadi; the full wave and harpoon event is deferred. | A |
| Wrath | The Nexus | A single mage/arcane wing could work, but the boss route crosses a hub; map a route before choosing a cut. | B |
| Wrath | The Oculus | No first choice: drake riding and multi-level flight are the dungeon’s identity and make a small walking route misleading. | C |
| Wrath | The Culling of Stratholme | Keep the whole compact timed story run, including its city push and final encounter. | C |
| Wrath | Halls of Stone | The Tribunal event into Sjonnir is a good short encounter episode; use the full instance if event staging is too costly. | B |
| Wrath | Drak’Tharon Keep | King Dred through the upper temple to Tharon’ja. | B |
| Wrath | Azjol-Nerub | Keep the full compact three-boss descent; the instance already reads as one short journey. | C |
| Wrath | Halls of Lightning | Ionar’s overlook into Loken’s sanctum. | A |
| Wrath | Gundrak | The late temple route from Moorabi toward Gal’darah; the connected ramps and branches need a deliberate route boundary. | B |
| Wrath | Violet Hold | Keep the full prison-defense event; the wave sequence is the dungeon. | C |
| Wrath | Ahn’kahet: The Old Kingdom | The last organic/Old God passage from Jedoga toward Herald Volazj; leave the optional Amanitar branch out. | B |
| Wrath | The Forge of Souls | Keep the full two-boss instance. | C |
| Wrath | Trial of the Champion | Keep the full arena event; its staged opponent sequence is already bite-sized. | C |
| Wrath | Pit of Saron | Slave rescue, quarry ambushes, tunnel, and Tyrannus. | A |
| Wrath | Halls of Reflection | Keep the full two-boss defense and escape sequence. | C |
## Design takeaways
- Favor a starting point at a real midpoint or event trigger, then seal or ignore side routes the way the Stockades variant does.
- One boss is enough when the route has a clear event or traversal payoff. Two bosses work when both are close together, as on the Deadmines ship.
- The best slice is not always the last two bosses. The Zul’Farrak stairs and Skadi’s gauntlet are strong because the route itself is the memorable content.
- Avoid slices whose identity depends on a complex escort, vehicle, multi-floor hub, or many interactable gates unless that event is the specific feature being built.
- Use the imported maps for candidate boundaries, then confirm start position, reachable path, enemy density, and encounter state in the running game before implementation.
## Project references
- [WoW dungeon maps and import scope](WOW_DUNGEON_MAPS.md)
- [Existing bite-sized Stockades definition](../src/game/dungeons/biteSizedStockades.ts)
- [Current WoW dungeon functionality gate](../dungeon-pipeline/WOW_DUNGEON_FUNCTIONALITY_REPORT.md)
- [Generated campaign catalog and encounter order](../src/game/generated/dungeonCampaignCatalog.compressed.ts)
+3 -2
View File
@@ -12,8 +12,9 @@ the selected floor are not clamped to a misleading marker at the map edge.
The current import includes 51 standard WoW dungeons and 107 unique map images. The
custom Tinkertech Showdown, Frozen Reach, and Forgotten Mine have no matching map
records in the supplied archives and retain their survey maps. RuneWaker maps are
unchanged. Missing images also fall back to the survey projection.
records in the supplied archives and retain their survey maps. Missing images also
fall back to the survey projection. RuneWaker dungeons use the same map controls;
see [RuneWaker dungeon maps](RUNEWAKER_DUNGEON_MAPS.md).
## Reimporting
Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

+121
View File
@@ -0,0 +1,121 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700">
<defs>
<linearGradient id="backdrop" x2="1" y2="1"><stop stop-color="#193628"/><stop offset="1" stop-color="#06100e"/></linearGradient>
<linearGradient id="panel" x2="1" y2="1"><stop stop-color="#27312a"/><stop offset="1" stop-color="#0c1714"/></linearGradient>
<radialGradient id="portrait"><stop stop-color="#435549"/><stop offset="1" stop-color="#111d1a"/></radialGradient>
<linearGradient id="scale" x2=".8" y2="1"><stop stop-color="#aac49b"/><stop offset=".45" stop-color="#4d8067"/><stop offset="1" stop-color="#1d4b42"/></linearGradient>
<linearGradient id="belly" x2="1" y2=".8"><stop stop-color="#eee5a5"/><stop offset="1" stop-color="#a7a474"/></linearGradient>
<linearGradient id="guardian-fill" x2=".8" y2="1"><stop stop-color="#aad36f"/><stop offset=".48" stop-color="#568d49"/><stop offset="1" stop-color="#274f37"/></linearGradient>
<filter id="shadow"><feDropShadow dx="0" dy="12" stdDeviation="17" flood-opacity=".7"/></filter>
<filter id="creature-shadow"><feDropShadow dx="0" dy="5" stdDeviation="4" flood-opacity=".7"/></filter>
<g id="serpent" filter="url(#creature-shadow)">
<path d="M24 92 C8 75 30 61 53 69 C84 79 89 55 69 45 C53 37 54 23 67 18" fill="none" stroke="#193f37" stroke-width="26" stroke-linecap="round"/>
<path d="M24 91 C12 77 36 65 55 73 C82 82 92 54 70 44 C56 36 57 23 68 18" fill="none" stroke="url(#scale)" stroke-width="19" stroke-linecap="round"/>
<path d="M27 84 C35 76 43 75 52 78 M67 73 C75 70 79 62 78 56 M67 41 C59 32 62 22 68 18" fill="none" stroke="url(#belly)" stroke-width="6" stroke-linecap="round" opacity=".85"/>
<path d="M62 25 L56 15 L64 18 L67 9 L74 17 L82 14 L79 25 Q78 35 70 36 Q61 35 62 25" fill="url(#scale)" stroke="#173d34" stroke-width="2"/>
<path d="M62 33 Q69 43 77 33 L73 48 L69 40 L64 48Z" fill="#e2cf9c"/>
<circle cx="65" cy="25" r="2.5" fill="#f8d378"/><circle cx="75" cy="25" r="2.5" fill="#f8d378"/>
</g>
<g id="guardian" filter="url(#creature-shadow)">
<path d="M40 92 L22 76 L17 47 L29 28 L43 22 L63 26 L77 43 L82 75 L65 91Z" fill="url(#guardian-fill)" stroke="#214c30" stroke-width="3"/>
<path d="M24 41 L11 20 L31 31 M72 40 L90 18 L75 50" fill="url(#guardian-fill)" stroke="#214c30" stroke-width="3"/>
<path d="M38 30 L40 15 L50 25 L60 14 L66 33 M27 58 L10 53 L25 72 M76 59 L91 54 L80 76" fill="#427d3d" stroke="#1b472b" stroke-width="3"/>
<path d="M40 43 Q50 51 61 43 L65 71 Q50 80 35 69Z" fill="#9cc372" opacity=".8"/>
<path d="M35 38 L45 42 L41 46 M65 38 L55 42 L59 46" fill="#eae99e"/>
<path d="M40 58 Q50 63 61 58" fill="none" stroke="#173e29" stroke-width="4"/>
</g>
<g id="mutant" filter="url(#creature-shadow)">
<path d="M30 90 L20 65 L30 37 L45 29 L58 31 L73 45 L80 69 L68 91Z" fill="url(#scale)" stroke="#214b3e" stroke-width="3"/>
<path d="M34 35 L21 19 L24 42 M62 36 L75 18 L75 46" fill="url(#scale)" stroke="#214b3e" stroke-width="3"/>
<path d="M21 61 L5 53 L18 80 M76 58 L92 48 L84 80" fill="none" stroke="#376b56" stroke-width="12" stroke-linecap="round"/>
<path d="M36 51 L46 54 M62 51 L55 54" stroke="#e7dc89" stroke-width="5" stroke-linecap="round"/>
<path d="M38 66 Q49 80 62 66 L58 75 L54 69 L50 79 L46 69 L42 75Z" fill="#eadfb3"/>
</g>
</defs>
<rect width="1200" height="700" fill="url(#backdrop)"/>
<path d="M0 660Q130 310 300 490T760 365T1200 470V700H0" fill="#2c5037" opacity=".24"/>
<rect x="58" y="34" width="1084" height="632" rx="16" fill="url(#panel)" stroke="#8d7d56" stroke-width="2" filter="url(#shadow)"/>
<text x="88" y="102" font-family="Georgia,serif" font-size="38" fill="#f3ead1">Wailing Caverns</text>
<text x="88" y="132" font-family="Arial,sans-serif" font-size="14" fill="#b8b5a7">6 bosses ahead · 2 collected</text>
<path d="M978 56V140" stroke="#88764f"/>
<text x="1050" y="78" text-anchor="middle" font-family="Arial,sans-serif" font-size="11" letter-spacing="2" fill="#b8b5a7">ENTERING IN</text>
<text x="1050" y="134" text-anchor="middle" font-family="Georgia,serif" font-size="60" fill="#d5b875">05</text>
<g transform="translate(88 166)">
<rect width="320" height="210" rx="9" fill="#151f1b" stroke="#69b4ff" stroke-width="2"/>
<rect x="1" y="1" width="318" height="28" rx="8" fill="#0a1410"/>
<text x="12" y="19" font-family="Arial,sans-serif" font-size="11" letter-spacing="2" fill="#efe8d7">BOSS 01</text>
<text x="307" y="19" text-anchor="end" font-family="Arial,sans-serif" font-size="10" fill="#69b4ff">COLLECTED</text>
<rect x="7" y="31" width="306" height="113" rx="3" fill="url(#portrait)"/>
<ellipse cx="160" cy="137" rx="53" ry="5" fill="#030a08" opacity=".7"/>
<use href="#serpent" transform="translate(105 30) scale(1.08)"/>
<text x="13" y="164" font-family="Georgia,serif" font-size="20" fill="#f3ead1">Lady Anacondra</text>
<text x="13" y="181" font-family="Arial,sans-serif" font-size="11" fill="#b8b5a7">3 copies owned</text>
<path d="M0 187H320" stroke="#536357" opacity=".6"/>
<g transform="translate(12 191)"><rect width="21" height="21" rx="3" fill="#2a3a3c" stroke="#ddd8ce"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#ddd8ce">N</text></g><g transform="translate(39 191)"><rect width="21" height="21" rx="3" fill="#2a3a3c" stroke="#69b4ff"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#69b4ff">R</text></g><g transform="translate(66 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">E</text></g><g transform="translate(93 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">L</text></g>
</g>
<g transform="translate(440 166)">
<rect width="320" height="210" rx="9" fill="#151f1b" stroke="#69756b" stroke-width="1"/>
<rect x="1" y="1" width="318" height="28" rx="8" fill="#0a1410"/>
<text x="12" y="19" font-family="Arial,sans-serif" font-size="11" letter-spacing="2" fill="#efe8d7">BOSS 02</text>
<text x="307" y="19" text-anchor="end" font-family="Arial,sans-serif" font-size="10" fill="#a5a99b">UNCOLLECTED</text>
<rect x="7" y="31" width="306" height="113" rx="3" fill="url(#portrait)"/>
<ellipse cx="160" cy="137" rx="53" ry="5" fill="#030a08" opacity=".7"/>
<use href="#serpent" transform="translate(105 30) scale(1.08)"/>
<text x="13" y="164" font-family="Georgia,serif" font-size="20" fill="#f3ead1">Lord Cobrahn</text>
<text x="13" y="181" font-family="Arial,sans-serif" font-size="11" fill="#b8b5a7">Card not collected</text>
<path d="M0 187H320" stroke="#536357" opacity=".6"/>
<g transform="translate(12 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">N</text></g><g transform="translate(39 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">R</text></g><g transform="translate(66 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">E</text></g><g transform="translate(93 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">L</text></g>
</g>
<g transform="translate(792 166)">
<rect width="320" height="210" rx="9" fill="#151f1b" stroke="#69756b" stroke-width="1"/>
<rect x="1" y="1" width="318" height="28" rx="8" fill="#0a1410"/>
<text x="12" y="19" font-family="Arial,sans-serif" font-size="11" letter-spacing="2" fill="#efe8d7">BOSS 03</text>
<text x="307" y="19" text-anchor="end" font-family="Arial,sans-serif" font-size="10" fill="#a5a99b">UNCOLLECTED</text>
<rect x="7" y="31" width="306" height="113" rx="3" fill="url(#portrait)"/>
<ellipse cx="160" cy="137" rx="53" ry="5" fill="#030a08" opacity=".7"/>
<use href="#serpent" transform="translate(105 30) scale(1.08)"/>
<text x="13" y="164" font-family="Georgia,serif" font-size="20" fill="#f3ead1">Lord Pythas</text>
<text x="13" y="181" font-family="Arial,sans-serif" font-size="11" fill="#b8b5a7">Card not collected</text>
<path d="M0 187H320" stroke="#536357" opacity=".6"/>
<g transform="translate(12 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">N</text></g><g transform="translate(39 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">R</text></g><g transform="translate(66 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">E</text></g><g transform="translate(93 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">L</text></g>
</g>
<g transform="translate(88 392)">
<rect width="320" height="210" rx="9" fill="#151f1b" stroke="#c493ff" stroke-width="2"/>
<rect x="1" y="1" width="318" height="28" rx="8" fill="#0a1410"/>
<text x="12" y="19" font-family="Arial,sans-serif" font-size="11" letter-spacing="2" fill="#efe8d7">BOSS 04</text>
<text x="307" y="19" text-anchor="end" font-family="Arial,sans-serif" font-size="10" fill="#c493ff">COLLECTED</text>
<rect x="7" y="31" width="306" height="113" rx="3" fill="url(#portrait)"/>
<ellipse cx="160" cy="137" rx="53" ry="5" fill="#030a08" opacity=".7"/>
<use href="#guardian" transform="translate(105 30) scale(1.08)"/>
<text x="13" y="164" font-family="Georgia,serif" font-size="18" fill="#f3ead1">Verdan the Everliving</text>
<text x="13" y="181" font-family="Arial,sans-serif" font-size="11" fill="#b8b5a7">5 copies owned</text>
<path d="M0 187H320" stroke="#536357" opacity=".6"/>
<g transform="translate(12 191)"><rect width="21" height="21" rx="3" fill="#2a3a3c" stroke="#ddd8ce"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#ddd8ce">N</text></g><g transform="translate(39 191)"><rect width="21" height="21" rx="3" fill="#2a3a3c" stroke="#69b4ff"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#69b4ff">R</text></g><g transform="translate(66 191)"><rect width="21" height="21" rx="3" fill="#2a3a3c" stroke="#c493ff"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#c493ff">E</text></g><g transform="translate(93 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">L</text></g>
</g>
<g transform="translate(440 392)">
<rect width="320" height="210" rx="9" fill="#151f1b" stroke="#69756b" stroke-width="1"/>
<rect x="1" y="1" width="318" height="28" rx="8" fill="#0a1410"/>
<text x="12" y="19" font-family="Arial,sans-serif" font-size="11" letter-spacing="2" fill="#efe8d7">BOSS 05</text>
<text x="307" y="19" text-anchor="end" font-family="Arial,sans-serif" font-size="10" fill="#a5a99b">UNCOLLECTED</text>
<rect x="7" y="31" width="306" height="113" rx="3" fill="url(#portrait)"/>
<ellipse cx="160" cy="137" rx="53" ry="5" fill="#030a08" opacity=".7"/>
<use href="#serpent" transform="translate(105 30) scale(1.08)"/>
<text x="13" y="164" font-family="Georgia,serif" font-size="20" fill="#f3ead1">Lord Serpentis</text>
<text x="13" y="181" font-family="Arial,sans-serif" font-size="11" fill="#b8b5a7">Card not collected</text>
<path d="M0 187H320" stroke="#536357" opacity=".6"/>
<g transform="translate(12 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">N</text></g><g transform="translate(39 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">R</text></g><g transform="translate(66 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">E</text></g><g transform="translate(93 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">L</text></g>
</g>
<g transform="translate(792 392)">
<rect width="320" height="210" rx="9" fill="#151f1b" stroke="#69756b" stroke-width="1"/>
<rect x="1" y="1" width="318" height="28" rx="8" fill="#0a1410"/>
<text x="12" y="19" font-family="Arial,sans-serif" font-size="11" letter-spacing="2" fill="#efe8d7">BOSS 06</text>
<text x="307" y="19" text-anchor="end" font-family="Arial,sans-serif" font-size="10" fill="#a5a99b">UNCOLLECTED</text>
<rect x="7" y="31" width="306" height="113" rx="3" fill="url(#portrait)"/>
<ellipse cx="160" cy="137" rx="53" ry="5" fill="#030a08" opacity=".7"/>
<use href="#mutant" transform="translate(105 30) scale(1.08)"/>
<text x="13" y="164" font-family="Georgia,serif" font-size="20" fill="#f3ead1">Mutanus the Devourer</text>
<text x="13" y="181" font-family="Arial,sans-serif" font-size="11" fill="#b8b5a7">Card not collected</text>
<path d="M0 187H320" stroke="#536357" opacity=".6"/>
<g transform="translate(12 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">N</text></g><g transform="translate(39 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">R</text></g><g transform="translate(66 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">E</text></g><g transform="translate(93 191)"><rect width="21" height="21" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="10.5" y="15" text-anchor="middle" font-family="Arial,sans-serif" font-size="12" fill="#79877d">L</text></g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

+125
View File
@@ -0,0 +1,125 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700">
<defs>
<linearGradient id="backdrop" x2="1" y2="1"><stop stop-color="#193628"/><stop offset="1" stop-color="#06100e"/></linearGradient>
<linearGradient id="panel" x2="1" y2="1"><stop stop-color="#27312a"/><stop offset="1" stop-color="#0c1714"/></linearGradient>
<radialGradient id="portrait"><stop stop-color="#435549"/><stop offset="1" stop-color="#111d1a"/></radialGradient>
<linearGradient id="scale" x2=".8" y2="1"><stop stop-color="#aac49b"/><stop offset=".45" stop-color="#4d8067"/><stop offset="1" stop-color="#1d4b42"/></linearGradient>
<linearGradient id="belly" x2="1" y2=".8"><stop stop-color="#eee5a5"/><stop offset="1" stop-color="#a7a474"/></linearGradient>
<linearGradient id="guardian-fill" x2=".8" y2="1"><stop stop-color="#aad36f"/><stop offset=".48" stop-color="#568d49"/><stop offset="1" stop-color="#274f37"/></linearGradient>
<filter id="shadow"><feDropShadow dx="0" dy="12" stdDeviation="17" flood-opacity=".7"/></filter>
<filter id="creature-shadow"><feDropShadow dx="0" dy="5" stdDeviation="4" flood-opacity=".7"/></filter>
<g id="serpent" filter="url(#creature-shadow)">
<path d="M24 92 C8 75 30 61 53 69 C84 79 89 55 69 45 C53 37 54 23 67 18" fill="none" stroke="#193f37" stroke-width="26" stroke-linecap="round"/>
<path d="M24 91 C12 77 36 65 55 73 C82 82 92 54 70 44 C56 36 57 23 68 18" fill="none" stroke="url(#scale)" stroke-width="19" stroke-linecap="round"/>
<path d="M27 84 C35 76 43 75 52 78 M67 73 C75 70 79 62 78 56 M67 41 C59 32 62 22 68 18" fill="none" stroke="url(#belly)" stroke-width="6" stroke-linecap="round" opacity=".85"/>
<path d="M62 25 L56 15 L64 18 L67 9 L74 17 L82 14 L79 25 Q78 35 70 36 Q61 35 62 25" fill="url(#scale)" stroke="#173d34" stroke-width="2"/>
<path d="M62 33 Q69 43 77 33 L73 48 L69 40 L64 48Z" fill="#e2cf9c"/>
<circle cx="65" cy="25" r="2.5" fill="#f8d378"/><circle cx="75" cy="25" r="2.5" fill="#f8d378"/>
</g>
<g id="guardian" filter="url(#creature-shadow)">
<path d="M40 92 L22 76 L17 47 L29 28 L43 22 L63 26 L77 43 L82 75 L65 91Z" fill="url(#guardian-fill)" stroke="#214c30" stroke-width="3"/>
<path d="M24 41 L11 20 L31 31 M72 40 L90 18 L75 50" fill="url(#guardian-fill)" stroke="#214c30" stroke-width="3"/>
<path d="M38 30 L40 15 L50 25 L60 14 L66 33 M27 58 L10 53 L25 72 M76 59 L91 54 L80 76" fill="#427d3d" stroke="#1b472b" stroke-width="3"/>
<path d="M40 43 Q50 51 61 43 L65 71 Q50 80 35 69Z" fill="#9cc372" opacity=".8"/>
<path d="M35 38 L45 42 L41 46 M65 38 L55 42 L59 46" fill="#eae99e"/>
<path d="M40 58 Q50 63 61 58" fill="none" stroke="#173e29" stroke-width="4"/>
</g>
<g id="mutant" filter="url(#creature-shadow)">
<path d="M30 90 L20 65 L30 37 L45 29 L58 31 L73 45 L80 69 L68 91Z" fill="url(#scale)" stroke="#214b3e" stroke-width="3"/>
<path d="M34 35 L21 19 L24 42 M62 36 L75 18 L75 46" fill="url(#scale)" stroke="#214b3e" stroke-width="3"/>
<path d="M21 61 L5 53 L18 80 M76 58 L92 48 L84 80" fill="none" stroke="#376b56" stroke-width="12" stroke-linecap="round"/>
<path d="M36 51 L46 54 M62 51 L55 54" stroke="#e7dc89" stroke-width="5" stroke-linecap="round"/>
<path d="M38 66 Q49 80 62 66 L58 75 L54 69 L50 79 L46 69 L42 75Z" fill="#eadfb3"/>
</g>
</defs>
<rect width="1200" height="700" fill="url(#backdrop)"/>
<path d="M0 680Q130 370 320 520T700 390T1200 540V700H0" fill="#275137" opacity=".32"/>
<rect width="1200" height="700" fill="#020705" opacity=".58"/>
<rect x="150" y="30" width="900" height="640" rx="15" fill="url(#panel)" stroke="#8d7d56" stroke-width="2" filter="url(#shadow)"/>
<text x="180" y="80" font-family="Arial,sans-serif" font-size="24" font-weight="bold" letter-spacing="4" fill="#f3ead1">PAUSED</text>
<circle cx="779" cy="70" r="22" fill="#253a31" stroke="#b7b381"/>
<text x="779" y="78" text-anchor="middle" font-family="Georgia,serif" font-size="23" fill="#d5b875">+</text>
<text x="814" y="67" font-family="Arial,sans-serif" font-size="17" font-weight="bold" fill="#f3ead1">Aria</text>
<text x="814" y="86" font-family="Arial,sans-serif" font-size="12" fill="#b8b5a7">Level 24 · Priest / Discipline</text>
<path d="M178 105H1022" stroke="#8d7d56" opacity=".6"/>
<text x="180" y="131" font-family="Arial,sans-serif" font-size="12" font-weight="bold" letter-spacing="1" fill="#d5b875">BOSS CARDS IN THIS DUNGEON</text>
<text x="1018" y="131" text-anchor="end" font-family="Arial,sans-serif" font-size="12" fill="#b8b5a7">2 / 6 COLLECTED</text>
<g transform="translate(180 145)">
<rect width="155" height="171" rx="7" fill="#17231e" stroke="#69b4ff"/>
<rect x="1" y="1" width="153" height="20" rx="6" fill="#0a1410"/>
<text x="8" y="14" font-family="Arial,sans-serif" font-size="8" letter-spacing="1" fill="#e9e2d2">BOSS 01</text>
<text x="147" y="14" text-anchor="end" font-family="Arial,sans-serif" font-size="8" fill="#69b4ff">OWNED</text>
<rect x="4" y="23" width="147" height="79" fill="url(#portrait)"/>
<ellipse cx="78" cy="100" rx="37" ry="4" fill="#030a08" opacity=".6"/>
<use href="#serpent" transform="translate(46 22) scale(.67)"/>
<text x="7" y="120" font-family="Georgia,serif" font-size="12" fill="#f3ead1">Lady Anacondra</text>
<text x="7" y="136" font-family="Arial,sans-serif" font-size="9" fill="#b8b5a7">3 copies owned</text>
<path d="M0 143H155" stroke="#536357" opacity=".6"/>
<g transform="translate(8 148)"><rect width="15" height="15" rx="3" fill="#2a3a3c" stroke="#ddd8ce"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#ddd8ce">N</text></g><g transform="translate(27 148)"><rect width="15" height="15" rx="3" fill="#2a3a3c" stroke="#69b4ff"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#69b4ff">R</text></g><g transform="translate(46 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">E</text></g><g transform="translate(65 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">L</text></g>
</g>
<g transform="translate(349 145)">
<rect width="155" height="171" rx="7" fill="#17231e" stroke="#6e796d"/>
<rect x="1" y="1" width="153" height="20" rx="6" fill="#0a1410"/>
<text x="8" y="14" font-family="Arial,sans-serif" font-size="8" letter-spacing="1" fill="#e9e2d2">BOSS 02</text>
<text x="147" y="14" text-anchor="end" font-family="Arial,sans-serif" font-size="8" fill="#a5a99b">MISSING</text>
<rect x="4" y="23" width="147" height="79" fill="url(#portrait)"/>
<ellipse cx="78" cy="100" rx="37" ry="4" fill="#030a08" opacity=".6"/>
<use href="#serpent" transform="translate(46 22) scale(.67)"/>
<text x="7" y="120" font-family="Georgia,serif" font-size="12" fill="#f3ead1">Lord Cobrahn</text>
<text x="7" y="136" font-family="Arial,sans-serif" font-size="9" fill="#b8b5a7">Not collected</text>
<path d="M0 143H155" stroke="#536357" opacity=".6"/>
<g transform="translate(8 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">N</text></g><g transform="translate(27 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">R</text></g><g transform="translate(46 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">E</text></g><g transform="translate(65 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">L</text></g>
</g>
<g transform="translate(518 145)">
<rect width="155" height="171" rx="7" fill="#17231e" stroke="#6e796d"/>
<rect x="1" y="1" width="153" height="20" rx="6" fill="#0a1410"/>
<text x="8" y="14" font-family="Arial,sans-serif" font-size="8" letter-spacing="1" fill="#e9e2d2">BOSS 03</text>
<text x="147" y="14" text-anchor="end" font-family="Arial,sans-serif" font-size="8" fill="#a5a99b">MISSING</text>
<rect x="4" y="23" width="147" height="79" fill="url(#portrait)"/>
<ellipse cx="78" cy="100" rx="37" ry="4" fill="#030a08" opacity=".6"/>
<use href="#serpent" transform="translate(46 22) scale(.67)"/>
<text x="7" y="120" font-family="Georgia,serif" font-size="12" fill="#f3ead1">Lord Pythas</text>
<text x="7" y="136" font-family="Arial,sans-serif" font-size="9" fill="#b8b5a7">Not collected</text>
<path d="M0 143H155" stroke="#536357" opacity=".6"/>
<g transform="translate(8 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">N</text></g><g transform="translate(27 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">R</text></g><g transform="translate(46 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">E</text></g><g transform="translate(65 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">L</text></g>
</g>
<g transform="translate(687 145)">
<rect width="155" height="171" rx="7" fill="#17231e" stroke="#c493ff"/>
<rect x="1" y="1" width="153" height="20" rx="6" fill="#0a1410"/>
<text x="8" y="14" font-family="Arial,sans-serif" font-size="8" letter-spacing="1" fill="#e9e2d2">BOSS 04</text>
<text x="147" y="14" text-anchor="end" font-family="Arial,sans-serif" font-size="8" fill="#c493ff">OWNED</text>
<rect x="4" y="23" width="147" height="79" fill="url(#portrait)"/>
<ellipse cx="78" cy="100" rx="37" ry="4" fill="#030a08" opacity=".6"/>
<use href="#guardian" transform="translate(46 22) scale(.67)"/>
<text x="7" y="120" font-family="Georgia,serif" font-size="10" fill="#f3ead1">Verdan the Everliving</text>
<text x="7" y="136" font-family="Arial,sans-serif" font-size="9" fill="#b8b5a7">5 copies owned</text>
<path d="M0 143H155" stroke="#536357" opacity=".6"/>
<g transform="translate(8 148)"><rect width="15" height="15" rx="3" fill="#2a3a3c" stroke="#ddd8ce"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#ddd8ce">N</text></g><g transform="translate(27 148)"><rect width="15" height="15" rx="3" fill="#2a3a3c" stroke="#69b4ff"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#69b4ff">R</text></g><g transform="translate(46 148)"><rect width="15" height="15" rx="3" fill="#2a3a3c" stroke="#c493ff"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#c493ff">E</text></g><g transform="translate(65 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">L</text></g>
</g>
<g transform="translate(856 145)">
<rect width="155" height="171" rx="7" fill="#17231e" stroke="#6e796d"/>
<rect x="1" y="1" width="153" height="20" rx="6" fill="#0a1410"/>
<text x="8" y="14" font-family="Arial,sans-serif" font-size="8" letter-spacing="1" fill="#e9e2d2">BOSS 05</text>
<text x="147" y="14" text-anchor="end" font-family="Arial,sans-serif" font-size="8" fill="#a5a99b">MISSING</text>
<rect x="4" y="23" width="147" height="79" fill="url(#portrait)"/>
<ellipse cx="78" cy="100" rx="37" ry="4" fill="#030a08" opacity=".6"/>
<use href="#serpent" transform="translate(46 22) scale(.67)"/>
<text x="7" y="120" font-family="Georgia,serif" font-size="12" fill="#f3ead1">Lord Serpentis</text>
<text x="7" y="136" font-family="Arial,sans-serif" font-size="9" fill="#b8b5a7">Not collected</text>
<path d="M0 143H155" stroke="#536357" opacity=".6"/>
<g transform="translate(8 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">N</text></g><g transform="translate(27 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">R</text></g><g transform="translate(46 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">E</text></g><g transform="translate(65 148)"><rect width="15" height="15" rx="3" fill="#252f2c" stroke="#5c6a60"/><text x="7.5" y="11" text-anchor="middle" font-family="Arial,sans-serif" font-size="9" fill="#79877d">L</text></g>
</g>
<text x="1018" y="335" text-anchor="end" font-family="Arial,sans-serif" font-size="11" fill="#9fa994">SCROLL →</text>
<path d="M178 347H1022" stroke="#8d7d56" opacity=".6"/>
<rect x="440" y="364" width="320" height="45" rx="8" fill="#587348" stroke="#9bb477"/>
<text x="600" y="393" text-anchor="middle" font-family="Arial,sans-serif" font-size="16" font-weight="bold" fill="#fbfee9">Resume game</text>
<g font-family="Arial,sans-serif">
<rect x="180" y="427" width="384" height="101" rx="8" fill="#1a2621" stroke="#526153"/>
<rect x="636" y="427" width="384" height="101" rx="8" fill="#1a2621" stroke="#526153"/>
<rect x="180" y="544" width="384" height="101" rx="8" fill="#1a2621" stroke="#526153"/>
<rect x="636" y="544" width="384" height="101" rx="8" fill="#1a2621" stroke="#526153"/>
<g fill="#b8b5a7" font-size="12" font-weight="bold" letter-spacing="2"><text x="195" y="451">ADVENTURE</text><text x="651" y="451">CHARACTER</text><text x="195" y="568">SETTINGS</text><text x="651" y="568">SESSION</text></g>
<g fill="#314136" stroke="#64795d"><rect x="195" y="466" width="169" height="43" rx="6"/><rect x="379" y="466" width="169" height="43" rx="6"/><rect x="651" y="466" width="169" height="43" rx="6"/><rect x="835" y="466" width="169" height="43" rx="6"/><rect x="195" y="583" width="169" height="43" rx="6"/><rect x="379" y="583" width="169" height="43" rx="6"/><rect x="651" y="583" width="169" height="43" rx="6"/><rect x="835" y="583" width="169" height="43" rx="6"/></g>
<g fill="#eee9d9" font-size="12" text-anchor="middle"><text x="279" y="493">Party management</text><text x="463" y="493">Dungeon map</text><text x="735" y="493">Inventory</text><text x="919" y="493">Spellbook</text><text x="279" y="610">Options</text><text x="463" y="610">Keybinds</text><text x="735" y="610">Return to entrance</text><text x="919" y="610">Main menu</text></g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,142 @@
import { writeFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import sharp from "sharp";
const outputDir = dirname(fileURLToPath(import.meta.url));
const bosses = [
{ name: "Lady Anacondra", copies: "3 copies owned", owned: true, accent: "#69b4ff", shape: "serpent" },
{ name: "Lord Cobrahn", copies: "Card not collected", owned: false, accent: "#a5a99b", shape: "serpent" },
{ name: "Lord Pythas", copies: "Card not collected", owned: false, accent: "#a5a99b", shape: "serpent" },
{ name: "Verdan the Everliving", copies: "5 copies owned", owned: true, accent: "#c493ff", shape: "guardian" },
{ name: "Lord Serpentis", copies: "Card not collected", owned: false, accent: "#a5a99b", shape: "serpent" },
{ name: "Mutanus the Devourer", copies: "Card not collected", owned: false, accent: "#a5a99b", shape: "mutant" },
];
const defs = `<defs>
<linearGradient id="backdrop" x2="1" y2="1"><stop stop-color="#193628"/><stop offset="1" stop-color="#06100e"/></linearGradient>
<linearGradient id="panel" x2="1" y2="1"><stop stop-color="#27312a"/><stop offset="1" stop-color="#0c1714"/></linearGradient>
<radialGradient id="portrait"><stop stop-color="#435549"/><stop offset="1" stop-color="#111d1a"/></radialGradient>
<linearGradient id="scale" x2=".8" y2="1"><stop stop-color="#aac49b"/><stop offset=".45" stop-color="#4d8067"/><stop offset="1" stop-color="#1d4b42"/></linearGradient>
<linearGradient id="belly" x2="1" y2=".8"><stop stop-color="#eee5a5"/><stop offset="1" stop-color="#a7a474"/></linearGradient>
<linearGradient id="guardian-fill" x2=".8" y2="1"><stop stop-color="#aad36f"/><stop offset=".48" stop-color="#568d49"/><stop offset="1" stop-color="#274f37"/></linearGradient>
<filter id="shadow"><feDropShadow dx="0" dy="12" stdDeviation="17" flood-opacity=".7"/></filter>
<filter id="creature-shadow"><feDropShadow dx="0" dy="5" stdDeviation="4" flood-opacity=".7"/></filter>
<g id="serpent" filter="url(#creature-shadow)">
<path d="M24 92 C8 75 30 61 53 69 C84 79 89 55 69 45 C53 37 54 23 67 18" fill="none" stroke="#193f37" stroke-width="26" stroke-linecap="round"/>
<path d="M24 91 C12 77 36 65 55 73 C82 82 92 54 70 44 C56 36 57 23 68 18" fill="none" stroke="url(#scale)" stroke-width="19" stroke-linecap="round"/>
<path d="M27 84 C35 76 43 75 52 78 M67 73 C75 70 79 62 78 56 M67 41 C59 32 62 22 68 18" fill="none" stroke="url(#belly)" stroke-width="6" stroke-linecap="round" opacity=".85"/>
<path d="M62 25 L56 15 L64 18 L67 9 L74 17 L82 14 L79 25 Q78 35 70 36 Q61 35 62 25" fill="url(#scale)" stroke="#173d34" stroke-width="2"/>
<path d="M62 33 Q69 43 77 33 L73 48 L69 40 L64 48Z" fill="#e2cf9c"/>
<circle cx="65" cy="25" r="2.5" fill="#f8d378"/><circle cx="75" cy="25" r="2.5" fill="#f8d378"/>
</g>
<g id="guardian" filter="url(#creature-shadow)">
<path d="M40 92 L22 76 L17 47 L29 28 L43 22 L63 26 L77 43 L82 75 L65 91Z" fill="url(#guardian-fill)" stroke="#214c30" stroke-width="3"/>
<path d="M24 41 L11 20 L31 31 M72 40 L90 18 L75 50" fill="url(#guardian-fill)" stroke="#214c30" stroke-width="3"/>
<path d="M38 30 L40 15 L50 25 L60 14 L66 33 M27 58 L10 53 L25 72 M76 59 L91 54 L80 76" fill="#427d3d" stroke="#1b472b" stroke-width="3"/>
<path d="M40 43 Q50 51 61 43 L65 71 Q50 80 35 69Z" fill="#9cc372" opacity=".8"/>
<path d="M35 38 L45 42 L41 46 M65 38 L55 42 L59 46" fill="#eae99e"/>
<path d="M40 58 Q50 63 61 58" fill="none" stroke="#173e29" stroke-width="4"/>
</g>
<g id="mutant" filter="url(#creature-shadow)">
<path d="M30 90 L20 65 L30 37 L45 29 L58 31 L73 45 L80 69 L68 91Z" fill="url(#scale)" stroke="#214b3e" stroke-width="3"/>
<path d="M34 35 L21 19 L24 42 M62 36 L75 18 L75 46" fill="url(#scale)" stroke="#214b3e" stroke-width="3"/>
<path d="M21 61 L5 53 L18 80 M76 58 L92 48 L84 80" fill="none" stroke="#376b56" stroke-width="12" stroke-linecap="round"/>
<path d="M36 51 L46 54 M62 51 L55 54" stroke="#e7dc89" stroke-width="5" stroke-linecap="round"/>
<path d="M38 66 Q49 80 62 66 L58 75 L54 69 L50 79 L46 69 L42 75Z" fill="#eadfb3"/>
</g>
</defs>`;
function rarityMarks(boss, x, y, compact = false) {
const size = compact ? 15 : 21;
const gap = compact ? 4 : 6;
return ["N", "R", "E", "L"].map((label, index) => {
const owned = boss.owned && (boss.name.startsWith("Verdan") ? index < 3 : index < 2);
const color = ["#ddd8ce", "#69b4ff", "#c493ff", "#e5a757"][index];
return `<g transform="translate(${x + index * (size + gap)} ${y})"><rect width="${size}" height="${size}" rx="3" fill="${owned ? "#2a3a3c" : "#252f2c"}" stroke="${owned ? color : "#5c6a60"}"/><text x="${size / 2}" y="${compact ? 11 : 15}" text-anchor="middle" font-family="Arial,sans-serif" font-size="${compact ? 9 : 12}" fill="${owned ? color : "#79877d"}">${label}</text></g>`;
}).join("");
}
function largeCard(boss, index) {
const x = 88 + index % 3 * 352;
const y = 166 + Math.floor(index / 3) * 226;
const owned = boss.owned;
return `<g transform="translate(${x} ${y})">
<rect width="320" height="210" rx="9" fill="#151f1b" stroke="${owned ? boss.accent : "#69756b"}" stroke-width="${owned ? 2 : 1}"/>
<rect x="1" y="1" width="318" height="28" rx="8" fill="#0a1410"/>
<text x="12" y="19" font-family="Arial,sans-serif" font-size="11" letter-spacing="2" fill="#efe8d7">BOSS ${String(index + 1).padStart(2, "0")}</text>
<text x="307" y="19" text-anchor="end" font-family="Arial,sans-serif" font-size="10" fill="${boss.accent}">${owned ? "COLLECTED" : "UNCOLLECTED"}</text>
<rect x="7" y="31" width="306" height="113" rx="3" fill="url(#portrait)"/>
<ellipse cx="160" cy="137" rx="53" ry="5" fill="#030a08" opacity=".7"/>
<use href="#${boss.shape}" transform="translate(105 30) scale(1.08)"/>
<text x="13" y="164" font-family="Georgia,serif" font-size="${boss.name.length > 20 ? 18 : 20}" fill="#f3ead1">${boss.name}</text>
<text x="13" y="181" font-family="Arial,sans-serif" font-size="11" fill="#b8b5a7">${boss.copies}</text>
<path d="M0 187H320" stroke="#536357" opacity=".6"/>
${rarityMarks(boss, 12, 191)}
</g>`;
}
const countdown = `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700">
${defs}
<rect width="1200" height="700" fill="url(#backdrop)"/>
<path d="M0 660Q130 310 300 490T760 365T1200 470V700H0" fill="#2c5037" opacity=".24"/>
<rect x="58" y="34" width="1084" height="632" rx="16" fill="url(#panel)" stroke="#8d7d56" stroke-width="2" filter="url(#shadow)"/>
<text x="88" y="102" font-family="Georgia,serif" font-size="38" fill="#f3ead1">Wailing Caverns</text>
<text x="88" y="132" font-family="Arial,sans-serif" font-size="14" fill="#b8b5a7">6 bosses ahead · 2 collected</text>
<path d="M978 56V140" stroke="#88764f"/>
<text x="1050" y="78" text-anchor="middle" font-family="Arial,sans-serif" font-size="11" letter-spacing="2" fill="#b8b5a7">ENTERING IN</text>
<text x="1050" y="134" text-anchor="middle" font-family="Georgia,serif" font-size="60" fill="#d5b875">05</text>
${bosses.map(largeCard).join("\n ")}
</svg>\n`;
function compactCard(boss, index) {
const x = 180 + index * 169;
return `<g transform="translate(${x} 145)">
<rect width="155" height="171" rx="7" fill="#17231e" stroke="${boss.owned ? boss.accent : "#6e796d"}"/>
<rect x="1" y="1" width="153" height="20" rx="6" fill="#0a1410"/>
<text x="8" y="14" font-family="Arial,sans-serif" font-size="8" letter-spacing="1" fill="#e9e2d2">BOSS ${String(index + 1).padStart(2, "0")}</text>
<text x="147" y="14" text-anchor="end" font-family="Arial,sans-serif" font-size="8" fill="${boss.accent}">${boss.owned ? "OWNED" : "MISSING"}</text>
<rect x="4" y="23" width="147" height="79" fill="url(#portrait)"/>
<ellipse cx="78" cy="100" rx="37" ry="4" fill="#030a08" opacity=".6"/>
<use href="#${boss.shape}" transform="translate(46 22) scale(.67)"/>
<text x="7" y="120" font-family="Georgia,serif" font-size="${boss.name.length > 19 ? 10 : 12}" fill="#f3ead1">${boss.name}</text>
<text x="7" y="136" font-family="Arial,sans-serif" font-size="9" fill="#b8b5a7">${boss.owned ? boss.copies : "Not collected"}</text>
<path d="M0 143H155" stroke="#536357" opacity=".6"/>
${rarityMarks(boss, 8, 148, true)}
</g>`;
}
const pause = `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700">
${defs}
<rect width="1200" height="700" fill="url(#backdrop)"/>
<path d="M0 680Q130 370 320 520T700 390T1200 540V700H0" fill="#275137" opacity=".32"/>
<rect width="1200" height="700" fill="#020705" opacity=".58"/>
<rect x="150" y="30" width="900" height="640" rx="15" fill="url(#panel)" stroke="#8d7d56" stroke-width="2" filter="url(#shadow)"/>
<text x="180" y="80" font-family="Arial,sans-serif" font-size="24" font-weight="bold" letter-spacing="4" fill="#f3ead1">PAUSED</text>
<circle cx="779" cy="70" r="22" fill="#253a31" stroke="#b7b381"/>
<text x="779" y="78" text-anchor="middle" font-family="Georgia,serif" font-size="23" fill="#d5b875">+</text>
<text x="814" y="67" font-family="Arial,sans-serif" font-size="17" font-weight="bold" fill="#f3ead1">Aria</text>
<text x="814" y="86" font-family="Arial,sans-serif" font-size="12" fill="#b8b5a7">Level 24 · Priest / Discipline</text>
<path d="M178 105H1022" stroke="#8d7d56" opacity=".6"/>
<text x="180" y="131" font-family="Arial,sans-serif" font-size="12" font-weight="bold" letter-spacing="1" fill="#d5b875">BOSS CARDS IN THIS DUNGEON</text>
<text x="1018" y="131" text-anchor="end" font-family="Arial,sans-serif" font-size="12" fill="#b8b5a7">2 / 6 COLLECTED</text>
${bosses.slice(0, 5).map(compactCard).join("\n ")}
<text x="1018" y="335" text-anchor="end" font-family="Arial,sans-serif" font-size="11" fill="#9fa994">SCROLL →</text>
<path d="M178 347H1022" stroke="#8d7d56" opacity=".6"/>
<rect x="440" y="364" width="320" height="45" rx="8" fill="#587348" stroke="#9bb477"/>
<text x="600" y="393" text-anchor="middle" font-family="Arial,sans-serif" font-size="16" font-weight="bold" fill="#fbfee9">Resume game</text>
<g font-family="Arial,sans-serif">
<rect x="180" y="427" width="384" height="101" rx="8" fill="#1a2621" stroke="#526153"/>
<rect x="636" y="427" width="384" height="101" rx="8" fill="#1a2621" stroke="#526153"/>
<rect x="180" y="544" width="384" height="101" rx="8" fill="#1a2621" stroke="#526153"/>
<rect x="636" y="544" width="384" height="101" rx="8" fill="#1a2621" stroke="#526153"/>
<g fill="#b8b5a7" font-size="12" font-weight="bold" letter-spacing="2"><text x="195" y="451">ADVENTURE</text><text x="651" y="451">CHARACTER</text><text x="195" y="568">SETTINGS</text><text x="651" y="568">SESSION</text></g>
<g fill="#314136" stroke="#64795d"><rect x="195" y="466" width="169" height="43" rx="6"/><rect x="379" y="466" width="169" height="43" rx="6"/><rect x="651" y="466" width="169" height="43" rx="6"/><rect x="835" y="466" width="169" height="43" rx="6"/><rect x="195" y="583" width="169" height="43" rx="6"/><rect x="379" y="583" width="169" height="43" rx="6"/><rect x="651" y="583" width="169" height="43" rx="6"/><rect x="835" y="583" width="169" height="43" rx="6"/></g>
<g fill="#eee9d9" font-size="12" text-anchor="middle"><text x="279" y="493">Party management</text><text x="463" y="493">Dungeon map</text><text x="735" y="493">Inventory</text><text x="919" y="493">Spellbook</text><text x="279" y="610">Options</text><text x="463" y="610">Keybinds</text><text x="735" y="610">Return to entrance</text><text x="919" y="610">Main menu</text></g>
</g>
</svg>\n`;
for (const [name, svg] of [["dungeon-boss-countdown", countdown], ["pause-boss-card-strip", pause]]) {
await writeFile(join(outputDir, `${name}.svg`), svg);
await sharp(Buffer.from(svg)).png().toFile(join(outputDir, `${name}.png`));
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "healer-man",
"version": "0.1.58",
"version": "0.1.59",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "healer-man",
"version": "0.1.58",
"version": "0.1.59",
"dependencies": {
"@capacitor/android": "8.4.1",
"@capacitor/core": "8.4.1",
+7 -2
View File
@@ -1,7 +1,7 @@
{
"name": "healer-man",
"private": true,
"version": "0.1.58",
"version": "0.1.59",
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
@@ -15,6 +15,11 @@
"android:prepare": "node scripts/content/prepare-android-bootstrap.mjs",
"content:test": "node --test scripts/content/*.test.mjs",
"effects:generate": "node scripts/combat-effects/generate-manifest.mjs --package-audio && node scripts/combat-effects/package-wow-original.mjs",
"effects:rom": "node scripts/combat-effects/generate-manifest.mjs --runewaker-only --package-audio",
"effects:rom:particles": "node scripts/combat-effects/package-rom-particles.mjs",
"effects:rom:audit": "node scripts/combat-effects/audit-rom-media.mjs --write",
"effects:rom:plan": "node scripts/combat-effects/plan-rom-media.mjs --write",
"effects:rom:batch": "npm run effects:rom && npm run effects:rom:particles && npm run effects:rom && npm run effects:rom:audit && npm run effects:rom:plan",
"effects:audit": "node scripts/combat-effects/package-wow-original.mjs --audit",
"effects:wow": "node scripts/combat-effects/package-wow-original.mjs",
"effects:priest": "npm run effects:wow",
@@ -125,7 +130,7 @@
"balance:lab:playtest": "node scripts/playtest-balance-lab.cjs",
"readiness:playtest": "node scripts/playtest-companion-readiness.cjs",
"test:ready-five": "vitest run src/game/companionSpecReadiness.test.ts src/game/readyFiveCertification.test.ts && python -m unittest scripts.test_balance_lab scripts.test_balance_lab_brackets",
"test:rom-five": "vitest run src/game/romFiveValidation.test.ts src/game/romCombatRuntime.test.ts src/game/romCombatMechanics.test.ts src/game/romAbilityCatalogSemantics.test.ts src/game/romPlayerCatalog.test.ts src/game/specializationCatalog.test.ts",
"test:rom-five": "vitest run src/game/romFiveValidation.test.ts src/game/romPriestKnightPairValidation.test.ts src/game/romScoutRoguePairValidation.test.ts src/game/romMageWarlockPairValidation.test.ts src/game/romCombatRuntime.test.ts src/game/romCombatMechanics.test.ts src/game/romAbilityCatalogSemantics.test.ts src/game/romPlayerCatalog.test.ts src/game/specializationCatalog.test.ts src/game/romHeirloomReference.test.ts src/game/romCreatureFamilies.test.ts",
"balance:smoke": "vite-node scripts/combat-benchmark/run.ts --class mage --role damage --scenario damage-single-burst --seeds 1 --duration-scale 0.2",
"prebuild": "npm run loot:generate && npm run web:prepare && npm run assets:ktx2:audit && npm run assets:performance:audit",
"build": "node --max-old-space-size=8192 node_modules/typescript/bin/tsc -b && node --max-old-space-size=8192 node_modules/vite/bin/vite.js build",
Loaded 100 of 913 files, more files were not shown because too many files have changed in this diff. Show more