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

39 lines
2.2 KiB
JavaScript

/** Read-only evidence projection. Removed/refreshed auras end the observed window. */
function summarizeActorDisarmEvidence(events, auraSamples, playerId, partyIds = []) {
const actorIds = new Set([playerId, ...partyIds]);
const windows = [], open = new Map();
const keyFor = (aura) => JSON.stringify([aura.instanceId, aura.sourceId, aura.targetId, aura.appliedAt]);
const samples = [...auraSamples].sort((a, b) => a.observedAt - b.observedAt);
for (const sample of samples) {
const auras = (sample.auras ?? []).filter((aura) => aura.definitionId === 'enemy-source-spell-6713'
&& actorIds.has(aura.targetId) && Number.isFinite(aura.appliedAt));
const keys = new Set(auras.map(keyFor));
for (const [key, window] of open) {
if (keys.has(key)) continue;
const replacement = auras.find((aura) => aura.instanceId === window.instanceId && aura.targetId === window.targetId);
const removalAt = Number.isFinite(replacement?.appliedAt) ? replacement.appliedAt : sample.observedAt;
window.observedEndAt = Math.min(window.expiresAt ?? Number.POSITIVE_INFINITY, removalAt);
window.endEvidence = replacement ? 'refresh' : 'committed-removal';
open.delete(key);
}
for (const aura of auras) {
const key = keyFor(aura);
if (open.has(key)) continue;
const window = { ...aura, observedEndAt: aura.expiresAt ?? null, endEvidence: 'source-expiry' };
windows.push(window);
open.set(key, window);
}
}
const actorHandEvents = events.filter((event) => event.kind === 'damage'
&& actorIds.has(event.sourceActorId) && event.attackHand != null);
const disarmWindows = windows.map((window) => ({ ...window,
handEvents: actorHandEvents.filter((event) => event.sourceActorId === window.targetId
&& event.occurredAt >= window.appliedAt
&& window.observedEndAt !== null && event.occurredAt < window.observedEndAt),
}));
return { actorHandEvents, disarmWindows,
coverage: 'Hand-tagged player/party damage results matched to committed source Disarm windows; removal/refresh ends a window. No event proves an unsampled cast gate, damage formula, or missing hand.' };
}
module.exports = { summarizeActorDisarmEvidence };