Files
healer-man/scripts/weapon-evidence-recorder.cjs
2026-10-06 22:25:34 -04:00

80 lines
3.5 KiB
JavaScript

// This self-contained factory also runs in the browser's QA context. It only
// subscribes to committed state; it never writes the game stores.
function createWeaponEvidenceRecorder(store, options = {}) {
const capacity = options.capacity ?? 50_000;
const clock = options.clock ?? Date.now;
const initial = store.getState();
const baselineEventId = initial.combatEvents?.at(-1)?.id ?? 0;
let lastEventId = baselineEventId;
let stopped = false;
const events = [], gaps = [], mobSamples = [], auraSamples = [], errors = [];
const signatures = new Map();
let auraSignature = null;
let discardedEvents = 0, discardedStates = 0;
const retain = (array, entry, event = false) => {
if (array.length < capacity) array.push(entry);
else if (event) discardedEvents += 1;
else discardedStates += 1;
};
const observe = (state) => {
if (stopped) return;
try {
const fresh = (state.combatEvents ?? []).filter((event) => event.id > lastEventId);
for (const event of fresh) {
if (event.id > lastEventId + 1) gaps.push({ afterId: lastEventId, beforeId: event.id });
retain(events, { ...event }, true);
lastEventId = event.id;
}
const observedAt = clock();
for (const mob of Object.values(state.mobs ?? {})) {
const entry = {
id: mob.id, name: mob.name, serverEntry: mob.serverEntry ?? null,
sourceCreatureGuid: mob.sourceCreatureGuid ?? null,
sourceEquipment: mob.sourceEquipment ? JSON.parse(JSON.stringify(mob.sourceEquipment)) : null,
engaged: Boolean(mob.engaged), dead: Boolean(mob.dead),
dualWieldEnabled: mob.dualWieldEnabled ?? null,
nextEnemyOffhandAttackAt: mob.nextEnemyOffhandAttackAt ?? null,
pendingExtraAttacks: mob.pendingExtraAttacks?.map((queued) => ({ ...queued })) ?? null,
lastDamagedActorId: mob.lastDamagedActorId ?? null,
};
const signature = JSON.stringify(entry);
if (signatures.get(mob.id) !== signature) {
signatures.set(mob.id, signature);
retain(mobSamples, { observedAt, ...entry });
}
}
const active = (state.auras ?? []).filter((aura) =>
/^(?:enemy-source-spell-(?:6742|6713|17230|3419)|enemy-control:source-spell-(?:5246|19134|20511|29544))$/.test(aura.definition?.id ?? '')).map((aura) => ({
instanceId: aura.instanceId, definitionId: aura.definition.id,
sourceId: aura.sourceId, targetId: aura.targetId,
appliedAt: aura.appliedAt, expiresAt: aura.expiresAt, stacks: aura.stacks,
dispelCategory: aura.definition.dispelCategory ?? null,
}));
const nextAuraSignature = JSON.stringify(active);
if (nextAuraSignature !== auraSignature) {
auraSignature = nextAuraSignature;
retain(auraSamples, { observedAt, auras: active });
}
} catch (error) {
if (errors.length < 10) errors.push(String(error?.message ?? error));
}
};
observe(initial);
const unsubscribe = store.subscribe(observe);
return {
stop() {
if (!stopped) {
observe(store.getState());
stopped = true;
unsubscribe();
}
return { baselineEventId, lastEventId, events, gaps, mobSamples, auraSamples,
discardedEvents, discardedStates, errors,
eventCoverage: 'Committed-store subscription with event-ID gap auditing; pre-start events excluded.',
stateCoverage: 'Weapon and source-aura changes at store commits, including combat between route checkpoints.' };
},
};
}
module.exports = { createWeaponEvidenceRecorder };