Compare commits
50 Commits
c3c41377e3
...
210d59e3f2
| Author | SHA1 | Date |
|---|---|---|
|
|
210d59e3f2 | |
|
|
ea430c1522 | |
|
|
246e1580a1 | |
|
|
60bd856fcf | |
|
|
7d4bd0717e | |
|
|
1609996501 | |
|
|
c4bd201b27 | |
|
|
b4e564fc77 | |
|
|
166cd4ab7e | |
|
|
55f3616016 | |
|
|
c02b66b83a | |
|
|
eb7bd41f23 | |
|
|
e32c46b18b | |
|
|
c77fa985cb | |
|
|
fbc2118a26 | |
|
|
7978d6bf08 | |
|
|
fbcf9c82c0 | |
|
|
06611910af | |
|
|
15e96acaa0 | |
|
|
c11219dd9d | |
|
|
11ee925c2e | |
|
|
104b4d6c62 | |
|
|
02e7080aec | |
|
|
50b5d95067 | |
|
|
63b109b473 | |
|
|
f0b9e7f834 | |
|
|
522ba0bcf2 | |
|
|
b1a72bbdf6 | |
|
|
c42275aab1 | |
|
|
84956115dd | |
|
|
3ee8d2057e | |
|
|
3fba1e79bc | |
|
|
6f8e6052ab | |
|
|
536d736803 | |
|
|
76d8fa3c8a | |
|
|
4f6d8d44e9 | |
|
|
2d9573805e | |
|
|
c2c139f17b | |
|
|
192cdc2424 | |
|
|
8189f3c643 | |
|
|
bec78e45ac | |
|
|
494409e04a | |
|
|
a6d41e7c1b | |
|
|
83bbb4abf6 | |
|
|
24bc586278 | |
|
|
d7993ebc3e | |
|
|
3243ef2c29 | |
|
|
cf075924de | |
|
|
8b04d010fd | |
|
|
130c22e563 |
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #085: BloodGolem (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 85 ("BloodGolem")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 18
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=56, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="", summon="BloodGolem"
|
||||||
|
* - 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: 85,
|
||||||
|
name: "BloodGolem",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 56,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-085-bloodgolem',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #086: Attract (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 86 ("Attract")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 24
|
||||||
|
* - Server Functions: srvstfunc=18, srvdofunc=59, 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: 86,
|
||||||
|
name: "Attract",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 18,
|
||||||
|
srvDoFunc: 59,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-086-attract',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #087: Decrepify (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 87 ("Decrepify")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 24
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=30, 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: 87,
|
||||||
|
name: "Decrepify",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 30,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-087-decrepify',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #088: Bone Prison (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 88 ("Bone Prison")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 24
|
||||||
|
* - Server Functions: srvstfunc=19, srvdofunc=62, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="", summon="bonewall"
|
||||||
|
* - 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: 88,
|
||||||
|
name: "Bone Prison",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 19,
|
||||||
|
srvDoFunc: 62,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-088-bone-prison',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #089: Summon Resist (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 89 ("Summon Resist")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 24
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=0, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="summonresist", 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: 89,
|
||||||
|
name: "Summon Resist",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 0,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-089-summon-resist',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #090: IronGolem (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 90 ("IronGolem")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 24
|
||||||
|
* - Server Functions: srvstfunc=20, srvdofunc=57, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="thorns", summon="IronGolem"
|
||||||
|
* - 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: 90,
|
||||||
|
name: "IronGolem",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 20,
|
||||||
|
srvDoFunc: 57,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-090-irongolem',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #091: Lower Resist (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 91 ("Lower Resist")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 30
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=30, 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: 91,
|
||||||
|
name: "Lower Resist",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 30,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-091-lower-resist',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #092: Poison Nova (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 92 ("Poison Nova")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 30
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=22, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="poisonnova", state="", summon=""
|
||||||
|
* - 1.13c Synergy Formulas: EDmgSymPerCalc="(skill('Poison Dagger'.blvl)+skill('Poison Explosion'.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: 92,
|
||||||
|
name: "Poison Nova",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 22,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-092-poison-nova',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #093: Bone Spirit (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 93 ("Bone Spirit")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 30
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=10, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="bonespirit", state="", summon=""
|
||||||
|
* - 1.13c Synergy Formulas: EDmgSymPerCalc="(skill('Bone Wall'.blvl)+skill('Bone Prison'.blvl)+skill('Teeth'.blvl)+skill('Bone Spear'.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: 93,
|
||||||
|
name: "Bone Spirit",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 10,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-093-bone-spirit',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #094: FireGolem (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 94 ("FireGolem")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 30
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=56, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="", summon="FireGolem"
|
||||||
|
* - 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: 94,
|
||||||
|
name: "FireGolem",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 56,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-094-firegolem',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #095: Revive (NEC)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 95 ("Revive")
|
||||||
|
* - Class: "nec"
|
||||||
|
* - Required Level: 30
|
||||||
|
* - Server Functions: srvstfunc=21, srvdofunc=58, 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: 95,
|
||||||
|
name: "Revive",
|
||||||
|
charClass: "nec",
|
||||||
|
srvStFunc: 21,
|
||||||
|
srvDoFunc: 58,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-095-revive',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #096: Sacrifice (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 96 ("Sacrifice")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 1
|
||||||
|
* - Server Functions: srvstfunc=29, srvdofunc=64, 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: 96,
|
||||||
|
name: "Sacrifice",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 29,
|
||||||
|
srvDoFunc: 64,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-096-sacrifice',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #097: Smite (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 97 ("Smite")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 1
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=150, 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: 97,
|
||||||
|
name: "Smite",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 150,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-097-smite',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #098: Might (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 98 ("Might")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 1
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=65, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="might", 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: 98,
|
||||||
|
name: "Might",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 65,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-098-might',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #099: Prayer (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 99 ("Prayer")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 1
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=65, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="prayer", 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: 99,
|
||||||
|
name: "Prayer",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 65,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-099-prayer',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #100: Resist Fire (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 100 ("Resist Fire")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 1
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=65, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="resistfire", 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: 100,
|
||||||
|
name: "Resist Fire",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 65,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-100-resist-fire',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #101: Holy Bolt (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 101 ("Holy Bolt")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 6
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=0, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="holybolt", state="", summon=""
|
||||||
|
* - 1.13c Synergy Formulas: EDmgSymPerCalc="(skill('Blessed Hammer'.blvl)+skill('Fist of the Heavens'.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: 101,
|
||||||
|
name: "Holy Bolt",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 0,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-101-holy-bolt',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #102: Holy Fire (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 102 ("Holy Fire")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 6
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=66, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="holyfire", summon=""
|
||||||
|
* - 1.13c Synergy Formulas: EDmgSymPerCalc="skill('Resist Fire'.blvl)*par8+skill('Salvation'.blvl)*par7", DmgSymPerCalc="", ELenSymPerCalc=""
|
||||||
|
*/
|
||||||
|
import type {
|
||||||
|
SkillEvalContext,
|
||||||
|
SkillEvalResult,
|
||||||
|
SkillExecContext,
|
||||||
|
SkillExecOutcome,
|
||||||
|
SkillModule,
|
||||||
|
} from '../../types.ts'
|
||||||
|
import { evaluateSkillCore113c, executeSkillCore113c } from '../../registry.ts'
|
||||||
|
|
||||||
|
export const skillModule: SkillModule = {
|
||||||
|
skillId: 102,
|
||||||
|
name: "Holy Fire",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 66,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-102-holy-fire',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #103: Thorns (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 103 ("Thorns")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 6
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=65, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="thorns", 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: 103,
|
||||||
|
name: "Thorns",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 65,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-103-thorns',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #104: Defiance (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 104 ("Defiance")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 6
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=65, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="defiance", 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: 104,
|
||||||
|
name: "Defiance",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 65,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-104-defiance',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #105: Resist Cold (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 105 ("Resist Cold")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 6
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=65, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="resistcold", 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: 105,
|
||||||
|
name: "Resist Cold",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 65,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-105-resist-cold',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #106: Zeal (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 106 ("Zeal")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 12
|
||||||
|
* - Server Functions: srvstfunc=37, srvdofunc=13, 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: 106,
|
||||||
|
name: "Zeal",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 37,
|
||||||
|
srvDoFunc: 13,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-106-zeal',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #107: Charge (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 107 ("Charge")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 12
|
||||||
|
* - Server Functions: srvstfunc=31, srvdofunc=67, 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: 107,
|
||||||
|
name: "Charge",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 31,
|
||||||
|
srvDoFunc: 67,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-107-charge',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #108: Blessed Aim (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 108 ("Blessed Aim")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 12
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=65, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="blessedaim", 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: 108,
|
||||||
|
name: "Blessed Aim",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 65,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-108-blessed-aim',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Diablo II: Lord of Destruction v1.13c — Skill #109: Cleansing (PAL)
|
||||||
|
*
|
||||||
|
* Authoritative 1.13c Ground Truth (`Patch_D2.mpq` -> `data\\global\\excel\\Skills.txt`):
|
||||||
|
* - Skill ID: 109 ("Cleansing")
|
||||||
|
* - Class: "pal"
|
||||||
|
* - Required Level: 12
|
||||||
|
* - Server Functions: srvstfunc=0, srvdofunc=65, srvprgfunc1=0, srvprgfunc2=0, srvprgfunc3=0
|
||||||
|
* - Associated Missile / State / Summon: missile="", state="cleansing", 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: 109,
|
||||||
|
name: "Cleansing",
|
||||||
|
charClass: "pal",
|
||||||
|
srvStFunc: 0,
|
||||||
|
srvDoFunc: 65,
|
||||||
|
|
||||||
|
evaluate(ctx: SkillEvalContext): SkillEvalResult {
|
||||||
|
return evaluateSkillCore113c(ctx)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeDo(ctx: SkillExecContext): SkillExecOutcome {
|
||||||
|
const outcome = executeSkillCore113c(ctx)
|
||||||
|
return {
|
||||||
|
...outcome,
|
||||||
|
notes: [
|
||||||
|
...outcome.notes,
|
||||||
|
'skill_module:skill-109-cleansing',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default skillModule
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #085: BloodGolem (NEC)
|
||||||
|
* Gitea Tracking Issue: #239
|
||||||
|
*/
|
||||||
|
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/nec/skill-085-bloodgolem.ts'
|
||||||
|
|
||||||
|
describe('[Skill #085] BloodGolem (NEC) — 1.13c Parity (Issue #239)', () => {
|
||||||
|
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(85)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(85)
|
||||||
|
expect(skillModule.name).toBe("BloodGolem")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(85, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(25)
|
||||||
|
expect(r1.manaCost256).toBe(6400)
|
||||||
|
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(85, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(61)
|
||||||
|
expect(r10.manaCost256).toBe(15616)
|
||||||
|
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(85, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(101)
|
||||||
|
expect(r20.manaCost256).toBe(25856)
|
||||||
|
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(2925)
|
||||||
|
expect(r20.petMax).toBe(1)
|
||||||
|
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(85)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(85, 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(85, 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: "nec",
|
||||||
|
skillId: 85,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(85)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #086: Attract (NEC)
|
||||||
|
* Gitea Tracking Issue: #240
|
||||||
|
*/
|
||||||
|
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/nec/skill-086-attract.ts'
|
||||||
|
|
||||||
|
describe('[Skill #086] Attract (NEC) — 1.13c Parity (Issue #240)', () => {
|
||||||
|
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(86)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(86)
|
||||||
|
expect(skillModule.name).toBe("Attract")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(86, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(17)
|
||||||
|
expect(r1.manaCost256).toBe(4352)
|
||||||
|
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(86, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(17)
|
||||||
|
expect(r10.manaCost256).toBe(4352)
|
||||||
|
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(86, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(17)
|
||||||
|
expect(r20.manaCost256).toBe(4352)
|
||||||
|
expect(r20.minPhysDmg).toBe(0)
|
||||||
|
expect(r20.maxPhysDmg).toBe(0)
|
||||||
|
expect(r20.minElemDmg256).toBe(0)
|
||||||
|
expect(r20.maxElemDmg256).toBe(0)
|
||||||
|
expect(r20.durationFrames).toBe(2010)
|
||||||
|
expect(r20.radiusSubtiles).toBe(9)
|
||||||
|
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(86)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(86, 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(86, 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: "nec",
|
||||||
|
skillId: 86,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(86)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #087: Decrepify (NEC)
|
||||||
|
* Gitea Tracking Issue: #241
|
||||||
|
*/
|
||||||
|
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/nec/skill-087-decrepify.ts'
|
||||||
|
|
||||||
|
describe('[Skill #087] Decrepify (NEC) — 1.13c Parity (Issue #241)', () => {
|
||||||
|
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(87)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(87)
|
||||||
|
expect(skillModule.name).toBe("Decrepify")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(87, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(11)
|
||||||
|
expect(r1.manaCost256).toBe(2816)
|
||||||
|
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(87, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(11)
|
||||||
|
expect(r10.manaCost256).toBe(2816)
|
||||||
|
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(87, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(11)
|
||||||
|
expect(r20.manaCost256).toBe(2816)
|
||||||
|
expect(r20.minPhysDmg).toBe(0)
|
||||||
|
expect(r20.maxPhysDmg).toBe(0)
|
||||||
|
expect(r20.minElemDmg256).toBe(0)
|
||||||
|
expect(r20.maxElemDmg256).toBe(0)
|
||||||
|
expect(r20.durationFrames).toBe(385)
|
||||||
|
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(87)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(87, 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(87, 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: "nec",
|
||||||
|
skillId: 87,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(87)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #088: Bone Prison (NEC)
|
||||||
|
* Gitea Tracking Issue: #242
|
||||||
|
*/
|
||||||
|
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/nec/skill-088-bone-prison.ts'
|
||||||
|
|
||||||
|
describe('[Skill #088] Bone Prison (NEC) — 1.13c Parity (Issue #242)', () => {
|
||||||
|
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(88)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(88)
|
||||||
|
expect(skillModule.name).toBe("Bone Prison")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(88, 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(0)
|
||||||
|
expect(r1.maxPhysDmg).toBe(0)
|
||||||
|
expect(r1.minElemDmg256).toBe(0)
|
||||||
|
expect(r1.maxElemDmg256).toBe(0)
|
||||||
|
|
||||||
|
const stats10 = new UnitStatList()
|
||||||
|
stats10.setBaseSkillLevel(88, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(18)
|
||||||
|
expect(r10.manaCost256).toBe(4608)
|
||||||
|
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(88, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(8)
|
||||||
|
expect(r20.manaCost256).toBe(2048)
|
||||||
|
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(11425)
|
||||||
|
expect(r20.petMax).toBe(1)
|
||||||
|
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(88)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(88, 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(88, 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: "nec",
|
||||||
|
skillId: 88,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(88)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #089: Summon Resist (NEC)
|
||||||
|
* Gitea Tracking Issue: #243
|
||||||
|
*/
|
||||||
|
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/nec/skill-089-summon-resist.ts'
|
||||||
|
|
||||||
|
describe('[Skill #089] Summon Resist (NEC) — 1.13c Parity (Issue #243)', () => {
|
||||||
|
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(89)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(89)
|
||||||
|
expect(skillModule.name).toBe("Summon Resist")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(89, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(44)
|
||||||
|
expect(r1.manaCost256).toBe(11264)
|
||||||
|
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(89, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(17)
|
||||||
|
expect(r10.manaCost256).toBe(4352)
|
||||||
|
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(89, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(1)
|
||||||
|
expect(r20.manaCost256).toBe(256)
|
||||||
|
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(1445)
|
||||||
|
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(89)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(89, 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(89, 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: "nec",
|
||||||
|
skillId: 89,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(89)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #090: IronGolem (NEC)
|
||||||
|
* Gitea Tracking Issue: #244
|
||||||
|
*/
|
||||||
|
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/nec/skill-090-irongolem.ts'
|
||||||
|
|
||||||
|
describe('[Skill #090] IronGolem (NEC) — 1.13c Parity (Issue #244)', () => {
|
||||||
|
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(90)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(90)
|
||||||
|
expect(skillModule.name).toBe("IronGolem")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(90, 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(90, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(35)
|
||||||
|
expect(r10.manaCost256).toBe(8960)
|
||||||
|
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(90, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(35)
|
||||||
|
expect(r20.manaCost256).toBe(8960)
|
||||||
|
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(435)
|
||||||
|
expect(r20.petMax).toBe(1)
|
||||||
|
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(90)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(90, 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(90, 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: "nec",
|
||||||
|
skillId: 90,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(90)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #091: Lower Resist (NEC)
|
||||||
|
* Gitea Tracking Issue: #245
|
||||||
|
*/
|
||||||
|
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/nec/skill-091-lower-resist.ts'
|
||||||
|
|
||||||
|
describe('[Skill #091] Lower Resist (NEC) — 1.13c Parity (Issue #245)', () => {
|
||||||
|
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(91)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(91)
|
||||||
|
expect(skillModule.name).toBe("Lower Resist")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(91, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(22)
|
||||||
|
expect(r1.manaCost256).toBe(5632)
|
||||||
|
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(91, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(22)
|
||||||
|
expect(r10.manaCost256).toBe(5632)
|
||||||
|
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(91, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(22)
|
||||||
|
expect(r20.manaCost256).toBe(5632)
|
||||||
|
expect(r20.minPhysDmg).toBe(0)
|
||||||
|
expect(r20.maxPhysDmg).toBe(0)
|
||||||
|
expect(r20.minElemDmg256).toBe(0)
|
||||||
|
expect(r20.maxElemDmg256).toBe(0)
|
||||||
|
expect(r20.durationFrames).toBe(1450)
|
||||||
|
expect(r20.radiusSubtiles).toBe(26)
|
||||||
|
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(91)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(91, 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(91, 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: "nec",
|
||||||
|
skillId: 91,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(91)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #092: Poison Nova (NEC)
|
||||||
|
* Gitea Tracking Issue: #246
|
||||||
|
*/
|
||||||
|
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/nec/skill-092-poison-nova.ts'
|
||||||
|
|
||||||
|
describe('[Skill #092] Poison Nova (NEC) — 1.13c Parity (Issue #246)', () => {
|
||||||
|
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(92)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(92)
|
||||||
|
expect(skillModule.name).toBe("Poison Nova")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(92, 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(464)
|
||||||
|
|
||||||
|
const stats10 = new UnitStatList()
|
||||||
|
stats10.setBaseSkillLevel(92, 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(896)
|
||||||
|
expect(r10.maxElemDmg256).toBe(1104)
|
||||||
|
|
||||||
|
const stats20 = new UnitStatList()
|
||||||
|
stats20.setBaseSkillLevel(92, 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(2048)
|
||||||
|
expect(r20.maxElemDmg256).toBe(2256)
|
||||||
|
expect(r20.durationFrames).toBe(50)
|
||||||
|
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(92)!
|
||||||
|
const synId = 73
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(92, 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(92, 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: "nec",
|
||||||
|
skillId: 92,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(92)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #093: Bone Spirit (NEC)
|
||||||
|
* Gitea Tracking Issue: #247
|
||||||
|
*/
|
||||||
|
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/nec/skill-093-bone-spirit.ts'
|
||||||
|
|
||||||
|
describe('[Skill #093] Bone Spirit (NEC) — 1.13c Parity (Issue #247)', () => {
|
||||||
|
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(93)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(93)
|
||||||
|
expect(skillModule.name).toBe("Bone Spirit")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(93, 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(5120)
|
||||||
|
expect(r1.maxElemDmg256).toBe(7680)
|
||||||
|
|
||||||
|
const stats10 = new UnitStatList()
|
||||||
|
stats10.setBaseSkillLevel(93, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(16)
|
||||||
|
expect(r10.manaCost256).toBe(4224)
|
||||||
|
expect(r10.minPhysDmg).toBe(0)
|
||||||
|
expect(r10.maxPhysDmg).toBe(0)
|
||||||
|
expect(r10.minElemDmg256).toBe(42496)
|
||||||
|
expect(r10.maxElemDmg256).toBe(47360)
|
||||||
|
|
||||||
|
const stats20 = new UnitStatList()
|
||||||
|
stats20.setBaseSkillLevel(93, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(21)
|
||||||
|
expect(r20.manaCost256).toBe(5504)
|
||||||
|
expect(r20.minPhysDmg).toBe(0)
|
||||||
|
expect(r20.maxPhysDmg).toBe(0)
|
||||||
|
expect(r20.minElemDmg256).toBe(87040)
|
||||||
|
expect(r20.maxElemDmg256).toBe(94464)
|
||||||
|
expect(r20.durationFrames).toBe(0)
|
||||||
|
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(93)!
|
||||||
|
const synId = 78
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(93, 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(120)
|
||||||
|
|
||||||
|
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
|
||||||
|
const statsSoftOnly = new UnitStatList()
|
||||||
|
statsSoftOnly.setBaseSkillLevel(93, 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: "nec",
|
||||||
|
skillId: 93,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(93)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #094: FireGolem (NEC)
|
||||||
|
* Gitea Tracking Issue: #248
|
||||||
|
*/
|
||||||
|
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/nec/skill-094-firegolem.ts'
|
||||||
|
|
||||||
|
describe('[Skill #094] FireGolem (NEC) — 1.13c Parity (Issue #248)', () => {
|
||||||
|
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(94)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(94)
|
||||||
|
expect(skillModule.name).toBe("FireGolem")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(94, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(50)
|
||||||
|
expect(r1.manaCost256).toBe(12800)
|
||||||
|
expect(r1.minPhysDmg).toBe(0)
|
||||||
|
expect(r1.maxPhysDmg).toBe(0)
|
||||||
|
expect(r1.minElemDmg256).toBe(2560)
|
||||||
|
expect(r1.maxElemDmg256).toBe(6912)
|
||||||
|
|
||||||
|
const stats10 = new UnitStatList()
|
||||||
|
stats10.setBaseSkillLevel(94, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(140)
|
||||||
|
expect(r10.manaCost256).toBe(35840)
|
||||||
|
expect(r10.minPhysDmg).toBe(0)
|
||||||
|
expect(r10.maxPhysDmg).toBe(0)
|
||||||
|
expect(r10.minElemDmg256).toBe(23808)
|
||||||
|
expect(r10.maxElemDmg256).toBe(30464)
|
||||||
|
|
||||||
|
const stats20 = new UnitStatList()
|
||||||
|
stats20.setBaseSkillLevel(94, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(240)
|
||||||
|
expect(r20.manaCost256).toBe(61440)
|
||||||
|
expect(r20.minPhysDmg).toBe(0)
|
||||||
|
expect(r20.maxPhysDmg).toBe(0)
|
||||||
|
expect(r20.minElemDmg256).toBe(50432)
|
||||||
|
expect(r20.maxElemDmg256).toBe(59648)
|
||||||
|
expect(r20.durationFrames).toBe(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(1925)
|
||||||
|
expect(r20.petMax).toBe(1)
|
||||||
|
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(94)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(94, 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(94, 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: "nec",
|
||||||
|
skillId: 94,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(94)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #095: Revive (NEC)
|
||||||
|
* Gitea Tracking Issue: #249
|
||||||
|
*/
|
||||||
|
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/nec/skill-095-revive.ts'
|
||||||
|
|
||||||
|
describe('[Skill #095] Revive (NEC) — 1.13c Parity (Issue #249)', () => {
|
||||||
|
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(95)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(95)
|
||||||
|
expect(skillModule.name).toBe("Revive")
|
||||||
|
expect(skillModule.charClass).toBe("nec")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(95, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(45)
|
||||||
|
expect(r1.manaCost256).toBe(11520)
|
||||||
|
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(95, 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(0)
|
||||||
|
expect(r10.maxPhysDmg).toBe(0)
|
||||||
|
expect(r10.minElemDmg256).toBe(0)
|
||||||
|
expect(r10.maxElemDmg256).toBe(0)
|
||||||
|
|
||||||
|
const stats20 = new UnitStatList()
|
||||||
|
stats20.setBaseSkillLevel(95, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(45)
|
||||||
|
expect(r20.manaCost256).toBe(11520)
|
||||||
|
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(0)
|
||||||
|
expect(r20.petMax).toBe(20)
|
||||||
|
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(95)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(95, 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(95, 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: "nec",
|
||||||
|
skillId: 95,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(95)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #096: Sacrifice (PAL)
|
||||||
|
* Gitea Tracking Issue: #250
|
||||||
|
*/
|
||||||
|
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/pal/skill-096-sacrifice.ts'
|
||||||
|
|
||||||
|
describe('[Skill #096] Sacrifice (PAL) — 1.13c Parity (Issue #250)', () => {
|
||||||
|
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(96)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(96)
|
||||||
|
expect(skillModule.name).toBe("Sacrifice")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(96, 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(96, 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(96, 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(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(465)
|
||||||
|
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(96)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(96, 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(96, 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: "pal",
|
||||||
|
skillId: 96,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(96)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #097: Smite (PAL)
|
||||||
|
* Gitea Tracking Issue: #251
|
||||||
|
*/
|
||||||
|
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/pal/skill-097-smite.ts'
|
||||||
|
|
||||||
|
describe('[Skill #097] Smite (PAL) — 1.13c Parity (Issue #251)', () => {
|
||||||
|
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(97)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(97)
|
||||||
|
expect(skillModule.name).toBe("Smite")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(97, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(2)
|
||||||
|
expect(r1.manaCost256).toBe(512)
|
||||||
|
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(97, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(2)
|
||||||
|
expect(r10.manaCost256).toBe(512)
|
||||||
|
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(97, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(2)
|
||||||
|
expect(r20.manaCost256).toBe(512)
|
||||||
|
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(110)
|
||||||
|
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(97)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(97, 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(97, 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: "pal",
|
||||||
|
skillId: 97,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(97)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #098: Might (PAL)
|
||||||
|
* Gitea Tracking Issue: #252
|
||||||
|
*/
|
||||||
|
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/pal/skill-098-might.ts'
|
||||||
|
|
||||||
|
describe('[Skill #098] Might (PAL) — 1.13c Parity (Issue #252)', () => {
|
||||||
|
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(98)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(98)
|
||||||
|
expect(skillModule.name).toBe("Might")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(98, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(1)
|
||||||
|
expect(r1.manaCost256).toBe(256)
|
||||||
|
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(98, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(1)
|
||||||
|
expect(r10.manaCost256).toBe(256)
|
||||||
|
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(98, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(1)
|
||||||
|
expect(r20.manaCost256).toBe(256)
|
||||||
|
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(54)
|
||||||
|
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(98)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(98, 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(98, 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: "pal",
|
||||||
|
skillId: 98,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(98)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #099: Prayer (PAL)
|
||||||
|
* Gitea Tracking Issue: #253
|
||||||
|
*/
|
||||||
|
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/pal/skill-099-prayer.ts'
|
||||||
|
|
||||||
|
describe('[Skill #099] Prayer (PAL) — 1.13c Parity (Issue #253)', () => {
|
||||||
|
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(99)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(99)
|
||||||
|
expect(skillModule.name).toBe("Prayer")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(99, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(1)
|
||||||
|
expect(r1.manaCost256).toBe(256)
|
||||||
|
expect(r1.minPhysDmg).toBe(0)
|
||||||
|
expect(r1.maxPhysDmg).toBe(0)
|
||||||
|
expect(r1.minElemDmg256).toBe(512)
|
||||||
|
expect(r1.maxElemDmg256).toBe(0)
|
||||||
|
|
||||||
|
const stats10 = new UnitStatList()
|
||||||
|
stats10.setBaseSkillLevel(99, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(2)
|
||||||
|
expect(r10.manaCost256).toBe(688)
|
||||||
|
expect(r10.minPhysDmg).toBe(0)
|
||||||
|
expect(r10.maxPhysDmg).toBe(0)
|
||||||
|
expect(r10.minElemDmg256).toBe(2816)
|
||||||
|
expect(r10.maxElemDmg256).toBe(0)
|
||||||
|
|
||||||
|
const stats20 = new UnitStatList()
|
||||||
|
stats20.setBaseSkillLevel(99, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(4)
|
||||||
|
expect(r20.manaCost256).toBe(1168)
|
||||||
|
expect(r20.minPhysDmg).toBe(0)
|
||||||
|
expect(r20.maxPhysDmg).toBe(0)
|
||||||
|
expect(r20.minElemDmg256).toBe(6400)
|
||||||
|
expect(r20.maxElemDmg256).toBe(0)
|
||||||
|
expect(r20.durationFrames).toBe(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(54)
|
||||||
|
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(99)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(99, 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(99, 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: "pal",
|
||||||
|
skillId: 99,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(99)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #100: Resist Fire (PAL)
|
||||||
|
* Gitea Tracking Issue: #254
|
||||||
|
*/
|
||||||
|
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/pal/skill-100-resist-fire.ts'
|
||||||
|
|
||||||
|
describe('[Skill #100] Resist Fire (PAL) — 1.13c Parity (Issue #254)', () => {
|
||||||
|
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(100)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(100)
|
||||||
|
expect(skillModule.name).toBe("Resist Fire")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(100, 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(100, 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(100, 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(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(54)
|
||||||
|
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(100)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(100, 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(100, 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: "pal",
|
||||||
|
skillId: 100,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(100)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #101: Holy Bolt (PAL)
|
||||||
|
* Gitea Tracking Issue: #255
|
||||||
|
*/
|
||||||
|
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/pal/skill-101-holy-bolt.ts'
|
||||||
|
|
||||||
|
describe('[Skill #101] Holy Bolt (PAL) — 1.13c Parity (Issue #255)', () => {
|
||||||
|
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(101)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(101)
|
||||||
|
expect(skillModule.name).toBe("Holy Bolt")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(101, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(2)
|
||||||
|
expect(r1.manaCost256).toBe(512)
|
||||||
|
expect(r1.minPhysDmg).toBe(0)
|
||||||
|
expect(r1.maxPhysDmg).toBe(0)
|
||||||
|
expect(r1.minElemDmg256).toBe(2048)
|
||||||
|
expect(r1.maxElemDmg256).toBe(4096)
|
||||||
|
|
||||||
|
const stats10 = new UnitStatList()
|
||||||
|
stats10.setBaseSkillLevel(101, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(2)
|
||||||
|
expect(r10.manaCost256).toBe(656)
|
||||||
|
expect(r10.minPhysDmg).toBe(0)
|
||||||
|
expect(r10.maxPhysDmg).toBe(0)
|
||||||
|
expect(r10.minElemDmg256).toBe(21504)
|
||||||
|
expect(r10.maxElemDmg256).toBe(24064)
|
||||||
|
|
||||||
|
const stats20 = new UnitStatList()
|
||||||
|
stats20.setBaseSkillLevel(101, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(3)
|
||||||
|
expect(r20.manaCost256).toBe(816)
|
||||||
|
expect(r20.minPhysDmg).toBe(0)
|
||||||
|
expect(r20.maxPhysDmg).toBe(0)
|
||||||
|
expect(r20.minElemDmg256).toBe(50176)
|
||||||
|
expect(r20.maxElemDmg256).toBe(56320)
|
||||||
|
expect(r20.durationFrames).toBe(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(39)
|
||||||
|
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(101)!
|
||||||
|
const synId = 112
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(101, 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(1000)
|
||||||
|
|
||||||
|
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
|
||||||
|
const statsSoftOnly = new UnitStatList()
|
||||||
|
statsSoftOnly.setBaseSkillLevel(101, 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: "pal",
|
||||||
|
skillId: 101,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(101)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #102: Holy Fire (PAL)
|
||||||
|
* Gitea Tracking Issue: #256
|
||||||
|
*/
|
||||||
|
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/pal/skill-102-holy-fire.ts'
|
||||||
|
|
||||||
|
describe('[Skill #102] Holy Fire (PAL) — 1.13c Parity (Issue #256)', () => {
|
||||||
|
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(102)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(102)
|
||||||
|
expect(skillModule.name).toBe("Holy Fire")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(102, 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(256)
|
||||||
|
expect(r1.maxElemDmg256).toBe(768)
|
||||||
|
|
||||||
|
const stats10 = new UnitStatList()
|
||||||
|
stats10.setBaseSkillLevel(102, 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(1664)
|
||||||
|
expect(r10.maxElemDmg256).toBe(2176)
|
||||||
|
|
||||||
|
const stats20 = new UnitStatList()
|
||||||
|
stats20.setBaseSkillLevel(102, 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(4736)
|
||||||
|
expect(r20.maxElemDmg256).toBe(5248)
|
||||||
|
expect(r20.durationFrames).toBe(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(25)
|
||||||
|
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(102)!
|
||||||
|
const synId = 100
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(102, 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(360)
|
||||||
|
|
||||||
|
// +20 allskills and +33 Marrowwalk charges without hard points must yield 0 synergy
|
||||||
|
const statsSoftOnly = new UnitStatList()
|
||||||
|
statsSoftOnly.setBaseSkillLevel(102, 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: "pal",
|
||||||
|
skillId: 102,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(102)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #103: Thorns (PAL)
|
||||||
|
* Gitea Tracking Issue: #257
|
||||||
|
*/
|
||||||
|
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/pal/skill-103-thorns.ts'
|
||||||
|
|
||||||
|
describe('[Skill #103] Thorns (PAL) — 1.13c Parity (Issue #257)', () => {
|
||||||
|
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(103)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(103)
|
||||||
|
expect(skillModule.name).toBe("Thorns")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(103, 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(103, 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(103, 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(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(54)
|
||||||
|
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(103)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(103, 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(103, 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: "pal",
|
||||||
|
skillId: 103,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(103)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #104: Defiance (PAL)
|
||||||
|
* Gitea Tracking Issue: #258
|
||||||
|
*/
|
||||||
|
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/pal/skill-104-defiance.ts'
|
||||||
|
|
||||||
|
describe('[Skill #104] Defiance (PAL) — 1.13c Parity (Issue #258)', () => {
|
||||||
|
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(104)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(104)
|
||||||
|
expect(skillModule.name).toBe("Defiance")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(104, 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(104, 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(104, 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(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(54)
|
||||||
|
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(104)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(104, 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(104, 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: "pal",
|
||||||
|
skillId: 104,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(104)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #105: Resist Cold (PAL)
|
||||||
|
* Gitea Tracking Issue: #259
|
||||||
|
*/
|
||||||
|
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/pal/skill-105-resist-cold.ts'
|
||||||
|
|
||||||
|
describe('[Skill #105] Resist Cold (PAL) — 1.13c Parity (Issue #259)', () => {
|
||||||
|
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(105)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(105)
|
||||||
|
expect(skillModule.name).toBe("Resist Cold")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(105, 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(105, 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(105, 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(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(54)
|
||||||
|
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(105)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(105, 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(105, 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: "pal",
|
||||||
|
skillId: 105,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(105)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #106: Zeal (PAL)
|
||||||
|
* Gitea Tracking Issue: #260
|
||||||
|
*/
|
||||||
|
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/pal/skill-106-zeal.ts'
|
||||||
|
|
||||||
|
describe('[Skill #106] Zeal (PAL) — 1.13c Parity (Issue #260)', () => {
|
||||||
|
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(106)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(106)
|
||||||
|
expect(skillModule.name).toBe("Zeal")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(106, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(2)
|
||||||
|
expect(r1.manaCost256).toBe(512)
|
||||||
|
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(106, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(2)
|
||||||
|
expect(r10.manaCost256).toBe(512)
|
||||||
|
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(106, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(2)
|
||||||
|
expect(r20.manaCost256).toBe(512)
|
||||||
|
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(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(106)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(106, 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(106, 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: "pal",
|
||||||
|
skillId: 106,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(106)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #107: Charge (PAL)
|
||||||
|
* Gitea Tracking Issue: #261
|
||||||
|
*/
|
||||||
|
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/pal/skill-107-charge.ts'
|
||||||
|
|
||||||
|
describe('[Skill #107] Charge (PAL) — 1.13c Parity (Issue #261)', () => {
|
||||||
|
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(107)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(107)
|
||||||
|
expect(skillModule.name).toBe("Charge")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(107, 1)
|
||||||
|
const r1 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 1, blvl: 1, statList: stats1 })
|
||||||
|
expect(r1.manaCost).toBe(9)
|
||||||
|
expect(r1.manaCost256).toBe(2304)
|
||||||
|
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(107, 10)
|
||||||
|
const r10 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 10, blvl: 10, statList: stats10 })
|
||||||
|
expect(r10.manaCost).toBe(9)
|
||||||
|
expect(r10.manaCost256).toBe(2304)
|
||||||
|
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(107, 20)
|
||||||
|
const r20 = skillModule.evaluate!({ registry, skill: skillRec, slvl: 20, blvl: 20, statList: stats20 })
|
||||||
|
expect(r20.manaCost).toBe(9)
|
||||||
|
expect(r20.manaCost256).toBe(2304)
|
||||||
|
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(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(107)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(107, 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(107, 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: "pal",
|
||||||
|
skillId: 107,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(107)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #108: Blessed Aim (PAL)
|
||||||
|
* Gitea Tracking Issue: #262
|
||||||
|
*/
|
||||||
|
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/pal/skill-108-blessed-aim.ts'
|
||||||
|
|
||||||
|
describe('[Skill #108] Blessed Aim (PAL) — 1.13c Parity (Issue #262)', () => {
|
||||||
|
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(108)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(108)
|
||||||
|
expect(skillModule.name).toBe("Blessed Aim")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(108, 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(108, 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(108, 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(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(54)
|
||||||
|
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(108)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(108, 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(108, 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: "pal",
|
||||||
|
skillId: 108,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(108)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 1.13c Numerical & Runtime Verification Suite — Skill #109: Cleansing (PAL)
|
||||||
|
* Gitea Tracking Issue: #263
|
||||||
|
*/
|
||||||
|
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/pal/skill-109-cleansing.ts'
|
||||||
|
|
||||||
|
describe('[Skill #109] Cleansing (PAL) — 1.13c Parity (Issue #263)', () => {
|
||||||
|
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(109)!
|
||||||
|
expect(skillRec).toBeDefined()
|
||||||
|
expect(skillModule.skillId).toBe(109)
|
||||||
|
expect(skillModule.name).toBe("Cleansing")
|
||||||
|
expect(skillModule.charClass).toBe("pal")
|
||||||
|
|
||||||
|
const stats1 = new UnitStatList()
|
||||||
|
stats1.setBaseSkillLevel(109, 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(109, 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(109, 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(0)
|
||||||
|
expect(r20.radiusSubtiles).toBe(54)
|
||||||
|
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(109)!
|
||||||
|
const synId = null
|
||||||
|
|
||||||
|
const statsBase = new UnitStatList()
|
||||||
|
statsBase.setBaseSkillLevel(109, 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(109, 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: "pal",
|
||||||
|
skillId: 109,
|
||||||
|
slvl: 20,
|
||||||
|
})
|
||||||
|
const outcome = arena.triggerCast()
|
||||||
|
arena.stepTicks(15)
|
||||||
|
const dbg = arena.getDebugState()
|
||||||
|
|
||||||
|
expect(dbg.ready).toBe(true)
|
||||||
|
expect(dbg.skillId).toBe(109)
|
||||||
|
expect(dbg.castCount).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(dbg.actionFrameTriggered).toBe(true)
|
||||||
|
expect(outcome?.executed).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue