User directive: 'update the plan. v14 only'. Implementation: **scope change (Foundry v14 only):** - registered-hooks.js: renderChatInput entry drops the v13 renderChatLog name. Single subscription to the v14 name. - anti-corruption.js: combatRound arg-normalization no longer detects the v13 round-num position. v14's updateOptions.round is the only path. Removed the v13 comments from the other arg shapes (combatEnd, combatTurn). - module.json: compatibility.minimum is now 14 (was 13). verified stays 14. - package.json: version 0.3.0 -> 0.4.0 (semver-breaking: dropping v13 support is a breaking change for v13 consumers). - package.json description: 'Foundry VTT v14-only module' prefix. **test plan:** - tests/PLAN.md: v14-only scope documented at the top of the file and in Section E. Status line bumps 554 to 546 assertions (v13-only assertions dropped). Test files table re-scoped to v0.4.0. - tests/verify-hooks-lib.mjs: dropped the v13-only assertions (E.2 'renderChatLog in installed', E.3's 'both v13 and v14 produce' check, E.4's v13 round shape). Kept the v14-only assertions + added an inverse assertion: 'renderChatLog is NOT in installed raw hooks' to lock the v14-only scope. - tests/verify-hooks-lib.mjs: stub table at line ~520 drops renderChatLog (dead in production now). **doc updates:** - README.md: new 'v0.4.0 — Foundry v14 only' section explaining the change + migration note for v13 consumers. - docs/HOOK_CONTRACT.md: v0.3.0 header. §9 marks the v13 column as historical. §10 example uses the v14 shape only. **artifact:** - foundry-hooks-lib-0.4.0.zip built (82KB, 46 entries, inner version 0.4.0, inner compatibility.minimum 14). **verified:** - npm test: 546/546 assertions passed - npm run test:foundry: 30/30 assertions passed - npm run test:perf: 6/6 assertions passed (median 0.0003ms/fire) - battle-focus E2E (consumer): 125/125 still green - its-achievable smoke (consumer): 75/75 still green **consumer follow-up (separate commits in their own repos):** - battle-focus/module.json: relationships.requires[0].version bumped to ^0.4.0 - its-achievable/module.json: relationships.requires[0].minimum bumped to 0.4.0
98 lines
3.8 KiB
JavaScript
98 lines
3.8 KiB
JavaScript
// scripts/internal/anti-corruption.js
|
|
//
|
|
// HOOK_CONTRACT.md §9-§10: absorb Foundry hook shape churn so
|
|
// consumers see stable envelope.hook names and a documented
|
|
// args[] shape.
|
|
//
|
|
// v0.3.0 is Foundry v14 only (per tests/PLAN.md file header). v0.2.0
|
|
// supported both v13 and v14; v0.3.0 narrows to v14. The dual v13
|
|
// `renderChatLog` listener is dropped, and arg-normalization shapes
|
|
// assume v14's arity. If a future v14 micro-release renames a hook
|
|
// again, add the mapping here.
|
|
//
|
|
// §9 Hook rename mapping: the envelope.hook name is always the v14
|
|
// name. v13 mapping is out of scope.
|
|
//
|
|
// §10 Arg normalization: for hooks with unstable arity, pad/truncate
|
|
// to a documented shape. Consumers can rely on args[N].
|
|
//
|
|
// This module exports the *normalization functions* and the
|
|
// per-hook arg shape definitions. The dispatcher in envelope.js
|
|
// calls these for every fire.
|
|
|
|
import { getEntryForRawName } from "./registered-hooks.js";
|
|
|
|
// Arg shape per envelope name. undefined = no normalization
|
|
// (the args array is passed verbatim from Foundry).
|
|
//
|
|
// Each shape is a function (rawArgs) -> normalizedArgs.
|
|
//
|
|
// v0.3.0 documents shapes for: combatStart, combatEnd, combatTurn,
|
|
// combatRound, preUpdateActor, updateActor, preUpdateToken,
|
|
// updateToken, dnd5e.rollAttackV2, dnd5e.rollDamageV2.
|
|
|
|
export const ARG_SHAPES = {
|
|
// combatStart(combat, updateData) — arity 2. Stable in v14.
|
|
combatStart: (args) => normalizeArity(args, 2),
|
|
|
|
// combatEnd(combat) — arity 1. Stable in v14.
|
|
combatEnd: (args) => normalizeArity(args, 2),
|
|
|
|
// combatTurn(combat, updateData, updateOptions) — arity 3. Stable in v14.
|
|
combatTurn: (args) => normalizeArity(args, 3),
|
|
|
|
// combatRound(combat, updateData, updateOptions) — arity 3 in v14.
|
|
// Normalized to 4 args: [combat, updateData, updateOptions.round, updateOptions]
|
|
// The third arg is always the round number; the fourth is options.
|
|
// v13's (combat, updateData, roundNum) shape is out of scope.
|
|
combatRound: (args) => {
|
|
const out = [args[0], args[1], null, args[2] ?? null];
|
|
if (args.length >= 3 && args[2] && typeof args[2] === "object") {
|
|
out[2] = args[2].round ?? null;
|
|
}
|
|
return out;
|
|
},
|
|
|
|
// preUpdateActor(actor, updateData, options, userId) — arity 4.
|
|
preUpdateActor: (args) => normalizeArity(args, 4),
|
|
|
|
// updateActor(actor, updateData, options, userId) — arity 4.
|
|
updateActor: (args) => normalizeArity(args, 4),
|
|
|
|
// preUpdateToken(token, updateData, options, userId) — arity 4.
|
|
preUpdateToken: (args) => normalizeArity(args, 4),
|
|
|
|
// updateToken(token, updateData, options, userId) — arity 4.
|
|
updateToken: (args) => normalizeArity(args, 4),
|
|
|
|
// dnd5e.rollAttackV2(rolls, { subject, ammoUpdate }) — arity 2.
|
|
"dnd5e.rollAttackV2": (args) => normalizeArity(args, 2),
|
|
|
|
// dnd5e.rollDamageV2(rolls, { subject }) — arity 2.
|
|
"dnd5e.rollDamageV2": (args) => normalizeArity(args, 2),
|
|
};
|
|
|
|
function normalizeArity(args, target) {
|
|
const out = new Array(target).fill(undefined);
|
|
for (let i = 0; i < Math.min(args.length, target); i++) {
|
|
out[i] = args[i];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// §9 anti-corruption: synthesize combatInactive from updateCombat
|
|
// when active flips true→false. The synthesized envelope has
|
|
// hook === "combatInactive" and args === [combat].
|
|
//
|
|
// Returns an array of envelopes to emit (usually 1, possibly 2 if
|
|
// both updateCombat and combatInactive fire for the same event).
|
|
export function maybeSynthesize(rawHookName, args) {
|
|
if (rawHookName !== "updateCombat") return null;
|
|
const [combat, updateData] = args;
|
|
if (!combat || !updateData) return null;
|
|
if (!("active" in updateData)) return null;
|
|
if (updateData.active !== false) return null;
|
|
// combatInactive envelope: { ts, hook: "combatInactive", args: [combat] }
|
|
// ts is set by the envelope builder, not here. We return the partial.
|
|
return [{ hook: "combatInactive", args: [combat] }];
|
|
} |