# PF1 Tracking Ledger: Damage Source Logging This document describes how the tracking-ledger module captures detailed HP change metadata (damage source, damage type, encounter ID, etc.) so the ledger can show where every hit point delta came from. ## Overview The module instruments every HP update by: 1. Wrapping the PF1 system's `ActorPF.applyDamage` method to intercept the combat context (chat card, damage/healing roll, target list, action name). 2. Buffering the captured metadata (source label, type, encounter) in a temporary cache keyed by actor ID. 3. Listening to the `updateActor` hook for the specific actor, detecting HP deltas, and consuming the buffered metadata while persisting it into the world flag (`flags.world.pf1HpHistory`). This preserves full provenance of each HP change without requiring upstream system modifications. ## Hooking `ActorPF.applyDamage` ```js const ActorPF = pf1?.documents?.actor?.ActorPF; const original = ActorPF.applyDamage; ActorPF.applyDamage = async function wrappedApplyDamage(value, options = {}) { try { noteDamageSource(value, options); } catch (err) { console.warn("Tracking Ledger | Failed to record damage source", err); } return original.call(this, value, options); }; ``` * Run once, guarded to avoid double wrapping. * `value` is the delta applied to each target (negative for damage, positive for healing); `options` carries metadata from PF1's roll flow. ## Recording the source details ```js function noteDamageSource(value, options) { const actors = resolveActorTargets(options?.targets); if (!actors.length) return; const label = buildSourceLabel(value, options); if (!label) return; for (const actor of actors) { trackingState.sources.set(actor.id, { label, ts: Date.now() }); } } ``` * `resolveActorTargets` converts the PF1 target collection (tokens or actors) into concrete `Actor` instances. * `buildSourceLabel` inspects `options.message.flags.pf1` (identified action info, metadata, flavor text) to construct a human-readable source such as `"Goblin -> Scimitar Slash"` or `"Manual healing"`. * The resulting label is cached in `trackingState.sources` until the corresponding HP delta is observed on that actor. ### Source label construction Order of precedence when building the label: 1. `options.message.flags.pf1.identifiedInfo` (action name, item name). 2. Chat card metadata (`flags.pf1.metadata.actor` / `.item`) to resolve the actor+item pair responsible. 3. Chat card flavor text (`message.flavor`). 4. Fallbacks: `"Healing"` or `"Damage"` depending on delta sign. This ensures we always have some context, even for custom macros. ## Consuming the buffered metadata Inside the `updateActor` hook, every time `system.attributes.hp.value` changes, we: ```js const diff = newValue - previous; const label = consumeDamageSource(actor.id) ?? inferManualSource(diff); const entry = { timestamp: Date.now(), hp: newValue, diff: diff >= 0 ? `+${diff}` : `${diff}`, user: game.users.get(userId)?.name ?? "System", source: label ?? "", }; ``` * `consumeDamageSource` pulls (and deletes) the cached metadata for that actor if present. * `inferManualSource` supplies `"Manual healing"` or `"Manual damage"` when the HP change didn't originate from a wrapped `applyDamage` call (e.g., direct sheet edits). * The entry is stored in `flags.world.pf1HpHistory` with a max history size of 50 rows. ## Encounter tagging Additionally, every recorded entrée is tagged with the active encounter: ```js entry.encounterId = game.combats?.active?.id ?? null; ``` `updateEncounterSummary` maintains `flags.world.pf1EncounterHistory`, aggregating: * Encounter ID * Start/end timestamps * Participants (actor names captured per HP/XP change) * XP gained per actor (sum of positive XP deltas) The history dialog exposes this as a dedicated "Encounters" tab that updates in real time as HP/XP changes occur within the same combat. ## Summary * **Damage source detection**: Done by wrapping `ActorPF.applyDamage` to capture chat metadata. * **Storage**: Cached per-actor, consumed when HP changes fire in `updateActor`, saved to `flags.world.pf1HpHistory`. * **Encounter tracking**: Each HP/XP change is tagged with `game.combats.active.id` and aggregated into `flags.world.pf1EncounterHistory`. This instrumentation gives the ledger full visibility into who dealt which damage (and why), while remaining self-contained in the module.