Compare commits

..

22 Commits

Author SHA1 Message Date
troytt 3c827be346 feat(skills): implement 1.13c Delerium Change [Skill #350] (fixes #374) 2026-09-22 03:48:40 +00:00
troytt c5242c93b7 feat(skill-350): implement 1.13c Delerium Change module and Vitest suite (#374) 2026-09-22 03:48:40 +00:00
troytt d6f45c6b82 feat(skills): implement 1.13c Royal Strike [Skill #280] (fixes #373) 2026-09-22 03:48:38 +00:00
troytt 468cf5d05e feat(skill-280): implement 1.13c Royal Strike module and Vitest suite (#373) 2026-09-22 03:48:38 +00:00
troytt 738757ba63 feat(skills): implement 1.13c Shadow Master [Skill #279] (fixes #372) 2026-09-22 03:48:37 +00:00
troytt 2f2116f9fd feat(skill-279): implement 1.13c Shadow Master module and Vitest suite (#372) 2026-09-22 03:48:36 +00:00
troytt c3e1e56e42 feat(skills): implement 1.13c Venom [Skill #278] (fixes #371) 2026-09-22 03:48:35 +00:00
troytt 2d558698dc feat(skill-278): implement 1.13c Venom module and Vitest suite (#371) 2026-09-22 03:48:34 +00:00
troytt 81c1adb838 feat(skills): implement 1.13c Blade Shield [Skill #277] (fixes #370) 2026-09-22 03:48:33 +00:00
troytt 7b8f954d32 feat(skill-277): implement 1.13c Blade Shield module and Vitest suite (#370) 2026-09-22 03:48:32 +00:00
troytt ab35d29984 feat(skills): implement 1.13c Death Sentry [Skill #276] (fixes #369) 2026-09-22 03:48:31 +00:00
troytt dee07295b2 feat(skill-276): implement 1.13c Death Sentry module and Vitest suite (#369) 2026-09-22 03:48:30 +00:00
troytt fe508d79dd feat(skills): implement 1.13c Dragon Flight [Skill #275] (fixes #368) 2026-09-22 03:48:29 +00:00
troytt 80e1af9cf4 feat(skill-275): implement 1.13c Dragon Flight module and Vitest suite (#368) 2026-09-22 03:48:29 +00:00
troytt 0b62f6c830 feat(skills): implement 1.13c Blades of Ice [Skill #274] (fixes #367) 2026-09-22 03:48:27 +00:00
troytt 8d30d0b6ed feat(skill-274): implement 1.13c Blades of Ice module and Vitest suite (#367) 2026-09-22 03:48:27 +00:00
troytt 0347c93834 feat(skills): implement 1.13c Mind Blast [Skill #273] (fixes #366) 2026-09-22 03:48:26 +00:00
troytt 3170f0aa68 feat(skill-273): implement 1.13c Mind Blast module and Vitest suite (#366) 2026-09-22 03:48:25 +00:00
troytt 2237b45a0e feat(skills): implement 1.13c Inferno Sentry [Skill #272] (fixes #365) 2026-09-22 03:48:24 +00:00
troytt a0e46c9046 feat(skill-272): implement 1.13c Inferno Sentry module and Vitest suite (#365) 2026-09-22 03:48:23 +00:00
troytt 2bafeefe12 feat(skills): implement 1.13c Lightning Sentry [Skill #271] (fixes #364) 2026-09-22 03:48:22 +00:00
troytt fed2fc6688 feat(skill-271): implement 1.13c Lightning Sentry module and Vitest suite (#364) 2026-09-22 03:48:21 +00:00
22 changed files with 1683 additions and 0 deletions

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #271: Lightning Sentry (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 271 ("Lightning Sentry")
* - Class: "ass"
* - Required Level: 24
* - Server Functions: srvstfunc=0, srvdofunc=45, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
* - Associated Missile / State / Summon: missile="", state="", summon="lightningsentry"
* - 1.13c Synergy Formulas: EDmgSymPerCalc="(skill('Shock Field'.blvl) + skill('Charged Bolt Sentry'.blvl) + skill('Death Sentry'.blvl))*par8", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 271,
name: "Lightning Sentry",
charClass: "ass",
srvStFunc: 0,
srvDoFunc: 45,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-271-lightning-sentry',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #272: Inferno Sentry (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 272 ("Inferno Sentry")
* - Class: "ass"
* - Required Level: 24
* - Server Functions: srvstfunc=0, srvdofunc=45, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
* - Associated Missile / State / Summon: missile="", state="", summon="infernosentry"
* - 1.13c Synergy Formulas: EDmgSymPerCalc="(skill('Fire Trauma'.blvl) + skill('Death Sentry'.blvl)) * par7 + skill('Wake of Fire Sentry'.blvl)*par8", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 272,
name: "Inferno Sentry",
charClass: "ass",
srvStFunc: 0,
srvDoFunc: 45,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-272-inferno-sentry',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #273: Mind Blast (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 273 ("Mind Blast")
* - Class: "ass"
* - Required Level: 24
* - Server Functions: srvstfunc=0, srvdofunc=51, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
* - Associated Missile / State / Summon: missile="", state="", summon=""
* - 1.13c Synergy Formulas: EDmgSymPerCalc="", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 273,
name: "Mind Blast",
charClass: "ass",
srvStFunc: 0,
srvDoFunc: 51,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-273-mind-blast',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #274: Blades of Ice (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 274 ("Blades of Ice")
* - Class: "ass"
* - Required Level: 24
* - Server Functions: srvstfunc=23, srvdofunc=35, srvprgfunc1=0, srvprgfunc2=38, srvprgfunc3=39
* - Associated Missile / State / Summon: missile="", state="progressive_cold", summon=""
* - 1.13c Synergy Formulas: EDmgSymPerCalc="(skill('Royal Strike'.blvl)) * par8", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 274,
name: "Blades of Ice",
charClass: "ass",
srvStFunc: 23,
srvDoFunc: 35,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-274-blades-of-ice',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #275: Dragon Flight (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 275 ("Dragon Flight")
* - Class: "ass"
* - Required Level: 24
* - Server Functions: srvstfunc=12, srvdofunc=52, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
* - Associated Missile / State / Summon: missile="", state="", summon=""
* - 1.13c Synergy Formulas: EDmgSymPerCalc="", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 275,
name: "Dragon Flight",
charClass: "ass",
srvStFunc: 12,
srvDoFunc: 52,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-275-dragon-flight',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #276: Death Sentry (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 276 ("Death Sentry")
* - Class: "ass"
* - Required Level: 30
* - Server Functions: srvstfunc=0, srvdofunc=45, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
* - Associated Missile / State / Summon: missile="", state="", summon="deathsentry"
* - 1.13c Synergy Formulas: EDmgSymPerCalc="(skill('Lightning Sentry'.blvl))*par8", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 276,
name: "Death Sentry",
charClass: "ass",
srvStFunc: 0,
srvDoFunc: 45,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-276-death-sentry',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #277: Blade Shield (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 277 ("Blade Shield")
* - Class: "ass"
* - Required Level: 30
* - Server Functions: srvstfunc=28, srvdofunc=54, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
* - Associated Missile / State / Summon: missile="blade shield attachment", state="bladeshield", summon=""
* - 1.13c Synergy Formulas: EDmgSymPerCalc="", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 277,
name: "Blade Shield",
charClass: "ass",
srvStFunc: 28,
srvDoFunc: 54,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-277-blade-shield',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #278: Venom (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 278 ("Venom")
* - Class: "ass"
* - Required Level: 30
* - Server Functions: srvstfunc=0, srvdofunc=18, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
* - Associated Missile / State / Summon: missile="", state="venomclaws", summon=""
* - 1.13c Synergy Formulas: EDmgSymPerCalc="", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 278,
name: "Venom",
charClass: "ass",
srvStFunc: 0,
srvDoFunc: 18,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-278-venom',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #279: Shadow Master (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 279 ("Shadow Master")
* - Class: "ass"
* - Required Level: 30
* - Server Functions: srvstfunc=0, srvdofunc=49, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
* - Associated Missile / State / Summon: missile="", state="shadowwarrior", summon="shadowmaster"
* - 1.13c Synergy Formulas: EDmgSymPerCalc="", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 279,
name: "Shadow Master",
charClass: "ass",
srvStFunc: 0,
srvDoFunc: 49,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-279-shadow-master',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #280: Royal Strike (ASS)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 280 ("Royal Strike")
* - Class: "ass"
* - Required Level: 30
* - Server Functions: srvstfunc=23, srvdofunc=34, srvprgfunc1=40, srvprgfunc2=143, srvprgfunc3=41
* - Associated Missile / State / Summon: missile="royalstrikemeteorcenter", state="progressive_other", summon=""
* - 1.13c Synergy Formulas: EDmgSymPerCalc="", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 280,
name: "Royal Strike",
charClass: "ass",
srvStFunc: 23,
srvDoFunc: 34,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-280-royal-strike',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,44 @@
/**
* Diablo II: Lord of Destruction v1.13c — Skill #350: Delerium Change (UNI)
*
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
* - Skill ID: 350 ("Delerium Change")
* - Class: "uni"
* - Required Level: 1
* - Server Functions: srvstfunc=0, srvdofunc=116, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
* - Associated Missile / State / Summon: missile="", state="delerium", summon=""
* - 1.13c Synergy Formulas: EDmgSymPerCalc="", DmgSymPerCalc="", ELenSymPerCalc=""
*/
import type {
SkillEvalContext,
SkillEvalResult,
SkillExecContext,
SkillExecOutcome,
SkillModule,
} from '../../types.ts'
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
export const skillModule: SkillModule = {
skillId: 350,
name: "Delerium Change",
charClass: "uni",
srvStFunc: 0,
srvDoFunc: 116,
evaluate(ctx: SkillEvalContext): SkillEvalResult {
return evaluateSkillCore113c(ctx)
},
executeDo(ctx: SkillExecContext): SkillExecOutcome {
const outcome = executeSkillCore113c(ctx)
return {
...outcome,
notes: [
...outcome.notes,
'skill_module:skill-350-delerium-change',
],
}
},
}
export default skillModule

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #271: Lightning Sentry (ASS)
* Gitea Tracking Issue: #364
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-271-lightning-sentry.ts'
describe('[Skill #271] Lightning Sentry (ASS) — 1.13c Parity (Issue #364)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(271)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(271)
expect(skillModule.name).toBe("Lightning Sentry")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(271, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(20)
expect(r1.manaCost256).toBe(5120)
expect(r1.minPhysDmg).toBe(0)
expect(r1.maxPhysDmg).toBe(0)
expect(r1.minElemDmg256).toBe(256)
expect(r1.maxElemDmg256).toBe(5120)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(271, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(20)
expect(r10.manaCost256).toBe(5120)
expect(r10.minPhysDmg).toBe(0)
expect(r10.maxPhysDmg).toBe(0)
expect(r10.minElemDmg256).toBe(256)
expect(r10.maxElemDmg256).toBe(31232)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(271, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(20)
expect(r20.manaCost256).toBe(5120)
expect(r20.minPhysDmg).toBe(0)
expect(r20.maxPhysDmg).toBe(0)
expect(r20.minElemDmg256).toBe(256)
expect(r20.maxElemDmg256).toBe(80384)
expect(r20.durationFrames).toBe(0)
expect(r20.radiusSubtiles).toBe(0)
expect(r20.petMax).toBe(5)
expect(r20.cooldownFrames).toBe(0)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(271)!
const synId = 256
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(271, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(240)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(271, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 271,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(271)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #272: Inferno Sentry (ASS)
* Gitea Tracking Issue: #365
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-272-inferno-sentry.ts'
describe('[Skill #272] Inferno Sentry (ASS) — 1.13c Parity (Issue #365)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(272)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(272)
expect(skillModule.name).toBe("Inferno Sentry")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(272, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(20)
expect(r1.manaCost256).toBe(5120)
expect(r1.minPhysDmg).toBe(0)
expect(r1.maxPhysDmg).toBe(0)
expect(r1.minElemDmg256).toBe(320)
expect(r1.maxElemDmg256).toBe(800)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(272, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(20)
expect(r10.manaCost256).toBe(5120)
expect(r10.minPhysDmg).toBe(0)
expect(r10.maxPhysDmg).toBe(0)
expect(r10.minElemDmg256).toBe(2896)
expect(r10.maxElemDmg256).toBe(3664)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(272, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(20)
expect(r20.manaCost256).toBe(5120)
expect(r20.minPhysDmg).toBe(0)
expect(r20.maxPhysDmg).toBe(0)
expect(r20.minElemDmg256).toBe(6576)
expect(r20.maxElemDmg256).toBe(7664)
expect(r20.durationFrames).toBe(0)
expect(r20.radiusSubtiles).toBe(0)
expect(r20.petMax).toBe(5)
expect(r20.cooldownFrames).toBe(0)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(272)!
const synId = 251
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(272, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(200)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(272, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 272,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(272)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #273: Mind Blast (ASS)
* Gitea Tracking Issue: #366
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-273-mind-blast.ts'
describe('[Skill #273] Mind Blast (ASS) — 1.13c Parity (Issue #366)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(273)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(273)
expect(skillModule.name).toBe("Mind Blast")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(273, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(15)
expect(r1.manaCost256).toBe(3840)
expect(r1.minPhysDmg).toBe(10)
expect(r1.maxPhysDmg).toBe(20)
expect(r1.minElemDmg256).toBe(0)
expect(r1.maxElemDmg256).toBe(0)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(273, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(15)
expect(r10.manaCost256).toBe(3840)
expect(r10.minPhysDmg).toBe(34)
expect(r10.maxPhysDmg).toBe(44)
expect(r10.minElemDmg256).toBe(0)
expect(r10.maxElemDmg256).toBe(0)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(273, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(15)
expect(r20.manaCost256).toBe(3840)
expect(r20.minPhysDmg).toBe(96)
expect(r20.maxPhysDmg).toBe(106)
expect(r20.minElemDmg256).toBe(0)
expect(r20.maxElemDmg256).toBe(0)
expect(r20.durationFrames).toBe(145)
expect(r20.radiusSubtiles).toBe(4)
expect(r20.petMax).toBe(0)
expect(r20.cooldownFrames).toBe(0)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(273)!
const synId = null
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(273, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(0)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(273, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 273,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(273)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #274: Blades of Ice (ASS)
* Gitea Tracking Issue: #367
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-274-blades-of-ice.ts'
describe('[Skill #274] Blades of Ice (ASS) — 1.13c Parity (Issue #367)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(274)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(274)
expect(skillModule.name).toBe("Blades of Ice")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(274, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(3)
expect(r1.manaCost256).toBe(768)
expect(r1.minPhysDmg).toBe(0)
expect(r1.maxPhysDmg).toBe(0)
expect(r1.minElemDmg256).toBe(3840)
expect(r1.maxElemDmg256).toBe(8960)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(274, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(3)
expect(r10.manaCost256).toBe(768)
expect(r10.minPhysDmg).toBe(0)
expect(r10.maxPhysDmg).toBe(0)
expect(r10.minElemDmg256).toBe(23296)
expect(r10.maxElemDmg256).toBe(28416)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(274, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(3)
expect(r20.manaCost256).toBe(768)
expect(r20.minPhysDmg).toBe(0)
expect(r20.maxPhysDmg).toBe(0)
expect(r20.minElemDmg256).toBe(59136)
expect(r20.maxElemDmg256).toBe(66304)
expect(r20.durationFrames).toBe(375)
expect(r20.radiusSubtiles).toBe(63)
expect(r20.petMax).toBe(0)
expect(r20.cooldownFrames).toBe(0)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(274)!
const synId = 280
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(274, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(160)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(274, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 274,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(274)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #275: Dragon Flight (ASS)
* Gitea Tracking Issue: #368
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-275-dragon-flight.ts'
describe('[Skill #275] Dragon Flight (ASS) — 1.13c Parity (Issue #368)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(275)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(275)
expect(skillModule.name).toBe("Dragon Flight")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(275, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(15)
expect(r1.manaCost256).toBe(3840)
expect(r1.minPhysDmg).toBe(0)
expect(r1.maxPhysDmg).toBe(0)
expect(r1.minElemDmg256).toBe(0)
expect(r1.maxElemDmg256).toBe(0)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(275, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(15)
expect(r10.manaCost256).toBe(3840)
expect(r10.minPhysDmg).toBe(0)
expect(r10.maxPhysDmg).toBe(0)
expect(r10.minElemDmg256).toBe(0)
expect(r10.maxElemDmg256).toBe(0)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(275, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(15)
expect(r20.manaCost256).toBe(3840)
expect(r20.minPhysDmg).toBe(0)
expect(r20.maxPhysDmg).toBe(0)
expect(r20.minElemDmg256).toBe(0)
expect(r20.maxElemDmg256).toBe(0)
expect(r20.durationFrames).toBe(0)
expect(r20.radiusSubtiles).toBe(27)
expect(r20.petMax).toBe(0)
expect(r20.cooldownFrames).toBe(25)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(275)!
const synId = null
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(275, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(0)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(275, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 275,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(275)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #276: Death Sentry (ASS)
* Gitea Tracking Issue: #369
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-276-death-sentry.ts'
describe('[Skill #276] Death Sentry (ASS) — 1.13c Parity (Issue #369)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(276)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(276)
expect(skillModule.name).toBe("Death Sentry")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(276, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(20)
expect(r1.manaCost256).toBe(5120)
expect(r1.minPhysDmg).toBe(0)
expect(r1.maxPhysDmg).toBe(0)
expect(r1.minElemDmg256).toBe(256)
expect(r1.maxElemDmg256).toBe(12800)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(276, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(20)
expect(r10.manaCost256).toBe(5120)
expect(r10.minPhysDmg).toBe(0)
expect(r10.maxPhysDmg).toBe(0)
expect(r10.minElemDmg256).toBe(256)
expect(r10.maxElemDmg256).toBe(34304)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(276, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(20)
expect(r20.manaCost256).toBe(5120)
expect(r20.minPhysDmg).toBe(0)
expect(r20.maxPhysDmg).toBe(0)
expect(r20.minElemDmg256).toBe(256)
expect(r20.maxElemDmg256).toBe(78336)
expect(r20.durationFrames).toBe(0)
expect(r20.radiusSubtiles).toBe(0)
expect(r20.petMax).toBe(5)
expect(r20.cooldownFrames).toBe(0)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(276)!
const synId = 271
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(276, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(240)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(276, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 276,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(276)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #277: Blade Shield (ASS)
* Gitea Tracking Issue: #370
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-277-blade-shield.ts'
describe('[Skill #277] Blade Shield (ASS) — 1.13c Parity (Issue #370)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(277)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(277)
expect(skillModule.name).toBe("Blade Shield")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(277, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(27)
expect(r1.manaCost256).toBe(6912)
expect(r1.minPhysDmg).toBe(1)
expect(r1.maxPhysDmg).toBe(30)
expect(r1.minElemDmg256).toBe(0)
expect(r1.maxElemDmg256).toBe(0)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(277, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(45)
expect(r10.manaCost256).toBe(11520)
expect(r10.minPhysDmg).toBe(48)
expect(r10.maxPhysDmg).toBe(77)
expect(r10.minElemDmg256).toBe(0)
expect(r10.maxElemDmg256).toBe(0)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(277, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(65)
expect(r20.manaCost256).toBe(16640)
expect(r20.minPhysDmg).toBe(112)
expect(r20.maxPhysDmg).toBe(141)
expect(r20.minElemDmg256).toBe(0)
expect(r20.maxElemDmg256).toBe(0)
expect(r20.durationFrames).toBe(2875)
expect(r20.radiusSubtiles).toBe(6)
expect(r20.petMax).toBe(0)
expect(r20.cooldownFrames).toBe(0)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(277)!
const synId = null
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(277, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(0)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(277, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 277,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(277)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #278: Venom (ASS)
* Gitea Tracking Issue: #371
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-278-venom.ts'
describe('[Skill #278] Venom (ASS) — 1.13c Parity (Issue #371)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(278)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(278)
expect(skillModule.name).toBe("Venom")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(278, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(12)
expect(r1.manaCost256).toBe(3072)
expect(r1.minPhysDmg).toBe(0)
expect(r1.maxPhysDmg).toBe(0)
expect(r1.minElemDmg256).toBe(1536)
expect(r1.maxElemDmg256).toBe(2048)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(278, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(12)
expect(r10.manaCost256).toBe(3072)
expect(r10.minPhysDmg).toBe(0)
expect(r10.maxPhysDmg).toBe(0)
expect(r10.minElemDmg256).toBe(5248)
expect(r10.maxElemDmg256).toBe(5760)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(278, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(12)
expect(r20.manaCost256).toBe(3072)
expect(r20.minPhysDmg).toBe(0)
expect(r20.maxPhysDmg).toBe(0)
expect(r20.minElemDmg256).toBe(10880)
expect(r20.maxElemDmg256).toBe(11392)
expect(r20.durationFrames).toBe(4900)
expect(r20.radiusSubtiles).toBe(4900)
expect(r20.petMax).toBe(0)
expect(r20.cooldownFrames).toBe(0)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(278)!
const synId = null
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(278, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(0)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(278, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 278,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(278)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #279: Shadow Master (ASS)
* Gitea Tracking Issue: #372
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-279-shadow-master.ts'
describe('[Skill #279] Shadow Master (ASS) — 1.13c Parity (Issue #372)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(279)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(279)
expect(skillModule.name).toBe("Shadow Master")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(279, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(35)
expect(r1.manaCost256).toBe(8960)
expect(r1.minPhysDmg).toBe(0)
expect(r1.maxPhysDmg).toBe(0)
expect(r1.minElemDmg256).toBe(0)
expect(r1.maxElemDmg256).toBe(0)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(279, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(39)
expect(r10.manaCost256).toBe(10112)
expect(r10.minPhysDmg).toBe(0)
expect(r10.maxPhysDmg).toBe(0)
expect(r10.minElemDmg256).toBe(0)
expect(r10.maxElemDmg256).toBe(0)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(279, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(44)
expect(r20.manaCost256).toBe(11392)
expect(r20.minPhysDmg).toBe(0)
expect(r20.maxPhysDmg).toBe(0)
expect(r20.minElemDmg256).toBe(0)
expect(r20.maxElemDmg256).toBe(0)
expect(r20.durationFrames).toBe(0)
expect(r20.radiusSubtiles).toBe(775)
expect(r20.petMax).toBe(1)
expect(r20.cooldownFrames).toBe(150)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(279)!
const synId = null
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(279, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(0)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(279, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 279,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(279)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #280: Royal Strike (ASS)
* Gitea Tracking Issue: #373
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/ass/skill-280-royal-strike.ts'
describe('[Skill #280] Royal Strike (ASS) — 1.13c Parity (Issue #373)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(280)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(280)
expect(skillModule.name).toBe("Royal Strike")
expect(skillModule.charClass).toBe("ass")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(280, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(4)
expect(r1.manaCost256).toBe(1024)
expect(r1.minPhysDmg).toBe(0)
expect(r1.maxPhysDmg).toBe(0)
expect(r1.minElemDmg256).toBe(0)
expect(r1.maxElemDmg256).toBe(0)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(280, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(4)
expect(r10.manaCost256).toBe(1024)
expect(r10.minPhysDmg).toBe(0)
expect(r10.maxPhysDmg).toBe(0)
expect(r10.minElemDmg256).toBe(0)
expect(r10.maxElemDmg256).toBe(0)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(280, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(4)
expect(r20.manaCost256).toBe(1024)
expect(r20.minPhysDmg).toBe(0)
expect(r20.maxPhysDmg).toBe(0)
expect(r20.minElemDmg256).toBe(0)
expect(r20.maxElemDmg256).toBe(0)
expect(r20.durationFrames).toBe(375)
expect(r20.radiusSubtiles).toBe(8)
expect(r20.petMax).toBe(0)
expect(r20.cooldownFrames).toBe(0)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(280)!
const synId = null
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(280, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(0)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(280, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "ass",
skillId: 280,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(280)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})

View File

@ -0,0 +1,109 @@
/**
* 1.13c Numerical & Runtime Verification Suite — Skill #350: Delerium Change (UNI)
* Gitea Tracking Issue: #374
*/
import { describe, expect, it } from 'vitest'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
import { WorldArena } from '../../../src/game/engine/world-arena.ts'
import { skillModule } from '../../../src/game/skills/impl/uni/skill-350-delerium-change.ts'
describe('[Skill #350] Delerium Change (UNI) — 1.13c Parity (Issue #374)', () => {
it('evaluates exact 1.13c Skills.txt / Missiles.txt formulas at slvl 1, 10, and 20', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(350)!
expect(skillRec).toBeDefined()
expect(skillModule.skillId).toBe(350)
expect(skillModule.name).toBe("Delerium Change")
expect(skillModule.charClass).toBe("uni")
const stats1 = new UnitStatList()
stats1.setBaseSkillLevel(350, 1)
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
expect(r1.manaCost).toBe(0)
expect(r1.manaCost256).toBe(0)
expect(r1.minPhysDmg).toBe(0)
expect(r1.maxPhysDmg).toBe(0)
expect(r1.minElemDmg256).toBe(0)
expect(r1.maxElemDmg256).toBe(0)
const stats10 = new UnitStatList()
stats10.setBaseSkillLevel(350, 10)
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
expect(r10.manaCost).toBe(0)
expect(r10.manaCost256).toBe(0)
expect(r10.minPhysDmg).toBe(0)
expect(r10.maxPhysDmg).toBe(0)
expect(r10.minElemDmg256).toBe(0)
expect(r10.maxElemDmg256).toBe(0)
const stats20 = new UnitStatList()
stats20.setBaseSkillLevel(350, 20)
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
expect(r20.manaCost).toBe(0)
expect(r20.manaCost256).toBe(0)
expect(r20.minPhysDmg).toBe(0)
expect(r20.maxPhysDmg).toBe(0)
expect(r20.minElemDmg256).toBe(0)
expect(r20.maxElemDmg256).toBe(0)
expect(r20.durationFrames).toBe(1500)
expect(r20.radiusSubtiles).toBe(0)
expect(r20.petMax).toBe(0)
expect(r20.cooldownFrames).toBe(0)
})
it('enforces 1.13c blvl synergy rule (hard points boost synergy; +skills and item charges do NOT)', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(350)!
const synId = null
const statsBase = new UnitStatList()
statsBase.setBaseSkillLevel(350, 20)
if (synId !== null) {
statsBase.setBaseSkillLevel(synId, 20)
}
const withHardPoints = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsBase,
})
expect(withHardPoints.synergyBonusPct).toBe(0)
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
const statsSoftOnly = new UnitStatList()
statsSoftOnly.setBaseSkillLevel(350, 20)
statsSoftOnly.addStat('item_allskills', 20)
if (synId !== null) {
statsSoftOnly.setChargedSkillLevel(synId, 33)
}
const withSoftOnly = skillModule.evaluate!({
registry,
skill: skillRec,
slvl: 20,
blvl: 20,
statList: statsSoftOnly,
})
expect(withSoftOnly.synergyBonusPct).toBe(0)
})
it('executes runtime skill effect in WorldArena (castCount, action frame, and combat/state/missile/summon outcome)', async () => {
const registry = await getSharedDataRegistry()
const arena = new WorldArena({
registry,
classCode: "uni",
skillId: 350,
slvl: 20,
})
const outcome = arena.triggerCast()
arena.stepTicks(15)
const dbg = arena.getDebugState()
expect(dbg.ready).toBe(true)
expect(dbg.skillId).toBe(350)
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
expect(dbg.actionFrameTriggered).toBe(true)
expect(outcome?.executed).toBe(true)
})
})