@axiapps/axilog 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +169 -21
  2. package/index.d.ts +37 -18
  3. package/index.js +54 -54
  4. package/package.json +6 -6
  5. package/types.d.ts +1281 -0
package/types.d.ts CHANGED
@@ -58,6 +58,33 @@ export interface TeamOut {
58
58
  team_id: number
59
59
  /** Stable content GUID for this team, when known (omitted, not null, when absent). */
60
60
  guid?: string
61
+ /**
62
+ * This team's WvW world/shard id from `CBTS_WVWTEAMS`, omitted the same
63
+ * way. Absent for logs predating that event and for any team the event
64
+ * does not name -- a team can have a `color` (resolved from the static
65
+ * fallback id table) but no `shard_id`.
66
+ */
67
+ shard_id?: number
68
+ }
69
+
70
+ /** One ownership observation. `time_ms` is log-relative milliseconds. */
71
+ export interface ObjectiveOwnerOut {
72
+ team_id: number
73
+ time_ms: number
74
+ }
75
+
76
+ /**
77
+ * One WvW objective's ownership timeline, from
78
+ * `CBTS_WVWOBJECTIVESTATUS`. Never carries an `"Unknown"`
79
+ * `objective_type`: an objective the static catalog cannot type is dropped
80
+ * rather than emitted untyped. `owners` keeps repeats rather than
81
+ * collapsing them, matching GW2EI.
82
+ */
83
+ export interface ObjectiveOut {
84
+ map_id: number
85
+ objective_id: number
86
+ objective_type: 'Camp' | 'Ruins' | 'Tower' | 'Keep' | 'Castle'
87
+ owners: ObjectiveOwnerOut[]
61
88
  }
62
89
 
63
90
  export interface PerEnemyOut {
@@ -73,6 +100,15 @@ export interface PerEnemyOut {
73
100
  * last-hit attributions, matching GW2EI. `downs_contribution_damage` is the
74
101
  * arcdps-methodology per-target down-contribution, NOT EI's own
75
102
  * 90%-to-downstate-window algorithm.
103
+ *
104
+ * Phase B (native-format-gap-closure Task 4) widened this from 8 fields to
105
+ * 24. `direct_damage` is EI's per-target `directDmg` -- the damage sum
106
+ * over `is_direct_hit` rows against this one target -- and is NOT the same
107
+ * quantity as this schema's `connected_direct_dmg` elsewhere (a different,
108
+ * whole-fight-only figure); collapsing the two would silently substitute
109
+ * the wrong number under a plausible-looking name. `critable_direct_count`
110
+ * is native-only: it is `criticalRate`'s denominator, which real EI never
111
+ * publishes per target, so it has no `statsTargets` counterpart.
76
112
  */
77
113
  export interface PerTargetStatsOut {
78
114
  enemy_id: number
@@ -83,6 +119,22 @@ export interface PerTargetStatsOut {
83
119
  killed: number
84
120
  interrupts: number
85
121
  downs_contribution_damage: number
122
+ direct_count: number
123
+ direct_damage: number
124
+ crit_count: number
125
+ crit_damage: number
126
+ flank_count: number
127
+ glance_count: number
128
+ critable_direct_count: number
129
+ against_downed_damage: number
130
+ missed: number
131
+ evaded: number
132
+ blocked: number
133
+ invulned: number
134
+ applied_total: number
135
+ applied_duration_ms: number
136
+ applied_downs_contribution: number
137
+ applied_duration_downs_contribution_ms: number
86
138
  }
87
139
 
88
140
  export interface DamageOut {
@@ -531,6 +583,17 @@ export interface SkillMapEntryOut {
531
583
  auto_attack?: boolean
532
584
  is_swap: boolean
533
585
  can_crit: boolean
586
+ /**
587
+ * MPROC. All five are OMITTED when false -- a proc flag is rare, and
588
+ * emitting ~370 x 5 literal `false`s cost 16% of the report. Absence
589
+ * means false, not unknown. `is_instant_cast` is the strong one: a
590
+ * finder actually fired in this log, not merely that one was available.
591
+ */
592
+ is_trait_proc?: boolean
593
+ is_gear_proc?: boolean
594
+ is_unconditional_proc?: boolean
595
+ is_not_accurate?: boolean
596
+ is_instant_cast?: boolean
534
597
  }
535
598
 
536
599
  export interface EnemyOut {
@@ -577,8 +640,17 @@ export interface EncounterOut {
577
640
  teams: TeamOut[]
578
641
  /** Every `CBTS_MARKER` assignment observed in the log, across all agents -- not just squad/enemy players. Always present (possibly empty), never omitted. */
579
642
  markers: MarkerAssignmentOut[]
643
+ /** WvW objective ownership timelines. Empty (not omitted) for non-WvW logs and for logs predating `CBTS_WVWOBJECTIVESTATUS`. */
644
+ objectives: ObjectiveOut[]
580
645
  /** Omitted entirely (not `null`) when the log has fewer than two `CBTS_TICK` events. */
581
646
  tick_rate?: TickRateOut
647
+ /**
648
+ * Wall-clock log start, **seconds** since the Unix epoch, from arcdps's
649
+ * `CBTS_LOGSTART`. Omitted entirely (not `null`) when the log carries no
650
+ * such event -- absence stays distinguishable from epoch zero, so do not
651
+ * default it to `0`.
652
+ */
653
+ started_at_unix?: number
582
654
  }
583
655
 
584
656
  /** Min/max `x`/`y` observed across every `ReplayOut.tracks[].samples` -- lets a consumer size a viewBox without a second pass over `tracks`. */
@@ -699,3 +771,1212 @@ export interface Report {
699
771
  */
700
772
  damage_mod_map?: Record<string, DamageModDescOut>
701
773
  }
774
+
775
+ /* -------------------------------------------------------------------- */
776
+ /* Native format 1.0 (`axilog_schema::v1::ReportV1`), Task 12 */
777
+ /* -------------------------------------------------------------------- */
778
+
779
+ /**
780
+ * Hand-transcribed types for `axilog_schema::v1::ReportV1`
781
+ * (`crates/axilog-schema/src/v1/{mod.rs,envelope.rs,entities.rs,catalogs.rs,
782
+ * series.rs,blocks/*.rs}`), the native output format 1.0 container.
783
+ * `parseFile`/`parseBuffer` now return THIS shape, not the legacy `Report`
784
+ * above (kept in place, unused by these two entry points as of Task 12, but
785
+ * left untouched -- see this file's own top comment). `parseFileEi` is
786
+ * unaffected and keeps returning the EI-compatibility JSON typed `any` in
787
+ * `index.d.ts`.
788
+ *
789
+ * Same numeric-field convention as above: every Rust `u64`/`u32`/`f64`/`i32`
790
+ * field is `number`, exact for every value this schema actually produces but
791
+ * lossy in principle for a `u64` beyond 2^53.
792
+ */
793
+
794
+ /** The FORMAT contract version plus the producing binary's version and the input file name. */
795
+ export interface AxilogMeta {
796
+ /** The FORMAT contract version. Moves independently of `version`. Currently always `"1.0"`. */
797
+ schema: string
798
+ /** The binary that produced this document (`CARGO_PKG_VERSION`). */
799
+ version: string
800
+ /** The input log's file NAME, never a full path. Omitted when unknown (e.g. `parseBuffer`, which has no file name to offer). */
801
+ generated_from?: string
802
+ }
803
+
804
+ /**
805
+ * One `CBTS_MARKER` assignment, native 1.0 shape. `agent_addr` is always
806
+ * present (arcdps does not restrict `CBTS_MARKER` to squad members, and many
807
+ * carrying agents never become tracked entities); `entity_id` is present
808
+ * only when the agent resolves to a roster entity.
809
+ */
810
+ export interface MarkerAssignmentOutV1 {
811
+ /** The entity this marker is on. Absent for agents that carry markers but are not tracked entities. */
812
+ entity_id?: number
813
+ agent_addr: number
814
+ marker: string
815
+ time_ms: number
816
+ }
817
+
818
+ /**
819
+ * The 1.0 encounter envelope -- a reprojection of the legacy `EncounterOut`
820
+ * with `markers[]` rekeyed from `agent_addr` to `entity_id`.
821
+ */
822
+ export interface EncounterOutV1 {
823
+ kind: string
824
+ map: string
825
+ duration_ms: number
826
+ build: string
827
+ revision: number
828
+ /** The recording player, as an entity id. Omitted when the recorder does not resolve to an entity. */
829
+ recorded_by?: number
830
+ teams: TeamOut[]
831
+ markers: MarkerAssignmentOutV1[]
832
+ /** Carried verbatim from the legacy shape -- an objective belongs to the map, not to an entity, so unlike `markers` there is no join key to rekey. */
833
+ objectives: ObjectiveOut[]
834
+ tick_rate?: TickRateOut
835
+ /**
836
+ * Wall-clock log start, **seconds** since the Unix epoch, from arcdps's
837
+ * `CBTS_LOGSTART`. Omitted when the log carries no such event -- absence
838
+ * is deliberately distinguishable from epoch zero, so do not default it
839
+ * to `0`. Multiply by 1000 for a JS `Date`.
840
+ */
841
+ started_at_unix?: number
842
+ }
843
+
844
+ /**
845
+ * What an entity IS -- squad member, non-squad friendly, enemy player, or
846
+ * NPC/gadget. Declaration order is `entities[]`'s sort order.
847
+ */
848
+ export type Role = 'squad' | 'friendly_player' | 'enemy_player' | 'npc'
849
+
850
+ export interface CommanderOut {
851
+ variant: string
852
+ guid: string
853
+ /**
854
+ * Terminated, half-open `[tag-on, tag-off)` windows in **arcdps session
855
+ * time** -- the same base as `encounter.markers[].time_ms`, NOT
856
+ * log-relative and NOT comparable against `encounter.duration_ms`. Do not
857
+ * clip these to `[0, duration_ms]`; subtract the log's own `t0` first if
858
+ * you need encounter-relative values.
859
+ *
860
+ * Literal per-instance holds, not a coalesced whole-fight span: a
861
+ * zero-width `[t, t]` pair from a same-timestamp reassignment is normal.
862
+ * An empty array on a PRESENT `commander` means the tag was detected but
863
+ * its windows could not be resolved -- not that the player never
864
+ * commanded.
865
+ */
866
+ segments: [number, number][]
867
+ }
868
+
869
+ /**
870
+ * One agent's IDENTITY only -- no statistics; those live in `blocks`, keyed
871
+ * by `id`. The single place account and character names appear, so a PII
872
+ * scrub is one pass rather than a hunt through nested structures.
873
+ */
874
+ export interface EntityOut {
875
+ /** Dense index into `entities[]`, from 0. Stable WITHIN a report only -- join across logs on `account`. */
876
+ id: number
877
+ role: Role
878
+ /** Players only. */
879
+ account?: string
880
+ /** Players only. */
881
+ character?: string
882
+ /** Non-player entities only. */
883
+ name?: string
884
+ /** Present exactly for player roles. */
885
+ profession?: string
886
+ /** Empty string when the agent has no elite spec, or one this project cannot name. Never a numeric spec id. */
887
+ elite_spec?: string
888
+ team: string
889
+ subgroup?: number
890
+ commander?: CommanderOut
891
+ guild_id?: string
892
+ marker?: string
893
+ /** The arcdps agent address -- a documented attribute, not a secret. */
894
+ agent_addr: number
895
+ instid?: number
896
+ /**
897
+ * Whether this entity interacted with the squad at all (dealt damage to
898
+ * the squad, took damage from the squad, or took CC from the squad).
899
+ * Always `true` for squad members and non-squad friendlies. Never
900
+ * optional -- absence would be ambiguous between "did not participate"
901
+ * and "not computed".
902
+ */
903
+ combat_participant: boolean
904
+ }
905
+
906
+ /** Definition metadata for one referenced skill id. */
907
+ export interface SkillEntry {
908
+ name: string
909
+ /**
910
+ * Render-service or wiki URL (Phase C), resolved from `skill_icons` (the
911
+ * GW2 API) first and `buff_icons` (GW2EI's own table) second; omitted
912
+ * when neither knows the id. Buff ids resolve here too -- there is no
913
+ * separate icon field on `BuffEntry`.
914
+ */
915
+ icon?: string
916
+ is_swap: boolean
917
+ can_crit: boolean
918
+ auto_attack?: boolean
919
+ /**
920
+ * MPROC. All five are OMITTED when false -- a proc flag is rare, and
921
+ * emitting ~370 x 5 literal `false`s cost 16% of the report. Absence
922
+ * means false, not unknown. `is_instant_cast` is the strong one: a
923
+ * finder actually fired in this log, not merely that one was available.
924
+ */
925
+ is_trait_proc?: boolean
926
+ is_gear_proc?: boolean
927
+ is_unconditional_proc?: boolean
928
+ is_not_accurate?: boolean
929
+ is_instant_cast?: boolean
930
+ }
931
+
932
+ /**
933
+ * Definition metadata for one referenced buff id. `kind` is GW2's real
934
+ * three-way taxonomy (arcdps does not distinguish these structurally).
935
+ */
936
+ export interface BuffEntry {
937
+ name: string
938
+ kind: 'condition' | 'boon' | 'effect'
939
+ stacking: 'intensity' | 'duration'
940
+ max_stacks?: number
941
+ }
942
+
943
+ /** Definition metadata for one referenced damage-modifier id. */
944
+ export interface DamageModEntry {
945
+ name: string
946
+ icon?: string
947
+ /**
948
+ * EI's full tooltip, including its derived `<br>Applied on ...` /
949
+ * `<br>Counter` / `<br>Approximate` suffixes.
950
+ */
951
+ description?: string
952
+ non_multiplier?: boolean
953
+ is_counter?: boolean
954
+ skill_based?: boolean
955
+ approximate?: boolean
956
+ }
957
+
958
+ /**
959
+ * Definition metadata for one minion group referenced by `MinionsBlock`.
960
+ * Identity lives here rather than on the row, like every other catalog in
961
+ * this format.
962
+ */
963
+ export interface MinionEntry {
964
+ species_id: number
965
+ name: string
966
+ }
967
+
968
+ /**
969
+ * Definition metadata for every id any block references. No human-readable
970
+ * name appears outside `catalogs` or `entities` -- every block references
971
+ * integers instead. Keys serialize as decimal strings.
972
+ */
973
+ export interface Catalogs {
974
+ skills: Record<string, SkillEntry>
975
+ buffs: Record<string, BuffEntry>
976
+ /** Omitted entirely (not `{}`) when no damage-modifier id was referenced (e.g. `modifiers` was not requested). */
977
+ damage_mods?: Record<string, DamageModEntry>
978
+ /** Omitted entirely (not `{}`) when no minion id was referenced (e.g. `skillDamage` was not requested). */
979
+ minions?: Record<string, MinionEntry>
980
+ }
981
+
982
+ /**
983
+ * One time series in the format's single series envelope. `enc` is `"raw"`
984
+ * (a plain array of values) or `"rle"` (an array of `[value, run_length]`
985
+ * pairs), chosen per series by whichever serializes smaller. `len` is the
986
+ * DECODED length in both cases (NOT `data.length`), so a consumer can
987
+ * allocate before decoding and validate after.
988
+ */
989
+ export interface SeriesOut {
990
+ interval_ms: number
991
+ /** Decoded length, NOT `data.length`. */
992
+ len: number
993
+ enc: 'raw' | 'rle'
994
+ data: unknown[]
995
+ }
996
+
997
+ /** Why a block is or is not present in `blocks`. */
998
+ /**
999
+ * Why a block is or is not in `blocks`.
1000
+ *
1001
+ * - `present` -- computed, carried, at least one row.
1002
+ * - `not_computed` -- the compute gate was off. NOT the same as empty:
1003
+ * it is a missing flag, not a fact about the log.
1004
+ * - `empty` -- computed and genuinely nothing to report. The block is
1005
+ * still carried; only `not_computed`/`unsupported` omit it.
1006
+ * - `unsupported` -- RESERVED. No code path in this version emits it:
1007
+ * nothing this container computes is era- or encounter-kind-gated.
1008
+ * Named now so spec #2's era-gated surfaces can fill the slot without
1009
+ * consumers having to learn a new value late. Handle it, but do not
1010
+ * expect it from this version.
1011
+ */
1012
+ export type CoverageState = 'present' | 'not_computed' | 'empty' | 'unsupported'
1013
+
1014
+ /**
1015
+ * Per-block computation/presence status, keyed by block name
1016
+ * (`"damage"`, `"defenses"`, `"hit_stats"`, `"cc"`, `"boons"`, `"support"`,
1017
+ * `"contribution"`, `"healing"`, `"rotation"`, `"damage_mods"`,
1018
+ * `"missiles"`, `"replay"`, `"series"`, `"conditions"`, `"minions"`).
1019
+ * Always names every known block, including ones a given parse did not
1020
+ * compute -- those read `"not_computed"`.
1021
+ *
1022
+ * One entry is narrower than it looks: `"boons"` reports on that block's
1023
+ * always-on uptime half only, NOT on its `{ timeseries: true }` stack
1024
+ * timelines (see `BoonsBlock`). `"replay"` is the same shape of exception
1025
+ * (see `ReplayBlock`).
1026
+ */
1027
+ export type Coverage = Record<string, CoverageState>
1028
+
1029
+ export type Severity = 'info' | 'warn' | 'error'
1030
+
1031
+ /**
1032
+ * A structured, user-facing analysis warning. `code` is a closed,
1033
+ * documented set: adding a code is additive, changing one's meaning is a
1034
+ * break.
1035
+ */
1036
+ export interface WarningOut {
1037
+ code: string
1038
+ severity: Severity
1039
+ message: string
1040
+ /** The entity this warning is about, when it is about one. */
1041
+ entity_id?: number
1042
+ }
1043
+
1044
+ /** The uniform entity-keyed map every block uses. Keys serialize as decimal strings. */
1045
+ export type ByEntity<T> = Record<string, T>
1046
+
1047
+ /* --- damage block -------------------------------------------------------- */
1048
+
1049
+ /**
1050
+ * Aggregates `Role::Squad` entities ONLY -- not every friendly player (see
1051
+ * `by_entity`, which is the full roster).
1052
+ */
1053
+ export interface DamageSquad {
1054
+ total: number
1055
+ dps: number
1056
+ }
1057
+
1058
+ /**
1059
+ * Mirrors the legacy `SkillEntryOut` field-for-field. `hits`/`min`/`max`
1060
+ * count only CONTRIBUTING (`dmg > 0`) events. `crit_hits`/`flank_hits` are
1061
+ * hit COUNTS, not damage sums.
1062
+ */
1063
+ export interface SkillRow {
1064
+ total: number
1065
+ /**
1066
+ * CONTRIBUTING (`dmg > 0`) row count. Present on player rows, ABSENT on
1067
+ * enemy rows -- those come from a different pass that never computes it,
1068
+ * so nothing divides `total` by a fabricated denominator.
1069
+ */
1070
+ hits?: number
1071
+ /**
1072
+ * `HasHit` row count -- GW2EI's `connectedHits`, which counts a
1073
+ * connecting hit that dealt zero health damage and excludes the
1074
+ * blocked/evaded/missed/invulned cases. A THIRD distinct quantity from
1075
+ * `hits` and `outcomes.attempt_hits`; they are separate fields so a
1076
+ * consumer cannot divide by the wrong denominator.
1077
+ */
1078
+ connected_hits?: number
1079
+ min: number
1080
+ max: number
1081
+ /** Hit COUNTS, not damage sums. */
1082
+ crit_hits: number
1083
+ flank_hits: number
1084
+ /**
1085
+ * Present on PLAYER rows when `skillDamage` is on, absent on enemy rows.
1086
+ * Absent means "this pass did not measure this row", never "every
1087
+ * attempt connected".
1088
+ */
1089
+ outcomes?: SkillOutcomeCols
1090
+ }
1091
+
1092
+ /**
1093
+ * The hit-OUTCOME breakdown of one `SkillRow`.
1094
+ *
1095
+ * `glance`/`missed`/`evaded`/`blocked` are zero on a condition skill (EI
1096
+ * zeroes them inside its `if (!IndirectDamage)` guard); `invulned` is NOT,
1097
+ * because a condition tick can land on an invulnerable target and EI
1098
+ * counts it.
1099
+ */
1100
+ export interface SkillOutcomeCols {
1101
+ /** GW2EI's own `hits`: every row that is not one of its no-damage markers. */
1102
+ attempt_hits: number
1103
+ glance: number
1104
+ missed: number
1105
+ evaded: number
1106
+ blocked: number
1107
+ invulned: number
1108
+ interrupted: number
1109
+ /**
1110
+ * This skill produced at least one non-direct (condition) damage row.
1111
+ * Every strike-damage surface downstream uses it as a skip filter.
1112
+ */
1113
+ indirect: boolean
1114
+ }
1115
+
1116
+ /**
1117
+ * Mirrors the legacy `PerTargetStatsOut` field-for-field, minus `enemy_id`
1118
+ * (the enclosing map's key here, as the target's ENTITY id). `interrupts`
1119
+ * and `downs_contribution_damage` are not derivable from any other block.
1120
+ *
1121
+ * Phase B (native-format-gap-closure Task 4) widened this from 7 fields to
1122
+ * 23, mirroring `PerTargetStatsOut`'s own widening above -- see that
1123
+ * interface's doc comment for the `direct_damage`/`connected_direct_dmg`
1124
+ * distinction and why `critable_direct_count` has no ei-json counterpart.
1125
+ */
1126
+ export interface PerTargetDetail {
1127
+ connected_hits: number
1128
+ connected_damage: number
1129
+ against_downed_count: number
1130
+ downed: number
1131
+ killed: number
1132
+ interrupts: number
1133
+ /**
1134
+ * arcdps-methodology down-contribution DAMAGE for downs of this specific
1135
+ * target -- NOT GW2EI's 90%-to-downstate-window algorithm.
1136
+ */
1137
+ downs_contribution_damage: number
1138
+ direct_count: number
1139
+ direct_damage: number
1140
+ crit_count: number
1141
+ crit_damage: number
1142
+ flank_count: number
1143
+ glance_count: number
1144
+ critable_direct_count: number
1145
+ against_downed_damage: number
1146
+ missed: number
1147
+ evaded: number
1148
+ blocked: number
1149
+ invulned: number
1150
+ applied_total: number
1151
+ applied_duration_ms: number
1152
+ applied_downs_contribution: number
1153
+ applied_duration_downs_contribution_ms: number
1154
+ }
1155
+
1156
+ /**
1157
+ * One `(entity, target)` pair. `total` is ungated; `detail` and `by_skill`
1158
+ * come from the `skillDamage: true`-gated families, so a row can
1159
+ * legitimately carry `total` alone.
1160
+ */
1161
+ export interface PerTarget {
1162
+ total: number
1163
+ /**
1164
+ * Grouped under one key rather than flattened so its absence has a
1165
+ * single unambiguous signal instead of 23 fabricated zeros.
1166
+ */
1167
+ detail?: PerTargetDetail
1168
+ /** Per-(entity, target, skill) outgoing damage, keyed by skill id. */
1169
+ by_skill?: Record<string, SkillRow>
1170
+ }
1171
+
1172
+ export interface DamageEntity {
1173
+ total: number
1174
+ dps: number
1175
+ taken: number
1176
+ /**
1177
+ * Enemy players this entity landed the DOWNING blow on. An outgoing
1178
+ * outcome; the incoming mirrors are `DefensesEntity.downs_taken`/
1179
+ * `deaths`, the same split GW2EI makes.
1180
+ */
1181
+ downs_dealt: number
1182
+ /** Enemy players this entity landed the KILLING blow on. */
1183
+ kills_dealt: number
1184
+ /**
1185
+ * Breakbar damage this entity DEALT. Outgoing, hence here rather than on
1186
+ * `defenses`, whose `breakbar_count`/`breakbar_damage` are its INCOMING
1187
+ * mirror. Feeds ei-json's `dpsAll[0].breakbarDamage`.
1188
+ */
1189
+ breakbar_damage_dealt: number
1190
+ /**
1191
+ * Keyed by the TARGET's entity id. Sparse; omitted when empty. A UNION of
1192
+ * three legacy per-enemy families, so a target can appear with `detail`
1193
+ * and no damage `total`.
1194
+ */
1195
+ per_target?: Record<string, PerTarget>
1196
+ /** OUTGOING per-skill damage, keyed by skill id. Present only when the per-skill compute gate (`skillDamage: true`) was on. */
1197
+ by_skill?: Record<string, SkillRow>
1198
+ /** INCOMING per-skill damage, keyed by skill id -- mirroring this row's own `total`/`taken` pair. Same gate as `by_skill`. */
1199
+ by_skill_taken?: Record<string, SkillRow>
1200
+ }
1201
+
1202
+ export interface DamageBlock {
1203
+ squad: DamageSquad
1204
+ by_entity: ByEntity<DamageEntity>
1205
+ }
1206
+
1207
+ /* --- defenses / hit_stats / cc blocks ------------------------------------ */
1208
+
1209
+ /** Incoming defenses. Mirrors the legacy `DefensesOut` field-for-field. */
1210
+ export interface DefensesEntity {
1211
+ blocked_count: number
1212
+ evaded_count: number
1213
+ dodge_count: number
1214
+ missed_count: number
1215
+ interrupted_count: number
1216
+ invulned_count: number
1217
+ strike_count: number
1218
+ strike_damage: number
1219
+ power_count: number
1220
+ power_damage: number
1221
+ condition_count: number
1222
+ condition_damage: number
1223
+ life_leech_count: number
1224
+ life_leech_damage: number
1225
+ barrier_count: number
1226
+ barrier_damage: number
1227
+ breakbar_count: number
1228
+ breakbar_damage: number
1229
+ /** Incoming crowd control. */
1230
+ received_cc_count: number
1231
+ received_cc_duration_ms: number
1232
+ /** Boons stripped OFF this player -- the incoming counterpart of `SupportEntity.strips`. */
1233
+ boon_strips_taken: number
1234
+ boon_strips_taken_duration_ms: number
1235
+ /**
1236
+ * Times this entity entered downstate -- GW2EI's
1237
+ * `defenses[0].downCount`. The outgoing mirrors live on
1238
+ * `DamageEntity.downs_dealt`/`kills_dealt`.
1239
+ */
1240
+ downs_taken: number
1241
+ /** Times this entity died -- GW2EI's `defenses[0].deadCount`. */
1242
+ deaths: number
1243
+ }
1244
+
1245
+ export interface DefensesBlock {
1246
+ by_entity: ByEntity<DefensesEntity>
1247
+ }
1248
+
1249
+ /**
1250
+ * Outgoing hit quality. Mirrors the legacy `HitStatsOut` field-for-field.
1251
+ * `above90_*` counts/damage are against a target at or above 90% health.
1252
+ */
1253
+ export interface HitStatsEntity {
1254
+ crit_count: number
1255
+ crit_damage: number
1256
+ flank_count: number
1257
+ glance_count: number
1258
+ moving_count: number
1259
+ connected_count: number
1260
+ connected_damage: number
1261
+ direct_count: number
1262
+ direct_damage: number
1263
+ condition_count: number
1264
+ condition_damage: number
1265
+ critable_direct_count: number
1266
+ against_downed_count: number
1267
+ against_downed_damage: number
1268
+ life_leech_count: number
1269
+ life_leech_damage: number
1270
+ above90_power_count: number
1271
+ above90_power_damage: number
1272
+ above90_condition_count: number
1273
+ above90_condition_damage: number
1274
+ }
1275
+
1276
+ export interface HitStatsBlock {
1277
+ by_entity: ByEntity<HitStatsEntity>
1278
+ }
1279
+
1280
+ /** Aggregates `Role::Squad` entities ONLY. */
1281
+ export interface CcSquad {
1282
+ applied_total: number
1283
+ applied_duration_ms: number
1284
+ }
1285
+
1286
+ /**
1287
+ * Mirrors the legacy `CcOut` field-for-field. Incoming CC lives on
1288
+ * `DefensesEntity.received_cc_count`/`received_cc_duration_ms` instead.
1289
+ */
1290
+ export interface CcEntity {
1291
+ applied_total: number
1292
+ applied_duration_ms: number
1293
+ stun_breaks: number
1294
+ removed_stun_duration_ms: number
1295
+ }
1296
+
1297
+ export interface CcBlock {
1298
+ squad: CcSquad
1299
+ by_entity: ByEntity<CcEntity>
1300
+ }
1301
+
1302
+ /* --- boons / support / contribution / healing blocks --------------------- */
1303
+
1304
+ /**
1305
+ * Mirrors the legacy `GenerationOut` field-for-field. WASTED fields are
1306
+ * rounded to 3 decimals and omitted when exactly zero -- read them as 0
1307
+ * when absent.
1308
+ */
1309
+ export interface GenerationRow {
1310
+ self_pct: number
1311
+ group_pct: number
1312
+ squad_pct: number
1313
+ self_wasted?: number
1314
+ group_wasted?: number
1315
+ squad_wasted?: number
1316
+ }
1317
+
1318
+ /**
1319
+ * One `[[time_ms_from_log_start, stacks], ...]` step timeline.
1320
+ *
1321
+ * Always opens with a `[0, 0]` pair and never carries two pairs at one
1322
+ * timestamp. Duration buffs report 0/1; only intensity buffs (Might,
1323
+ * Stability, most damaging conditions) exceed 1.
1324
+ */
1325
+ export type StateTimeline = [number, number][]
1326
+
1327
+ /**
1328
+ * A buff's stack timeline split by who applied it, keyed by SOURCE ENTITY
1329
+ * ID -- joining back into `entities[]` like every other key in this format.
1330
+ *
1331
+ * The upstream analysis keys these by the applier's character NAME; native
1332
+ * does not, both because a name is identity data this format confines to
1333
+ * `entities[]` and because two players sharing a character name collide
1334
+ * onto one key.
1335
+ */
1336
+ export interface PerSourceStates {
1337
+ /** Keyed by the applying entity's id. */
1338
+ by_source: Record<string, StateTimeline>
1339
+ /**
1340
+ * Every applier that resolves to no `entities[]` row at all, merged into
1341
+ * one timeline -- so those applications are neither dropped nor given a
1342
+ * fabricated entity id. Normally absent.
1343
+ */
1344
+ unresolved?: StateTimeline
1345
+ }
1346
+
1347
+ /**
1348
+ * Mirrors the legacy `BoonOut` field-for-field, minus `id` (the map key) and
1349
+ * `name` (resolve via `catalogs.buffs`) -- plus the two timeline fields,
1350
+ * which need `{ timeseries: true }`.
1351
+ */
1352
+ export interface BoonRow {
1353
+ uptime_pct: number
1354
+ avg_stacks?: number
1355
+ generation: GenerationRow
1356
+ /**
1357
+ * The fused stack timeline. `{ timeseries: true }` only.
1358
+ *
1359
+ * May be `[]`, meaning the pass ran and this entity never held the buff
1360
+ * -- a real timeline always has at least its leading `[0, 0]`, so `[]` is
1361
+ * unambiguous, and the field's presence is the signal the gate was on.
1362
+ */
1363
+ states?: StateTimeline
1364
+ /** The same timeline split by applier. `{ timeseries: true }` only. */
1365
+ per_source?: PerSourceStates
1366
+ }
1367
+
1368
+ /**
1369
+ * entity id -> buff id -> row. Two levels of real ids, no positional joins.
1370
+ *
1371
+ * Two gates, like `ReplayBlock`: the uptime/generation numbers are computed
1372
+ * on every parse, `BoonRow.states`/`per_source` need `{ timeseries: true }`.
1373
+ * So `coverage.boons === "present"` is about the uptime half ONLY and says
1374
+ * nothing about whether timelines are here -- check the fields.
1375
+ */
1376
+ export interface BoonsBlock {
1377
+ by_entity: ByEntity<Record<string, BoonRow>>
1378
+ }
1379
+
1380
+ /**
1381
+ * One enemy's condition timeline, split by the squad member who applied it.
1382
+ *
1383
+ * There is no sibling fused `states` here, unlike `BoonRow`: the enemy-side
1384
+ * pass computes only the source split, and summing sources would not
1385
+ * reconstruct a total (two appliers holding the same duration condition
1386
+ * overlap rather than stack).
1387
+ */
1388
+ export interface ConditionRow {
1389
+ per_source: PerSourceStates
1390
+ }
1391
+
1392
+ /**
1393
+ * enemy entity id -> condition buff id -> row. Wholly gated on
1394
+ * `{ timeseries: true }`, so unlike `boons` its `coverage` entry does settle
1395
+ * the question.
1396
+ *
1397
+ * Appliers are narrowed to the SQUAD -- an enemy-applied condition on
1398
+ * another enemy is real but has no consumer -- so
1399
+ * `PerSourceStates.unresolved` is always absent here.
1400
+ */
1401
+ export interface ConditionsBlock {
1402
+ by_entity: ByEntity<Record<string, ConditionRow>>
1403
+ }
1404
+
1405
+ /**
1406
+ * Per-player minion damage-TAKEN rollups, gated on `{ skillDamage: true }`.
1407
+ * One entry per player that HAS minions -- a player with none is absent
1408
+ * rather than carrying an empty array.
1409
+ */
1410
+ export interface MinionsBlock {
1411
+ by_entity: ByEntity<MinionRow[]>
1412
+ }
1413
+
1414
+ /**
1415
+ * One minion group belonging to one player. `minion_id` resolves through
1416
+ * `Catalogs.minions`, which carries the species id and name -- identity
1417
+ * lives there because no block in this format inlines a readable name.
1418
+ */
1419
+ export interface MinionRow {
1420
+ minion_id: number
1421
+ /** The damage this species took, keyed by skill id. */
1422
+ taken: Record<string, MinionSkillTakenRow>
1423
+ }
1424
+
1425
+ /**
1426
+ * One minion group's damage-taken row for one skill.
1427
+ *
1428
+ * Deliberately NOT a `SkillRow`: that row carries `crit_hits`/`flank_hits`,
1429
+ * which a damage-taken rollup does not have, while this one carries the
1430
+ * outcome counters inline rather than nested in `outcomes`.
1431
+ */
1432
+ export interface MinionSkillTakenRow {
1433
+ total: number
1434
+ /** Attempts, marker rows excluded. */
1435
+ hits: number
1436
+ /** `HasHit` rows only. */
1437
+ connected_hits: number
1438
+ min: number
1439
+ max: number
1440
+ blocked: number
1441
+ evaded: number
1442
+ glance: number
1443
+ missed: number
1444
+ invulned: number
1445
+ interrupted: number
1446
+ /** Condition damage rather than strike damage. */
1447
+ indirect: boolean
1448
+ }
1449
+
1450
+ /** Mirrors the legacy `SupportOut` field-for-field. */
1451
+ export interface SupportEntity {
1452
+ cleanses: number
1453
+ cleanses_self: number
1454
+ strips: number
1455
+ strips_duration_ms: number
1456
+ resurrects: number
1457
+ }
1458
+
1459
+ export interface SupportBlock {
1460
+ by_entity: ByEntity<SupportEntity>
1461
+ }
1462
+
1463
+ /** Mirrors the legacy `ContributionOut` field-for-field. */
1464
+ export interface ContributionRow {
1465
+ damage: number
1466
+ cc: number
1467
+ strips: number
1468
+ movement_impairing: number
1469
+ }
1470
+
1471
+ /** Both directions of the arcdps-methodology down contribution. */
1472
+ export interface ContributionEntity {
1473
+ downs_contribution: ContributionRow
1474
+ downed_by: ContributionRow
1475
+ /**
1476
+ * `downs_contribution.damage` sliced by the skill that dealt it, keyed by
1477
+ * skill id as a decimal string. Sparse -- only skills with a nonzero
1478
+ * credit appear -- and omitted entirely when there are none. Ungated:
1479
+ * the contribution pass is always-on, unlike the per-skill DAMAGE rows on
1480
+ * `blocks.damage`, which is why it lives here rather than beside them.
1481
+ */
1482
+ downs_contribution_by_skill?: Record<string, number>
1483
+ }
1484
+
1485
+ export interface ContributionBlock {
1486
+ by_entity: ByEntity<ContributionEntity>
1487
+ }
1488
+
1489
+ /** Mirrors the legacy `HealingOut` field-for-field. */
1490
+ export interface HealingEntity {
1491
+ outgoing_total: number
1492
+ outgoing_allies: number
1493
+ outgoing_self: number
1494
+ barrier_out: number
1495
+ downed_healing_out: number
1496
+ /**
1497
+ * The per-ally / per-skill breakdowns. Omitted when that pass did not
1498
+ * run -- the "not measured" signal, as distinct from a measured zero.
1499
+ *
1500
+ * Note the cumulative healing SERIES is not here; it lives at
1501
+ * `EntitySeries.healing_1s`, because what a field belongs to in this
1502
+ * format is its grid and its gate, not its subject matter.
1503
+ */
1504
+ detail?: HealingDetailCols
1505
+ }
1506
+
1507
+ /**
1508
+ * The three per-ally / per-skill breakdowns of one entity's outgoing
1509
+ * healing and barrier.
1510
+ */
1511
+ export interface HealingDetailCols {
1512
+ /**
1513
+ * Keyed by the ALLY's entity id (EI's positional
1514
+ * `outgoingHealingAllies`/`outgoingBarrierAllies` over `log.Friendlies`).
1515
+ * Within a PRESENT map, an absent ally is a MEASURED zero; the optional
1516
+ * `HealingEntity.detail` one level up carries "not measured". The healer
1517
+ * appears at its own id -- self-healing is one of these cells, exactly as
1518
+ * in GW2EI, not a separate scalar.
1519
+ */
1520
+ by_ally: Record<string, AllyHealingRow>
1521
+ /** EI's `totalHealingDist`, keyed by skill id. */
1522
+ by_skill: Record<string, HealSkillRow>
1523
+ /**
1524
+ * EI's `totalBarrierDist`, keyed by skill id. A separate map rather than
1525
+ * a column on `by_skill`: a skill can appear in one and not the other,
1526
+ * and merging them would force every healing row to publish a barrier it
1527
+ * never measured.
1528
+ */
1529
+ barrier_by_skill: Record<string, HealSkillRow>
1530
+ }
1531
+
1532
+ /** One cell of `HealingDetailCols.by_ally`. */
1533
+ export interface AllyHealingRow {
1534
+ healing: number
1535
+ /** The subset of `healing` that landed while the ally was downed. */
1536
+ downed_healing: number
1537
+ barrier: number
1538
+ }
1539
+
1540
+ /**
1541
+ * One skill's row of EI's `totalHealingDist` / `totalBarrierDist`.
1542
+ *
1543
+ * `hits`/`min`/`max` count EVERY event in the group -- GW2EI's healing dist
1544
+ * has no `HasHit` gate, unlike its damage dist, which is why this row
1545
+ * carries one hit count where `SkillRow` carries three.
1546
+ */
1547
+ export interface HealSkillRow {
1548
+ total: number
1549
+ /**
1550
+ * EI's `totalDownedHealing`. Omitted when zero, which is ALWAYS on a
1551
+ * barrier row: `EXTJsonBarrierDist` has no downed field at all, so a zero
1552
+ * there would invent a measurement GW2EI never makes.
1553
+ */
1554
+ total_downed?: number
1555
+ hits: number
1556
+ min: number
1557
+ max: number
1558
+ /** The group contains at least one healing-over-time tick. */
1559
+ indirect: boolean
1560
+ }
1561
+
1562
+ export interface HealingBlock {
1563
+ by_entity: ByEntity<HealingEntity>
1564
+ }
1565
+
1566
+ /* --- rotation / damage_mods / missiles / replay / series blocks ---------- */
1567
+
1568
+ /**
1569
+ * One cast. Mirrors the legacy `CastOut` field-for-field, plus `skill_id`
1570
+ * hoisted from the enclosing skill grouping so this is a flat,
1571
+ * time-ordered cast list per entity.
1572
+ */
1573
+ export interface CastRow {
1574
+ skill_id: number
1575
+ cast_time_ms: number
1576
+ duration_ms: number
1577
+ /** Negative when the cast was interrupted/cancelled early. */
1578
+ time_gained_ms: number
1579
+ quickness: number
1580
+ }
1581
+
1582
+ /**
1583
+ * Mirrors the legacy `AftercastOut`. Durations are MILLISECONDS (GW2EI
1584
+ * emits the same quantities as seconds).
1585
+ *
1586
+ * NOTE the name collision GW2EI bequeathed: `wasted_count` is a
1587
+ * CAST-INTERRUPT count, unrelated to the boon-generation `*_wasted` fields
1588
+ * under `boons`.
1589
+ */
1590
+ export interface Aftercast {
1591
+ /** Casts that skipped their aftercast. */
1592
+ saved_count: number
1593
+ saved_ms: number
1594
+ /** Casts interrupted before firing. */
1595
+ wasted_count: number
1596
+ /** Already the positive "time lost" figure. */
1597
+ wasted_ms: number
1598
+ }
1599
+
1600
+ export interface RotationEntity {
1601
+ /**
1602
+ * This entity's casts, in cast-start order. Present exactly when the cast
1603
+ * gate (`rotation: true`) was on -- an EMPTY array means the pass ran and
1604
+ * this entity cast nothing, while an absent field means it never ran.
1605
+ * `aftercast` below is always-on, so the block's `coverage` cannot answer
1606
+ * that question and this field's presence is the gate record.
1607
+ */
1608
+ casts?: CastRow[]
1609
+ /** Cast counters, computed unconditionally. */
1610
+ aftercast: Aftercast
1611
+ }
1612
+
1613
+ export interface RotationBlock {
1614
+ by_entity: ByEntity<RotationEntity>
1615
+ }
1616
+
1617
+ /**
1618
+ * One damage-modifier row. `id` (the map key) is SIGNED -- negative means
1619
+ * incoming -- so one map naturally separates outgoing from incoming.
1620
+ */
1621
+ export interface DamageModRow {
1622
+ hit_count: number
1623
+ total_hit_count: number
1624
+ damage_gain: number
1625
+ total_damage: number
1626
+ }
1627
+
1628
+ /**
1629
+ * One entity's damage modifiers, in the two scopes GW2EI evaluates them at.
1630
+ * Neither is derivable from the other: `overall` counts every qualifying
1631
+ * hit, including hits on agents that are not targets at all (enemy minions),
1632
+ * while `per_target` restricts to one foe.
1633
+ */
1634
+ export interface DamageModEntity {
1635
+ /** Whole fight. Keyed by the signed damage-modifier id as a decimal string. */
1636
+ overall: Record<string, DamageModRow>
1637
+ /**
1638
+ * Restricted to one foe, keyed by the TARGET's entity id then by the
1639
+ * signed modifier id. Sparse in both dimensions. The per-target split is
1640
+ * the expensive half and the native path does not compute it, so an
1641
+ * absent `per_target` on a present block means "the split was not
1642
+ * computed", not "there was none".
1643
+ */
1644
+ per_target?: Record<string, Record<string, DamageModRow>>
1645
+ }
1646
+
1647
+ export interface DamageModsBlock {
1648
+ by_entity: ByEntity<DamageModEntity>
1649
+ }
1650
+
1651
+ export interface MissilesSquad {
1652
+ fired: number
1653
+ hit: number
1654
+ denied: number
1655
+ incoming_fired: number
1656
+ incoming_denied: number
1657
+ }
1658
+
1659
+ /** Mirrors the legacy `PlayerMissilesOut`, minus `agent_addr`/`account` (identity already lives on this row's own `entities[]` entry). */
1660
+ export interface MissilesEntity {
1661
+ fired: number
1662
+ hit: number
1663
+ denied: number
1664
+ reflected_at_self: number
1665
+ }
1666
+
1667
+ /**
1668
+ * `squad` is REQUIRED, like every other squad aggregate in this format
1669
+ * (`damage`, `cc`, `series`): when the block is present, so is its
1670
+ * aggregate.
1671
+ */
1672
+ export interface MissilesBlock {
1673
+ squad: MissilesSquad
1674
+ by_entity: ByEntity<MissilesEntity>
1675
+ }
1676
+
1677
+ export interface ReplayBounds {
1678
+ min_x: number
1679
+ min_y: number
1680
+ max_x: number
1681
+ max_y: number
1682
+ }
1683
+
1684
+ /**
1685
+ * One entity's replay track. `samples` are `[t_ms, x, y]` triples;
1686
+ * `down_intervals`/`dead_intervals` are `[start_ms, end_ms]` pairs.
1687
+ * `name`/`team`/`commander`/`is_squad` are dropped versus the legacy
1688
+ * `ReplayTrackOut` -- they live on this entity's own `entities[]` row.
1689
+ *
1690
+ * The intervals are ALSO on `ReplayIntervals`, and that is deliberate: the
1691
+ * track roster covers enemy players too, whom the always-on intervals pass
1692
+ * never walks, so dropping them here would lose that history entirely. For
1693
+ * an entity present in both, the two copies are the same values.
1694
+ */
1695
+ export interface ReplayTrack {
1696
+ samples: [number, number, number][]
1697
+ down_intervals: [number, number][]
1698
+ dead_intervals: [number, number][]
1699
+ /**
1700
+ * Disconnect/not-yet-spawned windows, half-open like the two above. See
1701
+ * `ReplayIntervals.dc` for the GW2EI divergence and the log-end rule.
1702
+ */
1703
+ dc_intervals: [number, number][]
1704
+ }
1705
+
1706
+ /**
1707
+ * One SQUAD entity's activity window. `down`/`dead` are `[start_ms, end_ms]`
1708
+ * pairs.
1709
+ *
1710
+ * `active_ms` is carried rather than derivable: matching GW2EI, it subtracts
1711
+ * dead time and NOT down time. Recomputing it as "neither downed nor dead"
1712
+ * under-reports every player who went down.
1713
+ */
1714
+ export interface ReplayIntervals {
1715
+ start_ms: number
1716
+ end_ms: number
1717
+ active_ms: number
1718
+ down: [number, number][]
1719
+ dead: [number, number][]
1720
+ /**
1721
+ * Disconnect/not-yet-spawned windows (`CBTS_DESPAWN` to the matching
1722
+ * `CBTS_SPAWN`), half-open `[start_ms, end_ms)` like `down`/`dead`, and
1723
+ * NOT mutually exclusive with them -- an agent can despawn while dead.
1724
+ *
1725
+ * An agent still disconnected at log end gets **no interval at all** for
1726
+ * that trailing window; it is dropped, not closed at the log's end. So
1727
+ * `end_ms - start_ms` minus summed `dc` over-counts activity for anyone
1728
+ * who disconnects and never returns -- use `active_ms`.
1729
+ */
1730
+ dc: [number, number][]
1731
+ /**
1732
+ * GW2EI's `distToCom` -- mean distance to the commander over this actor's
1733
+ * active polls, in world inches.
1734
+ *
1735
+ * Three states, and the first two must not be collapsed: **absent** means
1736
+ * the position pass never ran (`{ replay: true }` was not passed, nothing
1737
+ * was measured); **`-1`** means the pass ran and nothing qualified (GW2EI's
1738
+ * own sentinel); **`>= 0`** is a real distance (`0` is legitimate -- the
1739
+ * commander's own value).
1740
+ *
1741
+ * These two scalars are the only part of `ReplayBlock.by_entity` that
1742
+ * depends on the `{ replay: true }` gate; every interval field above is
1743
+ * computed on every parse and is identical with and without it.
1744
+ */
1745
+ dist_to_com?: number
1746
+ /** GW2EI's `stackDist`, against the squad centre. Same three states and same gate as `dist_to_com`. */
1747
+ stack_dist?: number
1748
+ }
1749
+
1750
+ /** The gated half of `ReplayBlock` -- present only under `{ replay: true }`. */
1751
+ export interface ReplayTracks {
1752
+ /** Shared polling interval for every track. */
1753
+ poll_ms: number
1754
+ bounds?: ReplayBounds
1755
+ /** Keyed by entity id. Wider than `ReplayBlock.by_entity`: enemy players appear here too. */
1756
+ by_entity: ByEntity<ReplayTrack>
1757
+ }
1758
+
1759
+ /**
1760
+ * Two halves on two different gates -- the only block in the format shaped
1761
+ * this way. `by_entity` (down/dead intervals, squad only) is computed on
1762
+ * every parse; `tracks` (positions) needs `{ replay: true }`. So
1763
+ * `coverage.replay === "present"` does NOT mean positions are available --
1764
+ * check `tracks` for that.
1765
+ */
1766
+ export interface ReplayBlock {
1767
+ /** Keyed by entity id. Squad players only. */
1768
+ by_entity: ByEntity<ReplayIntervals>
1769
+ tracks?: ReplayTracks
1770
+ /**
1771
+ * Glider deploy/stow windows. Omitted (not `[]`) when the log has none.
1772
+ *
1773
+ * A flat list rather than an entity-keyed map: `CBTS_GLIDER` is not
1774
+ * restricted to the squad, so a window can belong to an agent that never
1775
+ * becomes a tracked entity. `agent_addr` is always present, `entity_id`
1776
+ * only when the join resolves.
1777
+ */
1778
+ gliding?: GliderOut[]
1779
+ /** Transformation (mount/tonic/form) windows. Same shape rules as `gliding`. */
1780
+ transformations?: TransformationOut[]
1781
+ /**
1782
+ * Capture-point areas as decoded. Empty on every log written before
1783
+ * arcdps build 20260602, which does not emit the family at all.
1784
+ */
1785
+ captures?: CaptureOut[]
1786
+ /** The renderable projection of `captures`. Neither form reconstructs the other. */
1787
+ decorations?: DecorationOut[]
1788
+ }
1789
+
1790
+ /**
1791
+ * One glider deployment. A MISSING `end_ms` means the glider was still
1792
+ * deployed at the last event in the log -- not that it closed at log end.
1793
+ */
1794
+ export interface GliderOut {
1795
+ entity_id?: number
1796
+ agent_addr: number
1797
+ start_ms: number
1798
+ end_ms?: number
1799
+ }
1800
+
1801
+ /** One transformation window. */
1802
+ export interface TransformationOut {
1803
+ entity_id?: number
1804
+ agent_addr: number
1805
+ /**
1806
+ * The arcdps SESSION-LOCAL id -- meaningless across logs on its own.
1807
+ * `guid` is the portable identity, and is omitted when the log carried no
1808
+ * `CBTS_IDTOGUID` mapping for this id.
1809
+ */
1810
+ transformation_id: number
1811
+ guid?: string
1812
+ start_ms: number
1813
+ end_ms?: number
1814
+ }
1815
+
1816
+ /**
1817
+ * The arcdps "wrbg" capture owner. `white` is unowned/neutral. An owner
1818
+ * index arcdps adds later serializes as `unknown_<n>` rather than folding
1819
+ * into `white`, which is why this is not a closed union.
1820
+ */
1821
+ export type CaptureOwner = 'white' | 'red' | 'blue' | 'green' | string
1822
+
1823
+ /** One capture-point area over its lifetime. */
1824
+ export interface CaptureOut {
1825
+ /** Almost never resolves -- a capture point is a gadget, not a tracked entity. */
1826
+ entity_id?: number
1827
+ agent_addr: number
1828
+ start_ms: number
1829
+ /**
1830
+ * Omitted when the area never got a hide row and no later show superseded
1831
+ * it. Deliberately NOT defaulted to the gadget's last-aware time here --
1832
+ * that substitution is a rendering decision, made in `DecorationOut`.
1833
+ */
1834
+ end_ms?: number
1835
+ original_owner: CaptureOwner
1836
+ /** Omitted when no geometry row ever arrived. Such an area produces no decoration. */
1837
+ shape?: CaptureShapeOut
1838
+ owner_states: OwnerStateOut[]
1839
+ progress_states: ProgressStateOut[]
1840
+ }
1841
+
1842
+ /**
1843
+ * The capture area's geometry, in WORLD coordinates. The circle case is the
1844
+ * arcdps single-point overload (radius around the gadget), not a degenerate
1845
+ * polygon.
1846
+ */
1847
+ export type CaptureShapeOut =
1848
+ | { kind: 'circle'; radius: number }
1849
+ | { kind: 'polygon'; points: [number, number][] }
1850
+
1851
+ /** At `time_ms` the area was held by `from` and being taken by `by`. */
1852
+ export interface OwnerStateOut {
1853
+ time_ms: number
1854
+ from: CaptureOwner
1855
+ by: CaptureOwner
1856
+ }
1857
+
1858
+ /** A run of progress samples sharing one owner pair. */
1859
+ export interface ProgressStateOut {
1860
+ from: CaptureOwner
1861
+ by: CaptureOwner
1862
+ /** `by` is nobody, so the bar is falling back toward `from` rather than being captured. */
1863
+ decaying: boolean
1864
+ /** `[time_ms, percent]`, percent in 0..100 at 2 decimal places. */
1865
+ progress: [number, number][]
1866
+ }
1867
+
1868
+ /** One drawable environment decoration. */
1869
+ export interface DecorationOut {
1870
+ kind: 'capture_outline' | 'capture_progress'
1871
+ /**
1872
+ * Log-relative ms, SIGNED. Unlike every other time in this format this can
1873
+ * be negative by exactly one millisecond: the capture-progress splitter
1874
+ * synthesizes a sample at `time - 1`.
1875
+ */
1876
+ start_ms: number
1877
+ end_ms: number
1878
+ /** World-space `[x, y]` the shape is drawn around. */
1879
+ anchor: [number, number]
1880
+ /**
1881
+ * CSS `rgba(...)`. For a progress bar the two colour slots do NOT have
1882
+ * fixed owner roles: capturing puts the capper here, decaying the holder.
1883
+ */
1884
+ color: string
1885
+ secondary_color?: string
1886
+ shape: DecorationShapeOut
1887
+ }
1888
+
1889
+ /** A decoration's geometry, RELATIVE to `DecorationOut.anchor`. */
1890
+ export type DecorationShapeOut =
1891
+ | { kind: 'circle'; radius: number; filled: boolean }
1892
+ | { kind: 'polygon'; points: [number, number][]; filled: boolean }
1893
+ | { kind: 'progress_bar'; width: number; height: number; progress: [number, number][] }
1894
+
1895
+ export interface SquadSeries {
1896
+ damage: SeriesOut
1897
+ cc_applied: SeriesOut
1898
+ downs: SeriesOut
1899
+ }
1900
+
1901
+ /** Mirrors the legacy `PlayerTargetSeriesOut`, minus `enemy_id` (that's the map key here, joined by entity id). */
1902
+ export interface TargetSeries {
1903
+ damage: SeriesOut
1904
+ power_damage: SeriesOut
1905
+ }
1906
+
1907
+ /** Mirrors the legacy `PlayerPerSecondOut` field-for-field. `per_target` is keyed by the TARGET's entity id. */
1908
+ export interface EntitySeries {
1909
+ damage: SeriesOut
1910
+ /**
1911
+ * The non-condition half of OUTGOING `damage`. In practice present only
1912
+ * on ENEMY rows: no pass computes an outgoing power split for players.
1913
+ * Absent means "no pass measured this", which a zero-filled series would
1914
+ * misreport as "measured, and it was all condition damage".
1915
+ */
1916
+ power_damage?: SeriesOut
1917
+ damage_taken: SeriesOut
1918
+ power_damage_taken: SeriesOut
1919
+ per_target?: Record<string, TargetSeries>
1920
+ /**
1921
+ * `[[time_ms, percent], ...]` -- a STEP function, which is why this is a
1922
+ * plain pair list rather than a `SeriesOut`: re-sampling it onto a fixed
1923
+ * grid would either invent readings between updates or lose updates
1924
+ * inside a bucket. A value holds until the next pair. Absent when the
1925
+ * entity emitted no health updates at all (EI omits `healthPercents` for
1926
+ * such a player rather than writing `[]`).
1927
+ */
1928
+ health_percents?: Array<[number, number]>
1929
+ /**
1930
+ * Cumulative outgoing healing from the arcdps healing extension -- EI's
1931
+ * `extHealingStats.healing1S`. Absent for enemies, and for everyone on a
1932
+ * log with no healing extension.
1933
+ */
1934
+ healing_1s?: SeriesOut
1935
+ }
1936
+
1937
+ /**
1938
+ * `squad` is REQUIRED (see `MissilesBlock`). The squad series is computed
1939
+ * unconditionally; only `by_entity` needs `timeseries: true`.
1940
+ */
1941
+ export interface SeriesBlock {
1942
+ squad: SquadSeries
1943
+ by_entity: ByEntity<EntitySeries>
1944
+ }
1945
+
1946
+ /**
1947
+ * Every statistic block. A block is omitted entirely when `coverage` says
1948
+ * `not_computed`/`unsupported`; an `empty` block is still carried, so a
1949
+ * consumer can tell "computed and there was nothing" from "never ran".
1950
+ */
1951
+ export interface Blocks {
1952
+ damage?: DamageBlock
1953
+ defenses?: DefensesBlock
1954
+ hit_stats?: HitStatsBlock
1955
+ cc?: CcBlock
1956
+ boons?: BoonsBlock
1957
+ support?: SupportBlock
1958
+ contribution?: ContributionBlock
1959
+ healing?: HealingBlock
1960
+ rotation?: RotationBlock
1961
+ damage_mods?: DamageModsBlock
1962
+ missiles?: MissilesBlock
1963
+ replay?: ReplayBlock
1964
+ series?: SeriesBlock
1965
+ conditions?: ConditionsBlock
1966
+ minions?: MinionsBlock
1967
+ }
1968
+
1969
+ /**
1970
+ * axilog's native output format 1.0 container (`axilog_schema::v1::
1971
+ * ReportV1`), as returned by `parseFile`/`parseBuffer` (Task 12).
1972
+ */
1973
+ export interface ReportV1 {
1974
+ axilog: AxilogMeta
1975
+ encounter: EncounterOutV1
1976
+ entities: EntityOut[]
1977
+ catalogs: Catalogs
1978
+ blocks: Blocks
1979
+ coverage: Coverage
1980
+ /** Structured, user-facing analysis warnings. Omitted entirely (not `[]`) when there are none. */
1981
+ warnings?: WarningOut[]
1982
+ }