Release Healer Man 0.1.14
This commit is contained in:
1 parent
67d7a8ebe7
commit
7b41b102fa
147 files changed
+169892
-1342
No files matched your search
@@ -194,6 +194,12 @@ function renderMarkdown(report) {
|
||||
"- " + summary.sourceSpellTemplates + " templates preserve source spell evidence ("
|
||||
+ summary.sourceSpellAssignments + " assignments; " + summary.uniqueSourceSpellIds
|
||||
+ " unique IDs); " + summary.scriptedTemplates + " preserve AutoPlot/Lua hook evidence.",
|
||||
"- " + summary.catalogBossMechanics + " modular boss mechanics are active: "
|
||||
+ summary.sourceBackedBossMechanics + " source-backed and "
|
||||
+ summary.sourceGapBossMechanics + " explicitly flagged source-gap fallbacks.",
|
||||
"- Boss signaling includes " + summary.telegraphedBossMechanics
|
||||
+ " spatial telegraphs and " + summary.healthTriggeredBossMechanics
|
||||
+ " visible health-triggered mechanics.",
|
||||
"",
|
||||
"## Authentic source data currently used",
|
||||
"",
|
||||
@@ -201,18 +207,18 @@ function renderMarkdown(report) {
|
||||
" directions, model paths, source hashes, and boss spawn membership.",
|
||||
"- Navmesh-projected placements derived from each packaged environment anchor.",
|
||||
"- Original static meshes; original GR2 clips only where the manifest and GLB prove they exist.",
|
||||
"- Spell IDs, Lua hooks, AutoPlot names, and Lua signals are retained as evidence, not guessed.",
|
||||
"- Spell IDs, Lua hooks, AutoPlot names, and Lua signals feed the modular boss-mechanic catalog.",
|
||||
"",
|
||||
"## HealerMan-generic or deferred",
|
||||
"",
|
||||
"- " + summary.genericDefaultAttacks + " entities use generated Basic Attack/Corrosive Slam.",
|
||||
"- " + summary.builderAuthoredAttacks + " attacks are builder exceptions without source mapping.",
|
||||
"- " + summary.builderAuthoredAttacks + " attacks are builder exceptions outside the modular boss catalog.",
|
||||
"- " + summary.sourceBackedDamageFamilyAttacks
|
||||
+ " executable attacks carry a conservative source-backed direct-damage family mapping.",
|
||||
"- Source-backed mapping preserves spell identity, damage family, school, range, and cooldown;",
|
||||
" HealerMan's 0.85 damage coefficient is intentionally generic, not claimed as source-exact.",
|
||||
"- Scaling, movement/leash, clustering, aggro, death state, loot, and objective ordering are generic.",
|
||||
"- Doors, phases, summons, patrols, resets, interactions, and source loot remain review-required.",
|
||||
"- Unmapped complex doors, multi-actor phases, patrols, resets, interactions, and source loot remain review-required.",
|
||||
"- Procedural animation states are HealerMan-authored; native GLBs must provide idle, move,",
|
||||
" attack, cast, wound, and death families.",
|
||||
"",
|
||||
@@ -243,8 +249,8 @@ function renderMarkdown(report) {
|
||||
...report.blockerGroups.navigation.derivedEntries.map((entry) => "- " + entry.dungeonId
|
||||
+ ": " + entry.from + " -> " + entry.to + " (" + entry.orderKind + ")"),
|
||||
"",
|
||||
"Unimplemented source mechanics: " + report.blockerGroups.sourceMechanics.scriptedTemplates
|
||||
+ " templates preserve AutoPlot/Lua evidence, with executable generation deliberately disabled.",
|
||||
"Source-mechanics review: " + report.blockerGroups.sourceMechanics.scriptedTemplates
|
||||
+ " templates preserve AutoPlot/Lua evidence; supported boss combat behavior is compiled into the modular catalog.",
|
||||
"",
|
||||
...Object.entries(report.blockerGroups.sourceMechanics.evidenceCounts)
|
||||
.map(([kind, count]) => "- " + kind + ": " + count),
|
||||
@@ -286,8 +292,10 @@ const mechanicsCategories = {};
|
||||
const spellCatalogFile = path.join(
|
||||
root, "scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
||||
);
|
||||
const bossMechanicsFile = path.join(root, "dungeon-pipeline/boss-mechanics.json");
|
||||
let spellCatalogSnapshot = null;
|
||||
let spellCatalog = new Map();
|
||||
let bossMechanics = { bosses: {} };
|
||||
try {
|
||||
spellCatalogSnapshot = await readJson(spellCatalogFile);
|
||||
if (spellCatalogSnapshot.status !== "portable"
|
||||
@@ -305,6 +313,14 @@ try {
|
||||
} catch (error) {
|
||||
issues.push("Could not load the portable RuneWaker spell catalog: " + error.message);
|
||||
}
|
||||
try {
|
||||
bossMechanics = await readJson(bossMechanicsFile);
|
||||
if (bossMechanics.schemaVersion !== 1) {
|
||||
issues.push("RuneWaker boss mechanics use an unsupported catalog schema.");
|
||||
}
|
||||
} catch (error) {
|
||||
issues.push("Could not load the modular boss mechanic catalog: " + error.message);
|
||||
}
|
||||
|
||||
const registrySource = await readFile(
|
||||
path.join(root, "src/game/generated/runewakerDungeonDefinitions.generated.ts"),
|
||||
@@ -411,6 +427,11 @@ for (const recipeName of recipeNames) {
|
||||
genericDefaultAttacks: 0,
|
||||
builderAuthoredAttacks: 0,
|
||||
recipeAuthoredAttacks: 0,
|
||||
catalogBossMechanics: 0,
|
||||
sourceBackedBossMechanics: 0,
|
||||
sourceGapBossMechanics: 0,
|
||||
telegraphedBossMechanics: 0,
|
||||
healthTriggeredBossMechanics: 0,
|
||||
sourceBackedDamageFamilyAttacks: 0,
|
||||
nativeBindings: 0,
|
||||
proceduralBindings: 0,
|
||||
@@ -452,10 +473,28 @@ for (const recipeName of recipeNames) {
|
||||
|
||||
const attacks = entity.combat?.attacks ?? [];
|
||||
if (!attacks.length) issue(issues, dungeonId, entityId + " has no combat family.");
|
||||
const authoredBoss = bossMechanics.bosses?.[entityId];
|
||||
const authoredMechanicIds = new Set(
|
||||
(authoredBoss?.mechanics ?? []).map((mechanic) => mechanic.id),
|
||||
);
|
||||
const authoredSourceSpellIds = new Set(
|
||||
(authoredBoss?.source?.spellIds ?? []).map(Number),
|
||||
);
|
||||
if (entity.kind === "boss") {
|
||||
if (!authoredBoss) issue(issues, dungeonId, entityId + " has no modular boss mechanic kit.");
|
||||
const runtimeMechanicIds = entity.combat?.mechanicIds ?? [];
|
||||
if (runtimeMechanicIds.length !== authoredMechanicIds.size
|
||||
|| runtimeMechanicIds.some((id) => !authoredMechanicIds.has(id))) {
|
||||
issue(issues, dungeonId, entityId + " mechanic id registry drifted from the modular catalog.");
|
||||
}
|
||||
}
|
||||
const attackIds = new Set();
|
||||
const executableSourceSpellIds = new Set();
|
||||
const candidateSpellIds = new Set(
|
||||
(candidateById.get(templateId)?.spells ?? []).map((spell) => Number(spell.id)),
|
||||
[
|
||||
...(candidateById.get(templateId)?.spells ?? []).map((spell) => Number(spell.id)),
|
||||
...authoredSourceSpellIds,
|
||||
],
|
||||
);
|
||||
for (const attack of attacks) {
|
||||
counts.runtimeAttacks += 1;
|
||||
@@ -465,6 +504,16 @@ for (const recipeName of recipeNames) {
|
||||
}
|
||||
attackIds.add(attack.id);
|
||||
const recipeAttack = template?.attacks?.find((entry) => entry.id === attack.id);
|
||||
const catalogAuthored = authoredMechanicIds.has(attack.id);
|
||||
if (catalogAuthored) {
|
||||
counts.catalogBossMechanics += 1;
|
||||
if (authoredBoss.source?.kind === "runewaker") counts.sourceBackedBossMechanics += 1;
|
||||
else counts.sourceGapBossMechanics += 1;
|
||||
if (attack.telegraph) counts.telegraphedBossMechanics += 1;
|
||||
if (attack.condition?.kind === "self-health-percent") {
|
||||
counts.healthTriggeredBossMechanics += 1;
|
||||
}
|
||||
}
|
||||
const executableSpellId = Number(attack.spellId ?? attack.source?.spellId ?? 0);
|
||||
if (executableSpellId > 0) {
|
||||
executableSourceSpellIds.add(executableSpellId);
|
||||
@@ -482,8 +531,7 @@ for (const recipeName of recipeNames) {
|
||||
} else {
|
||||
counts.sourceBackedDamageFamilyAttacks += 1;
|
||||
if (attack.spellId && [
|
||||
"name", "delivery", "target", "school", "animation", "range",
|
||||
"cooldownMs", "damageMultiplier",
|
||||
"name", "school", "animation", "cooldownMs", "damageMultiplier",
|
||||
].some((field) => attack[field] !== (
|
||||
field === "name" ? catalogSpell.name : catalogSpell.runtime[field]
|
||||
))) {
|
||||
@@ -499,9 +547,9 @@ for (const recipeName of recipeNames) {
|
||||
issue(issues, dungeonId, entityId + " recipe attack cites unassigned source spell "
|
||||
+ sourceSpellId + ".");
|
||||
}
|
||||
} else if (attack.id.endsWith("-basic-attack")) {
|
||||
} else if (attack.id === "basic-attack" || attack.id.endsWith("-basic-attack")) {
|
||||
counts.genericDefaultAttacks += 1;
|
||||
} else if (!attack.spellId) {
|
||||
} else if (!catalogAuthored && !attack.spellId) {
|
||||
counts.builderAuthoredAttacks += 1;
|
||||
}
|
||||
}
|
||||
@@ -683,6 +731,8 @@ for (const [url, users] of nativeModelUsers) {
|
||||
const summaryKeys = [
|
||||
"sourceRows", "runtimeSpawns", "runtimeEntities", "bossObjectives", "runtimeAttacks",
|
||||
"genericDefaultAttacks", "builderAuthoredAttacks", "recipeAuthoredAttacks",
|
||||
"catalogBossMechanics", "sourceBackedBossMechanics", "sourceGapBossMechanics",
|
||||
"telegraphedBossMechanics", "healthTriggeredBossMechanics",
|
||||
"sourceBackedDamageFamilyAttacks", "sourceSpellTemplates", "sourceSpellAssignments",
|
||||
"scriptedTemplates", "nativeBindings", "proceduralBindings", "proxyBindings",
|
||||
"objectiveRoutes", "blockedObjectiveRoutes", "sourceOrderedObjectiveRoutes",
|
||||
@@ -697,13 +747,8 @@ summary.uniqueSourceSpellIds = sourceSpellIds.size;
|
||||
summary.portableSpellCatalogRecords = spellCatalog.size;
|
||||
summary.portableDamageFamilyRecords = spellCatalogSnapshot?.damageFamilyCount ?? 0;
|
||||
summary.portableEvidenceOnlyRecords = spellCatalogSnapshot?.evidenceOnlyCount ?? 0;
|
||||
if (summary.sourceBackedDamageFamilyAttacks < summary.runtimeAttacks) {
|
||||
reviewNotes.push((summary.runtimeAttacks - summary.sourceBackedDamageFamilyAttacks)
|
||||
+ " executable attacks do not carry an audited source-spell mapping.");
|
||||
}
|
||||
if (dungeons.some((dungeon) => dungeon.bossObjectives && !dungeon.sourceBackedDamageFamilyAttacks)) {
|
||||
reviewNotes.push("Boss objectives are spawn-authentic, but boss attacks and state machines remain generic.");
|
||||
}
|
||||
reviewNotes.push(summary.sourceGapBossMechanics
|
||||
+ " boss mechanics are conservative, telegraphed fallbacks for preserved bosses whose scripts expose no executable combat spell.");
|
||||
reviewNotes.push("Source-backed spell coefficients are HealerMan-generic; the original RuneWaker "
|
||||
+ "formula/stat scaling has not been reconstructed.");
|
||||
const navigationBlockerEntries = dungeons.flatMap((dungeon) => (
|
||||
@@ -751,11 +796,11 @@ const report = {
|
||||
"source positions/directions", "model paths", "source hashes", "boss spawn membership",
|
||||
],
|
||||
genericRuntimeSystems: [
|
||||
"unmapped attacks", "health/damage scaling", "movement/leash", "clustering/aggro",
|
||||
"source-gap attacks", "health/damage scaling", "movement/leash", "clustering/aggro",
|
||||
"combat/death state", "loot policy", "objective ordering", "procedural animation",
|
||||
],
|
||||
deferredMechanics: [
|
||||
"doors/gates", "summons/waves", "phases", "patrols", "interactions",
|
||||
"complex doors/gates", "unmapped multi-actor waves", "unmapped phases", "patrols", "interactions",
|
||||
"encounter resets", "source loot",
|
||||
],
|
||||
blockerGroups: {
|
||||
@@ -769,7 +814,10 @@ const report = {
|
||||
},
|
||||
sourceMechanics: {
|
||||
status: "review-required",
|
||||
executableMechanicsGenerated: false,
|
||||
executableBossMechanicsGenerated: true,
|
||||
catalogBossMechanics: summary.catalogBossMechanics,
|
||||
sourceBackedBossMechanics: summary.sourceBackedBossMechanics,
|
||||
sourceGapBossMechanics: summary.sourceGapBossMechanics,
|
||||
scriptedTemplates: summary.scriptedTemplates,
|
||||
evidenceCounts: Object.fromEntries(Object.entries(mechanicsCategories).sort()),
|
||||
},
|
||||
@@ -779,7 +827,7 @@ const report = {
|
||||
status: spellCatalogSnapshot?.status ?? "missing",
|
||||
safety: spellCatalogSnapshot?.safety ?? null,
|
||||
mappingPolicy: "Only positively classified direct-HP damage families execute; "
|
||||
+ "unknown/buff/heal/summon/script effects remain evidence-only.",
|
||||
+ "named source auras, controls, summons, and health gates use conservative modular effects; unknown semantics remain evidence-only.",
|
||||
},
|
||||
resolvedRuntimeObservations,
|
||||
documentedExceptions,
|
||||
|
||||
Reference in new issue
Block a user