Raw data reference

What the game's tables and text files hold, how they join, and the rules afkj-parse reads them by. Covers both raw layouts: the AFKJourneyData dump and this repo's extract output.

Contents

  1. About this page
  2. The two raw layouts
  3. Large files
  4. Table format
  5. Localization keys
  6. The array pool
  7. SkillIndex and skill partitions
  8. Hero pipeline
  9. Skill rows
  10. Buffs
  11. Units and lookup tables
  12. EX weapon and refinements
  13. Mastery and stat scaling
  14. Artifacts
  15. Phantimals
  16. Charms
  17. Stat and damage codes
  18. Other tables
  19. Verified facts
  20. Deferred

1  About this page

This page is the field-level reference for the raw game data that afkj-parse reads. It is ported from afkj-data-viewer's docs/raw-data-reference.md and the "Verified facts" list in that repo's CLAUDE.md, with the corrections the Season 8 run (2026-09-25, build 14368) found. The viewer's UI, filter chips, effect tags, text scanning and search are out of scope.

The code that applies these rules is in src/afkj_parse/: the source adapters, which turn either raw layout into plain rows, in source/, and the model, which joins the rows into heroes, artifacts, phantimals, charms and buffs, in model/ (constants with their rationale in model/constants.py). Hand-kept hero lists are in config.json.

Counts drift with every build Row counts and file indexes on this page were measured on the build named next to them, most of them by the viewer on the 1.7.x dump. They illustrate the shape of the data; re-measure before relying on one.
Documented but not implemented Some joins below (summons, the EX item, mastery GsRatio, base stats and per-level scaling) are not part of the lean model. Their sections stay, each marked with a note, so that adding one back is a table read and a module. The viewer's heuristic layer (buff-tag, marker and visibility classification, text scans, chips) is not documented here; it went with afkj-data-viewer, which was retired and deleted.

2  The two raw layouts

The same game data reaches afkj-parse in one of two layouts. The extract output under out/raw/ is the primary source; the AFKJourneyData dump is the fallback, chosen for a whole run with --source ../AFKJourneyData. Both describe the same tables; for the same game build they differ by format, not content.

2.1 The dump: AFKJourneyData/

AFKJourneyData/
├── README.md
├── Tables/                  one JSON file per table (1,670 files in the 1.7.4 dump)
│   ├── Skill/               per-caster skill partitions: Skill_0.json … (858 in 1.7.4)
│   ├── SkillIndex.json      master skill id → partition
│   ├── Skill*.json          SkillBuff, SkillBuffUI, SkillKeywords, SkillCommonRes, SkillEffectConfig, SkillDamagePopup, SkillTutorial
│   ├── Unit.json            unit registry: heroes, monsters, summons, NPCs
│   ├── UnitFaction.json  UnitRarity.json  UnitTag.json  Job.json    enum lookups
│   ├── Exclusive.json  ItemHeroExclusive.json  ExclusiveHeroLevel.json   EX weapon
│   ├── MasteryLevel.json  TavernPool.json  Season.json  Fairy*.json  CampPet*.json  Gem*.json
│   ├── Array.json           the global $arrayRef pool
│   └── …                    many more (Avatar*, Item*, Charge*, Camp*, Plot*, …)
├── Config/                  thin index and navigation JSON
└── Localization/            17 languages, one strings.json each
    ├── cn/  en/  fr/  ge/  id/  it/  jp/  kr/
    └── pl/  po/  ru/  sp/  th/  tr/  tw/  vn/  zh/

The dump is versioned by its git history (for example [1.7.4] Build 2837 | Data 14242) and lags a new game build by days: on the Season 8 cutover day it was a build behind.

2.2 The extract output: out/raw/

out/raw/
├── tables/
│   ├── <Table>.json         { fields[], arrayFields[], rows: { "<key>": row } }
│   ├── Skill/Skill_<n>.json per-caster skill partitions
│   ├── SkillIndex.json      rows { ID, Partition } (Partition is "_<n>")
│   ├── HeroScripts.json     rows { Script, Name, Value }: hero battle script settings (section 10.5)
│   └── _stage.json          { formatVersion, extractor, gameVersion, fileTable, complete }
├── locales/
│   ├── <code>.json          flat { key: text }, same shape as the dump's strings.json
│   └── _stage.json          { formatVersion, extractor, gameVersion, fileTable, from: install | cdn, cdnBuild?, locales }
└── cdn/<build>/            download cache for --locales-from cdn

The install only holds the languages selected in game; the CDN option fetches all 17, verified against the CDN's xxHash64 and the install's file table so that text and tables are one build. Each stage stamps its own directory; out/manifest.json describes only the last invocation and is not a version record. Details are in architecture §9 and integrations §1.

2.3 Where the layouts differ

AFKJourneyData/out/raw/
Table fileTables/<T>.json: { _meta, default, row_keys[], rows[] }; rows are sparse (fields equal to default omitted); mapping tables (SkillIndex, Array) use data: {}tables/<T>.json: { fields[], arrayFields[], rows: { "<key>": row } }; rows complete, defaults applied, verified cell by cell against the game's own Lua accessors
Arrays{ "$arrayRef": N } into Tables/Array.json, or inline 1-based maps { "1": v, "2": v } (section 6)resolved inline as JSON arrays; null holes preserved
Grouped tablesparallel row_keys (the SkillID for Skill partitions, empty for other grouped tables)keys are JSON-array strings ("[1,1]") with keyFields and rowKeyFormat: "json-array" in the file
Skill partitionsTables/Skill/Skill_<n>.json, mapped by data.Skill[masterId] = "_<n>"tables/Skill/Skill_<n>.json, mapped by SkillIndex rows { ID, Partition }
Text fieldsthe localization key where one exists, else the literalthe Chinese source text; the adapter turns it into keys (section 5.3)
LocalizationLocalization/<code>/strings.json, 17 codeslocales/<code>.json, same shape
Hero scriptsnonetables/HeroScripts.json, read from the battle scripts' bytecode (section 10.5)
Versiongit history of the dumptables/_stage.json and locales/_stage.json

Against the 1.7.4 dump, the extract matched Unit in 907 of 923 rows and Skill in 9,896 of 10,319; every difference was a game change between the builds. A cross-source comparison is meaningful only for the same build.

3  Large files

Both trees are large. Never load these whole into a conversation or an editor: grep for a key or a name, stream them in a script, or read the model dump instead. Run ls -lah before reading a file you have not seen, and read at most one or two Skill_<n>.json samples (about 95 KB each) to see the schema. Both source trees are read-only: never write into AFKJourneyData/.

FileAFKJourneyData/ (1.7.4)out/raw/ (14368)Notes
Locale dictsLocalization/<code>/strings.json, 25 to 52 MBlocales/<code>.json, 25 to 54 MBgrep one key; only en and cn stay resident in a run
SkillBuff10 MB7.8 MBgrep by Name or ID
Array10 MBabsent (arrays inline)the $arrayRef pool; load once in a script
ExclusiveHeroLevel4.4 MB3.3 MBonly four fields are read (section 12.2)
UnitEpLevel2.5 MBnot in the measured extractnot read (section 13.2)
Unit1.5 MB1.2 MBborderline; grep for ad-hoc lookups

The dump also carries large tables nothing here reads (SeasonBookResetReward 10 MB, UnitQuality 9.5 MB, Plot 7.9 MB, Gem 7.0 MB, and others). Sizes were measured on 2026-09-26; the extract sizes come from a build 14368 extract.

4  Table format

4.1 The dump's wrapper

Almost every Tables/*.json file shares this wrapper:

{
  "_meta": {
    "table": "...",
    "header_table": "...",
    "format": "flat_rows" | "mapping_table",
    "schema_size": <int>,
    "row_count": <int>,
    "array_fields": [...],            // which fields use $arrayRef
    "unresolved_array_refs": <int>    // count of $arrayRef pointers in this file
  },
  "default":  { /* every field with a typed zero value */ },
  "row_keys": [ /* primary keys, parallel to rows */ ],
  "rows":     [ /* records, rows[i] ↔ row_keys[i] */ ]
}

mapping_table files (SkillIndex.json, Avatar_Index.json, Array.json) replace default, row_keys and rows with a data: {...} object. Three properties matter:

  1. default is the schema. Every field appears with a typed zero value. Rows are sparse: a field equal to its default is omitted, so always read row.get(field, default[field]).
  2. Parallel arrays. row_keys[i] belongs to rows[i]. Index by position, then check the key against the row's primary-key field (for example SkillID).
  3. $arrayRef is global. A field listed in _meta.array_fields is {} (empty), {"$arrayRef": N} or an inline 1-based map. The ref namespace spans the whole export, not one file.

The dump adapter materializes {**default, **row} for every table and checks row_keys[i] against the row's primary key where one exists.

4.2 The extract's tables

Extract rows are complete objects keyed by primary key, with arrays resolved inline. Grouped tables (whose inner keys repeat across groups) use JSON-encoded composite keys such as "[1,1]"; the key fields are also present in each row, so the adapter reads rows.values() as plain rows. null elements in an array are holes the game's data has; the model treats a None element as absent. See architecture §7 for how the tables are recovered.

5  Localization keys

5.1 What a key is

A localization dict (Localization/<code>/strings.json in the dump, locales/<code>.json in the extract) is a flat { "<key>": "<text>" } object, one per language. A key is the xxHash64 (seed 0) of the Chinese source text, written as a decimal string of 1 to 20 digits (for example "18148772017108329122"). In the dump, Skill_92.json's ult DisplayName: "18148772017108329122" resolves in en to "Killer Flush", and the same key resolves in all 17 languages.

The rule for reading one:

5.2 Tokens inside text

5.3 Keys in the extract output

The game's own tables, and therefore the extract output, hold the Chinese source text where the dump holds keys. The rule that reproduces the dump: a top-level string field whose text_key (xxHash64 of the string) is in the cn locale becomes that key; anything else stays literal. It applies to ASCII strings as the dump does (X, Transfer), and never inside arrays, where the only hits are animation names. On build 14368 it reproduced 4,065 of 4,065 keyed fields and kept 1,107 literals (such as 不用翻译 dev names).

The dump also hashes the literal "1" in a few columns (UnitAttr.AlarmType, BuffAffectType, SkillDesIcon, Unit.WalkSpeed, ReselectTargetArgs). That key resolves to nothing and nothing here reads those columns, so the extract adapter leaves them literal.

The extract adapter keeps only the cn key set, not the cn dict, and needs it: without a cn locale in out/raw/locales/ the run stops with --locales-from cdn as the fix.

6  The array pool

In the dump, Tables/Array.json is a mapping_table with two top-level keys:

{
  "_meta": { "table": "Array", "format": "mapping_table", "entry_count": 2 },
  "data": {
    "1": { "1": <v>, "2": <v>, ..., "310327": <v> },        // flat values (heterogeneous)
    "2": { "1": 1, "2": 7, "3": 13, ..., "115083": <values + 1> }  // 1-based start of each array, then a sentinel
  }
}

data["2"][N] is the 1-based start of array N, and the last entry is a sentinel one past the last value. Array N is values[start(N) .. start(N+1) − 1]:

start = data["2"][N]          # 1-based start of array N
end   = data["2"][N + 1]      # start of the next array (the last entry is the sentinel)
array = [data["1"][i] for i in range(start, end)]

This is the layout of the game's own Config/Array.luae (architecture §7.4). data["2"] begins 1, 7, 13 and ends one past the last value.

Not cumulative ends The viewer read data["2"] as cumulative ends until 2026-09-25. That reading shifts every array one element into its predecessor: common_taunt's ControlEffects came out as [uncontrollable, invulnerable, disableAI, taunted] instead of [taunted]. Against the extract's inline values, the starts reading matches 9,896 skill rows and the ends reading 872. Two viewer rules had adapted to the shift (the artifact anchor as the last SkillID, the artifact stat code as the last string of AttrParamN); both are the first element (section 14).

An array field may instead hold an inline map {"1": v, "2": v, ...} with no $arrayRef; its values are read in numeric key order. A field in _meta.array_fields that is neither is a source error. In the extract output every array is already inline, with null holes kept in place.

7  SkillIndex and skill partitions

Skill rows are split into one partition per caster (master skill id). SkillIndex maps a master id to its partition:

// AFKJourneyData: Tables/SkillIndex.json (Config/SkillIndex.json is equivalent)
{ "_meta": {...}, "data": { "Skill": {
  "1":  "_0",      // master skill id 1  → Skill_0.json
  "92": "_87",     // master skill id 92 → Skill_87.json
  "98": "_92",     // master skill id 98 → Skill_92.json (Ravion, "mask")
  ...
}}}

// out/raw: tables/SkillIndex.json
{ "fields": ["ID", "Partition"], "arrayFields": [], "rows": { "1": { "ID": 1, "Partition": "_0" }, ... } }

8  Hero pipeline

8.1 From unit to skills

Unit (UnitType = 1, Enable | TempEnable | AsrEnable, or in config.json heroes.forceParse)
    │  filter: master id present in SkillIndex
    ▼
{ Unit.ID, Unit.Name (codename), Unit.HeroTagID, Unit.UnitRarity, Unit.UnitJob, ... }
    │
    ├─ Unit.DisplayName        → key → display name   [authoritative]
    ├─ Unit.DisplayTitle       → key → epithet (e.g. "Twilight's Burden")
    ├─ TavernPool.UpHeroName   → key → gacha-banner label; fallback only
    │
    ▼  SkillIndex[ Unit.ID ]
Skill_<n>.json                   about 28 rows, one per skill and upgrade level
    │   row.CasterID == Unit.ID
    │   row.SkillName == "<codename>_<slot>[...]"   e.g. "mask_atk", "mask_ult", "mask_atk2_task"
    │
    ▼  per row:
    ├─ DisplayName / Description / SimpleDescription   (keys → text)
    ├─ SArg0..SArgN + PlusAttr / PlusRatio              (interpolated into the description)
    ├─ Buff (name) / BuffID                            → SkillBuff
    ├─ SummonUnitList (array of unit ids)              → Unit   (not implemented, section 9.6)
    └─ VFX arrays (Action(s), CastDisplays, …)         → not read

8.2 Hero names

Unit.DisplayName is the canonical name: a key for every current roster hero. Upstream has shipped raw Chinese literals there ("沙利叶") for heroes that were new or CN-only at the time, and keyed them in a later build, so the name follows a fallback chain:

  1. Unit.DisplayName, if it is a key;
  2. TavernPool.UpHeroName;
  3. a reverse lookup of the Chinese literal in the cn dict (literals of at most 24 characters; on a tie the shortest unpunctuated EN text wins);
  4. the literal.

TavernPool.UpHeroName is a gacha-banner label, not the unit's name, so it is never the first choice. In cn, zh, tw and jp it bundles the title into the name ("浪心剑客-维伦" where DisplayName is "维伦"), and for a collab unit it names only the human partner ("Taichi Yagami" for "Taichi & Agumon"). Hero slugs come from the EN resolution of this chain, so preferring the banner label would slug a hero wrong.

8.3 Roster and enable flags

In the viewer's 1.7.x measurement, 130 of the 907 Unit rows are UnitType == 1; 126 of those have a SkillIndex entry and at least one Enable flag, and 122 are in the roster, the union of TavernPool.TavernUpHeroID and Exclusive.HeroID. The four outside it (mousebro Hammie, mousesis Chippy, soldiermelee, soldierranged) are enabled but neither in the gacha pool nor with an EX weapon: they are acquired through story or event paths.

Hand-kept exceptions live in config.json under heroes:

8.4 Disabled hero records

Unit rows with UnitType == 1, a SkillIndex entry and all three Enable flags false are heroes that exist in the data but are not live. The set grows as new heroes are datamined, so re-list it after every data update rather than trusting a list. Only the rows that explain config entries or common traps are recorded here:

IDCodenameScriptDisplayNameNotes
38generalgeneral霍根 (Hogan)real CN-side hero, disabled on global; a permanent blocklist entry
101lorsanwheellorsanLorsanevent-mode reskin of live Lorsan (ID 74); the wheel* script prefix marks event reskins; its codename collides with the live hero
900HeroNameXXX(empty)小明 (不用翻译)dev placeholder ("Xiao Ming" is the Chinese "John Doe"); recognizable by the dev-marked DisplayName and empty Script

deathknight appears in older notes but has no Unit row, only a stale codename in unrelated tables. The unallocated ids 51, 54, 55, 63, 93, 107 and 112 have neither a Unit row nor a SkillIndex entry (1.7.x).

A standalone listing against the dump (sparse rows, so read each field through default):

import json, os
base = "../AFKJourneyData"
L = lambda p: json.load(open(os.path.join(base, p)))

u = L("Tables/Unit.json"); d = u["default"]
rows = [{k: r.get(k, d.get(k)) for k in d} for r in u["rows"]]
gallery = {r["ID"] for r in L("Tables/GalleryHero.json")["rows"]}  # released hero ids

en = L("Localization/en/strings.json")   # 25 MB; fine in a script, never in a conversation
resolve = lambda v: en.get(str(v)) if v is not None else None

for r in rows:
    if r.get("UnitType") == 1 and r.get("Enable") is False:
        rid = r.get("ID")
        print(rid, r.get("Name"), "rarity", r.get("UnitRarity"), "job", r.get("UnitJob"),
              "inGallery", rid in gallery,
              "EN-name", resolve(r.get("DisplayName")) or r.get("DisplayName"))

Against out/raw/ the rows are already complete: iterate json.load(open("out/raw/tables/Unit.json"))["rows"].values(), and remember that text fields hold the Chinese source text, not keys (section 5.3). To triage a hit:

8.5 Hero interactions live in scripts

Questions like "how does Florabelle's Protective Blessing know to target Cecia's summons?" or "how does Dionel detect a non-permanent buff from an ally?" have no answer in the tables. The behaviour is in script handlers named by Unit.Script (multiplants for Florabelle, godspear for Dionel, thornknight for Cecia's summon), which are Lua bytecode under Src/Battle/Logic/Heroes/ in igame.pkg, not tables. Their logic is not read by the pipeline; Battle scripts reads it for research. The data holds the hooks those scripts read at runtime, and each script's integer settings, among them the buff ids it applies (section 10.5):

HookSource fieldUsed for
is this unit a summon?none: the scripts test unit.config.is_summoned, set at run time (Battle scripts §5.8); Unit.UnitType ∉ {1, 2} was the old viewer's approximationsummon-aware effects (Florabelle, Cecia, Sonja, …); section 9.6
is this buff permanent?SkillBuff.Time >= 9999"non-permanent buff" conditions (Dionel)
is this a stat boost?any of ATK, HP, HAST, … non-zerostat-boost conditions (Dionel)
self or ally?SkillBuff.AddSource ("owner" = self-applied)telling self-applied from ally-applied buffs
which control effects?SkillBuff.ControlEffects[]the Buff → SkillBuffUI chain
can it be dispelled?SkillBuff.CanDispelcounter-play
which skill made this buff?name prefix <codename>_<slot>_…implicit link by shared codename (section 10.2)
which buffs does the hero apply?HeroScripts settings (atk2_buff = 1106)attribution by the hero's own script (section 10.5)

9  Skill rows

9.1 Slot and DisplaySlot

Skill_<n>.json rows have a 121-field schema. Each row has a Slot (internal mechanic id) and a DisplaySlot (the slot the player sees). They disagree systematically; rows are grouped by (DisplaySlot || Slot, DisplayName ref).

SlotDisplaySlotPlayer labelMeaning
00Basic Attack*_atk
11Ultimate*_ult, one row per upgrade level (keys 11 to 15)
22Skill 2*_atk2 (keys 21 to 24)
33Skill 3keys 31 to 34
64Mastery*_mastery, keys 2001 to 2003; joins MasteryLevel
45EX Skill*_atk4, keys 41 to 44, UnlockExclusive 5 / 10 / 15 (the EX-weapon-locked skill)
1 / 2 / 36Awakening*_awake_1, rewrites the matching skill
1000Skill 2 sub-trigger*_atk2b; internal
1010EX Skill sub-trigger*_atk4b; internal
40010Special modes*_imba_1: Honor Duel and Dura's Trials variants. The display name is dev-marked but the Description resolves to real player text shown in those modes
1 / 2 / 31 / 2 / 3 (keys 3001 to 3003)(hidden)per-Skill-Power increments, dev-only
55EX Skill (rare)a few heroes use this directly

With the grouping key DisplaySlot || Slot, sub-trigger rows land at 100, 101 or 102 and are internal; 4001 is the special-modes group; the rest is the kit. The *_imba_1 rows carry mode-specific tuning data (SummonUnitList references, ARM debuffs) that does not describe the main kit.

9.2 Dev rows and IsShow

9.3 Key fields

FieldMeaning
CasterIDint; constant within one partition and equal to its master skill id
SkillIDint; the row key
SkillName<codename>_<slot>[...]; the prefix is the hero codename (mask, chris, fw, …)
DisplayName / Description / SimpleDescriptionkeys (or literals on dev rows)
IsShowthe game's visibility flag (section 9.2)
SkillType"basic", "manual", "passive", …
IsPositiveManual, ActiveTypeactivation flags
Slot, DisplaySlot, FatherSkillSlotplacement
SkillLevelupgrade tier
UnlockLevel / UnlockExclusive / UnlockMastery / UnlockAwakenLevel / UnlockSeasonLevel / UnlockByImbaunlock conditions; UnlockExclusive gates each EX upgrade tier (Florabelle's atk4 tiers at 5 / 10 / 15)
EnhanceAttrwhich attribute the upgrade boosts; observed BAP (Skill Power) and UAP (Ult Power)
CD, InitCD, GlobalCD, CostMP, GainMP, VictimMPcombat economy. CD and InitCD are seconds between casts and before the first; 9999 and above (9999, 99999, 999999, 9999990) mean no timer (passives; energy-gated ultimates, whose CD of 0–3 s is an internal lock). GlobalCD is the lock after any cast (usually 1.25 s). CostMP is the energy a cast costs (1000 on most ultimates; a few cost 200–600, a few Skill 2s 300). GainMP is the energy the skill gives its caster and varies by level. VictimMP scales the energy the target gains (1 normal, 0 none). The feed carries all six in skillNumbers.json (Model & feed §3.10). The hero's Unit row adds StartMP (starting energy, 0–1000), BaseMP (the maximum, 1000) and VictimMPCoef (a multiplier on the energy it gains when hit), which the feed also carries, per hero; StartMP is the skill panel's "Initial Energy" tag on the ultimate
TargetType / TargetShape / TargetCamp / AffectedCamp / AOEOrigin / AOEShapetargeting and area (section 9.4). The first TargetShapeArgs value is the range in tiles the skill panel shows ("Global" from 15 up); the feed carries it as range in skillNumbers.json (Model & feed §3.10)
PlusAttrUnit / PlusAttr / PlusRatioscaling; placeholders such as <ATK>{PlusRatio%} interpolate it
DamageTypeAD, AP, Holy, holy (section 17)
DamageTrigger, DamageTriggerParamdamage formula
SArg0 … SArg20numeric script arguments, interpolated by token name ({SArg0}, {SArg2%}). Rows carry 0 to 20 and descriptions use up to 16, so scan fields by /^SArg(\d+)$/ rather than a fixed range
Buff / BuffIDjoin keys into SkillBuff
SummonUnitList (array)unit ids (section 9.6)
MoveForward, KnockUp, KnockBack, FallFrozenTime, KnockNotFallphysics and control
Action(s), DisplayAction(s), CastDisplays, HitDisplay, Trajectory, SkinId, ResourceListarrays for VFX and animation; not read
ChainJumps / ChainGap / ChainJumpDistance / MaxAffectTimes / ChainEffectschema slots that are never populated

9.4 Targeting fields

9.5 Skill-type codes (UnitBriefBattleConfig)

UnitBriefBattleConfig is the game's own skill-type taxonomy: 495 unit rows, each with up to five SkillNType array fields (N = 1 to 5). Only the string elements of a resolved array are codes; leading integers ([4, 5, 7, 9, 'A_ad']) are positional markers and are ignored. There are only seven codes; an unknown code is dropped and reported.

CodeMeaning
A_adphysical attack
A_apmagical attack
A_ctrlattack with control
C_buffapplies buffs
C_debuffapplies debuffs
D_healheal
D_shieldshield

SkillNType is keyed by internal Slot N, not by DisplaySlot:

FieldInternal SlotDisplaySlot
Skill1Type11, Ultimate
Skill2Type22
Skill3Type33
Skill4Type45, EX Skill
Skill5Typeraremerged into 5

Example: Florabelle (multiplants) Skill4Type = ['D_heal', 'D_shield'] is her EX Skill (Protective Blessing, heal and shield on summons), matching the game. Because the viewer misread the array pool (section 6), its skill-type codes on dump input were wrong until 2026-09-25.

9.6 Summons (SummonUnitList)

Not implemented Not implemented by afkj-parse; documented for future use. afkj-data-viewer, whose scripts/build-data.ts implemented it, has been deleted, so this section is the record.

SummonUnitList is an array of unit ids, heterogeneous: it can include slot-marker strings such as "atk2b", which are ignored. It also holds heroes' unit ids as tuning data and NPC-typed obstacles, so keep only entries that resolve to a Unit row with UnitType ∉ {1, 2}:

This is a table-side approximation. In the game's code a summon is a unit whose config.is_summoned is true, set by AddSummonUnit and cleared for starting companions (Battle scripts §5.8).

Known gaps of this rule: Daimon's daimonghost is on a slot-0 dev row, which the dev filter drops (section 20); Sonja summons only in her imba_1 special-modes row. Several summoners (Zanie, Damian, Berial, Bryon, Galahad, Phraesto) create their units from Unit.Script with no SummonUnitList entry at all, so no table rule finds them.

10  Buffs

10.1 SkillBuff: the buff registry

3,266 buff rows in the viewer's measurement, ids 1 to 600,001, in a 117-field schema. Names are codename-prefixed (mask_atk2_task, chris_atk2_stun).

FieldMeaning
Name / IDidentity; the only name is this codename, with no localized title field
Timeduration in seconds; Time >= 9999 is permanent
IsDot, DotIntervaldamage over time and its tick interval
ControlEffects (array)control-state names the buff carries ("stun", "silence", …)
stat fields (HP, ATK, ARM, MR, CRIT, HAST, LFS, …)flat modifier
<stat>_a / <stat>_xadditive (ATK_a: ATK adds N) and multiplicative (ATK_x: ATK × (1 + N); -0.3 reads as -30 %) variants
AddSource"owner" = self-applied
CanDispel, MaxStackTimes, ShieldType, HidePopup, ClearOnCasterDeathdispellable, stack cap, shield kind, popup suppression, removal on the caster's death
BuffType0 common and system buffs (common_stun, commonDash, faction defence; about 80 %), 1 positive-leaning hero buffs (about 14 %), 2 a mixed bucket (about 5 %); not a clean self / ally / enemy classifier
visual (Effect, Puppet, EffectBone, Prefab*, …)rendering; not read

For buffs.json, stats keeps the 21 base stat codes with their _a and _x variants (TakeDmg* and MakeDmg* excluded), and only records a hero links, files by name or names in its script are kept (section 10.6).

10.2 Codename-prefixed passives

A skill row often has Buff: null, BuffID: 0, SkillScript: null yet clearly does something (Florabelle's Protective Blessing grants permanent shields to summons). The mechanism is a passive buff whose name starts with the hero's codename, applied by the script handler at runtime. No skill row references these buffs; they are linked by the naming convention, and the hero's battle script, which applies them, names most of them by id (section 10.5). In the viewer's count, about 960 such buffs across 113 heroes, against 46 directly referenced.

Because the name link is a convention and not a fact, it can misassign. The feed's buffs.json keeps the sources apart: attached (a row links the record), inferredPassives (records whose name starts with <codename>_, case-insensitive) and scriptBuffs (the hero's script names the record).

Florabelle (multiplants):

BuffTimeDistinctive fieldsLikely role
multiplants_atk4_shield9999ShieldType: 'common'permanent shield on summons (Protective Blessing)
multiplants_globalBuff9999HidePopup: true, ControlEffects[…]hero-wide passive applying control immunities
multiplants_enterBuff9999HAST non-zero, HidePopup: trueon-entry haste
multiplants_atk4_mega8AddSource: 'owner'temporary self-buff during atk4

For Dionel (godspear), the stack gained on receiving a non-permanent ally stat boost is godspear_atk3_passivebuff: Time: 9999, MaxStackTimes: 3, AddSource: 'owner', ATK_a: 'ATK'. Whether to grant a stack is decided by the godspear script.

10.3 Slot tokens

Passive names follow <codename>_<slotToken>_<rest>. The rest of the name after the codename is split on _, case-insensitive, and the first token in a priority-ordered list that starts a segment wins; specific tokens win over general ones, so <hero>_awaken_atk2buff maps to Skill 2, the skill it modifies, not to Awakening.

Token (priority order)DisplaySlotLabel
atk4 / atk55EX Skill (wins over awake and ult)
atk33Skill 3
atk22Skill 2
ult1Ultimate
awake / awaken6Awakening
mastery4Mastery
imba4001Special modes
atk (fallback)0Basic Attack

A codename-prefixed name with no token is hero-wide (<hero>_globalBuff, <hero>_enterBuff, concept names like oldsheep_ThornBuffShield or meerkat_aroma). Coverage in the viewer's count of 960 passives:

slot 1 (Ultimate)   210   slot 4 (Mastery)           13
slot 2 (Skill 2)    167   slot 0 (Basic Attack)       6
slot 3 (Skill 3)    162   slot 4001 (Special modes)  50
slot 5 (EX Skill)   214   slot 6 (Awakening)         43
                          no token (hero-wide)       95   (about 10 %)

10.4 SkillBuffUI: control-effect popups

17 rows: stun, frozen, silence, disarm, fear, taunted, unmanual, unheal, noAddMp, twine, immobilize, invulnerable, uncontrollable, blind, suppress, sleep, unshield. Each row has BuffEffectName (the name ControlEffects uses), PopupText (a key: "Stunned", …), PopupIcon, IsBuff (positive or negative) and PopupType.

10.5 HeroScripts: buff ids in hero battle scripts

A hero's battle script (Src/Battle/Logic/Heroes/<script>.luae in igame.pkg, where <script> is the unit's Unit.Script) sets its tuning as module-level integers and applies buffs by those ids. The extract's tables stage records every such name = <integer> setting in out/raw/tables/HeroScripts.json (game.hero_script_table), one row {Script, Name, Value} per setting, in the grouped form (keyFields: [Script, Name], rowKeyFormat: "json-array"). The settings are read from the bytecode (formats/luadis.py integer_assignments: a store of a constant, or of a register just loaded with LOADI/LOADK), and the script is never run; a later store wins, as at run time. The table is written by the same stage and under the same stamp as the Config/ tables, so both are one build. Build 14368 has 610 scripts and 2,898 rows. The AFKJourneyData dump has no such table.

{ "Script": "rs", "Name": "atk2_buff",       "Value": 1106 }   // rs_atk2_shield
{ "Script": "rs", "Name": "ult_controlbuff", "Value": 1112 }
{ "Script": "rs", "Name": "ult_buffid",      "Value": 3 }      // common_taunt

A setting names a buff when its name contains buff (case-insensitive), the name is not a quantity (time, duration, a trailing dur, stack, count, num, value, coef, ratio, rate, interval, max, min, chance, percent; BUFF_QUANTITY), and its value is a SkillBuff id. atk2_debuffDuration = 5 is five seconds, not record 5, and ult_darkBuff_maxStacks = 9 is a cap. Small ids are real shared buffs: 1 is common_stun, 3 is common_taunt.

In build 14368, hero scripts name 885 buff ids; 837 of the 1,102 records filed by name (section 10.2) are among them, and 14 records no naming rule reaches (shared ones such as common_taunt) join buffs.json this way. Lumont (rs)'s script names 1106 rs_atk2_shield (the other record with that name, 1101, belongs to an enemy), 1107, 1108, 1111, 1112 and 3 (common_taunt); the pair 1109/1110 (rs_atk2_debuff), which no script names, stays only inferred.

10.6 How afkj-parse links buffs

The output shape is Model & feed §3.9; the model's records are §2.10.

11  Units and lookup tables

11.1 Unit

907 rows in the viewer's 1.7.x measurement. UnitType: 1 hero (130), 2 NPC (7), 3 monster (363), 4 summon-only puppet (117), 5 other (15), null summon (275). For heroes:

FieldMeaning
IDthe master skill id
Namecodename ("mask")
DisplayNamea key, occasionally a raw Chinese literal (section 8.2)
DisplayTitle, Descriptionkeys: epithet, flavour text
Enable / TempEnable / AsrEnablelive flags
UnitType1 for heroes
UnitRacenot the faction; use HeroTagID
UnitJob, UnitRarity, HeroTagIDcodes into Job, UnitRarity, UnitTag (faction)
Gender"male" / "female"
SeasonTypenon-null for season heroes
DamageType, IsMelee"AD" / "AP"; bool
HP / ATK / ARM / MR / BaseMP / StartMP / BaseCRIT / GrowthValue / AttrIncreaseRatiobase stats (section 13.2)
Scriptthe script handler (section 8.5)
IconSquare / UltIconSquare / HeadEntranceIconimage paths
DisplaySkillIDalways 0 for heroes; never read it, use Unit.ID

UnitDetail maps UnitID → PagePuppet, which confirms codenames; it is not needed.

11.2 Enum tables

TableKey fieldHero fieldValues
JobNameUnitJob1 Tank, 2 Warrior, 3 Mage, 4 Support, 5 Marksman, 6 Rogue
UnitRarityNameUnitRarity1 S, 2 A, 3 Rare, 4 Common, 5 (blank; special or NPC tier). The localized names are uneven
UnitTagTagHeroTagID1 Lightbearer, 2 Mauler, 3 Wilder, 4 Graveborn, 5 Hypogean, 6 Celestial, 7 Other, 8 Dimensional

UnitTag has both a Tag and a Name field per row and they do not agree: key by Tag, which is what HeroTagID matches. DisplayName on enum rows is sometimes a key and sometimes raw Chinese.

11.3 TavernPool

One row per gacha banner: TavernUpHeroID (= Unit.ID) and UpHeroName (a key, the banner label). 68 distinct hero ids in the viewer's measurement. Used for roster membership and as the second step of the name chain.

11.4 SkillKeywords: the glossary

57 entries (weakest, strongest, frontest, backest, dotDmg, meleeDmg, nukedmg, lonely, fatal, invincible, …), each with DisplayContent (a key or a Chinese literal; the friendly name), DisplayTips (a key; the tooltip body) and an optional DisplayPic. Descriptions reference them as [name]label[/] ([invincible]invincible[/], [weakest]the weakest[/]). DisplayPic exists for the positional keywords (frontest, backest, sameline, symmetric, frontonehex, backonehex, centralaxis, standardline), but the dump carries only the paths, not the images.

11.5 UnitAttr: the stat dictionary

52 stat codes (ATK, ARM, MR, HP, MP, HEAL, CURE, ATKHAST, LFS, CRIT, RES, HAST, BLOCK, PT, HalfHp, SUP, CritP, CritD, MpD, MpP, DebuffP/D, PRFC, PVP*, DmgP/D, MakeDmgMulti/Division, TakeDmgMulti/Division, DEF, BAP, UAP, MakeHpLoss*, TakeHpLoss*, ATKHASTIgnUnctrl, HASTIgnUnctrl). Each row has Name (the code), Tips (full name; a key or a Chinese literal), SimpleTips, Description (a key), Icon / BuffIcon / DebuffIcon paths and an IsPvPAttr flag. afkj-parse does not read it: the Refine 4 labels come from a fixed EN table (section 12.4).

12  EX weapon and refinements

12.1 Exclusive and ItemHeroExclusive

Not implemented The EX item (from Exclusive and ItemHeroExclusive) is not implemented by afkj-parse; documented for future use. afkj-data-viewer, whose scripts/build-data.ts implemented it, has been deleted, so this section is the record.

Exclusive: 124 rows, one per EX-bearing hero.

An Exclusive row for a hero also signals release-readiness (section 8.4). ItemHeroExclusive: 99 thin (HeroID → ItemID) rows. Older heroes use a different linkage and may be missing, so not all 124 EX-bearing heroes are covered.

12.2 ExclusiveHeroLevel

Per hero and EX level: 3,780 rows (30 levels × 126 heroes) in the viewer's measurement, 51 fields:

{ HeroID, Level,
  GsRatio, GsConst, PVPGsRatio,
  HP_PR, ATK_PR, ARM_PR, MR_PR,
  HEAL, CURE, ATKHAST, LFS, CRIT, RES, HAST, BLOCK, PT, HalfHp, SUP,
  CritP, CritD, MpD, MpP,
  PVPHEAL, PVPCURE, PVPATKHAST, PVPLFS, PVPCRIT, PVPRES, PVPHAST,
  PVPBLOCK, PVPPT, PVPHalfHp, PVPSUP, PVPCritP, PVPCritD,    // Refine 4 reads these
  PVPDmgP, PVPDmgD, PVPMpD, PVPMpP, PVPDebuffP, PVPDebuffD,
  Attributes (array),              // ARM / MR per-faction modifiers
  SkillIDs (array),                // changes at levels 5 / 10 / 15: the EX skill-unlock ladder
  ExtraSkill, ExtraSkillCaster,    // Refine 2: the rivalry skill
  SignatureRes,
  UpgradeCosts (array),            // material cost to reach this level
  CommonMaterialCost (array),
  TotalCosts }

For Valen (HeroID 1) the SkillIDs ref changes at Lv0 (ref 406), Lv5 (73627), Lv10 (73628) and Lv15 (73581); each transition is a new EX skill becoming active. The source is ambiguous on the _PR suffix: the viewer's schema note calls it "per rank", while in FairyLevel a trailing PR marks a percentage. Only the refinement inputs are read: ExtraSkill and ExtraSkillCaster for Refine 2, the PVP* codes at levels 28 and 29 for Refine 4.

12.3 Refinements: Refine 2 (rivalry skill)

Beyond the level-15 ladder, the EX panel shows two refinement tiers, appended after the kit ladder of the EX slot. Refine 1 (EX +26) and Refine 3 (EX +28) are skipped because the game skips them: those rows only raise values of stats already introduced.

Refine 2, rivalry skill (EX +27). ExtraSkill with ExtraSkillCaster (4031, the rivalry pseudo-caster; it must be a single value across heroes) keys into that caster's partition (Skill_618.json in build 14368, found through SkillIndex), rows 101 to 106: Tank, Support, Marksman, Mage, Rogue, Warrior, one row per class with one level each. The row's DisplayName reads "Rivalry Skill: Rogue" and so on; its description interpolates like any skill row.

12.4 Refinements: Refine 4 (new attributes)

Refine 4, new attributes (EX +29), is derived: per hero, compare the level-28 and level-29 rows of ExclusiveHeroLevel and collect the PVP* codes that flip from 0 to above 0. Each hero gets one to three codes. The candidates are the UnitAttr rows flagged IsPvPAttr that ExclusiveHeroLevel has a column for, in column order: 19 on build 14368 (PVPPT, PVPHalfHp, PVPHAST, PVPATKHAST, PVPCRIT, PVPCritP, PVPCritD, PVPRES, PVPBLOCK, PVPCURE, PVPLFS, PVPHEAL, PVPSUP, PVPDmgP/D, PVPMpD/P, PVPDebuffP/D). The level has no source row and no description key; its text is the localized template 15370264355377780556 ("<color=${1}>Refine ${2}: In Rivalry Mode, increases ${3}.</color>") with ${2} the tier ("4") and ${3} a comma-joined list of <value> <label>; colour tags are stripped. The template is localized, and so are the stat names: each is its UnitAttr.Tips (PVPPT "DEF Penetration" / 防御穿透, PVPHalfHp "Execution" / 终结). The viewer used a fixed English table for them in every locale; its English equals the game's for all 19 stats.

13  Mastery and stat scaling

13.1 MasteryLevel

Not implemented Mastery GsRatio from MasteryLevel is not implemented by afkj-parse; documented for future use. afkj-data-viewer, whose scripts/build-data.ts implemented it, has been deleted, so this section is the record.

369 rows = 123 heroes × 3 mastery tiers. Per row: (UnitID, MasteryLevel), MasterySkillID (matches skill row keys 2001 to 2003), Cost (array), GsRatio (gear-score gain, typically 0.06 / 0.08 / 0.10) and RecordScore. It mostly repeats the mastery rows in the skill partition; GsRatio is its own contribution.

13.2 Base stats and per-level scaling: UnitLevel, UnitEpLevel

Not implemented Base stats and per-level scaling (UnitLevel, UnitEpLevel) are not implemented by afkj-parse; documented for future use. afkj-data-viewer, whose scripts/build-data.ts read them, has been deleted, so this section is the record; the per-level formula below was never implemented there either.

Both are generic per-level tables, not per hero. UnitLevel (420 KB): 800 rows (Level 1 to 800) with Rank, Season, AddBase (level-base multiplier), RankAddBase, TotalAddBase, GsRatio, AddPRFC, AddCRIT, AbyssAddBase and the UpToCost / UpToTotalCost arrays. UnitEpLevel (2.5 MB): 3,000 rows of ascension (EP) scaling, adding PVE fields (AddPVEDmgP/D, AddPVEMpP/D, AddPVEDebuffP/D, PVEGSCoef).

The viewer's notes gave a hero's stats at level N as Unit.HP / ATK / … × UnitLevel[N].AddBase, plus the ascension contribution from UnitEpLevel. It was never implemented, so the formula is unverified. UnitRank has the same shape as UnitLevel and plateaus at rank 24.

14  Artifacts

Artifacts, the equippable battle Spells (Awakening, Starshard, …), are internally "Fairy".

Registry: Fairy (127 rows): FairyID, DisplayName (a key, "Awakening Spell"), SeasonType, FairyType, Icon, MinLevel / MaxLevel, SkillID (array). Two sets are read:

SetFilterCountLevelsFairyIDs (Season 8)
pre-seasonSeasonType == 1 && FairyType == 1631, 2, 3, 5, 7, 9
seasonSeasonType == the current season1278001 to 8012

Other FairyTypes are out of scope: 2 duel charms (Golden Blooms, …, DuelEnable), 3 hero-specific picks ("Reinier's Grasp"), 4 single-level quick-pick variants.

The current season is derived, never hard-coded. In Season, take the online row (IsOnline == true) with the highest SeasonType > 100; SeasonType 1, also online, is the always-available base game. Numbering is offset: Season labels SeasonType 108 "Season 7" (Foundation Season and Seasons 1 to 6 precede it), asset paths say ep8, and stargazer numbers it 8. Compare only the raw SeasonType, never a display number. Extract uses the same rule.

Skills are in the leaderavatar caster file. Caster 4009 → SkillIndex → Skill_<n>.json (Skill_602.json in build 14368, about 920 rows keyed by SkillID). Rows interpolate like hero skill rows. A skill's own DisplayName ("Radiant Life") differs from the artifact name for the pre-season set; for the season set they are identical.

Anchor and family. The artifact's anchor is the first element of the resolved Fairy.SkillID (Awakening [101, 102, 103], Candleflame [8901 … 8907]): its level-1 skill. The family is every row sharing that row's Slot and SkillName, sorted by SkillLevel; one artifact is one (Slot, SkillName) family. Slot alone does not identify an artifact: the next season's skill rows land in the leaderavatar file one patch before their Fairy rows exist and reuse the live season's Slot numbers. In 1.7.4, Season 8's leaderavatar_candlelight (8901 to 8907) shares Slot 87 with Season 7's leaderavatar_ep7healwave (7701 to 7707), and likewise across all twelve season slots (87 to 98). Every SkillName lives in exactly one Slot, so the pair is unambiguous.

Stat bonuses. An equipped artifact also grants stats that scale per level, from FairyLevel (766 rows, per (FairyID, Level)). The displayed bonuses pair AttrParam1..4 with AttrValue1..4; the stat code is the first element of AttrParamN, and a following PR marks a percentage (["ATK", "PR"]). They mirror the named *_PR and flat stat fields on the row. Only the max-level set is read (Fairy.MaxLevel: 10 pre-season, 30 seasonal). Codes seen: ATK, ARM, MR, HP, HAST, ATKHAST, DEF, PT, BLOCK, HEAL, LFS.

The viewer took the last element for both the anchor and the stat code; those rules compensated for its array misread (section 6) and were changed to the first element on 2026-09-25.

Icons. Fairy.Icon is an asset path; the dump does not ship the asset. This repo's extract exports the art (images §6).

15  Phantimals

Phantimals, the seasonal summon companions, are internally "CampPet".

Roster: CampPet, five rows, one per camp slot, listing the current season's five by CampPetUnitID (Season 8: 5116 to 5120, Gervan, Seralyth, Crystal Beetle, Plague Creeper and Wedge of Matter, whose CampPetSubUnitID 5078 is its companion). Exactly that roster is read. Each unit id resolves to a Unit row (UnitType == 3); its skill file's camppet codename prefix (camppetcolossuslight_ult, …) is checked as a guard.

Faction is HeroTagID, never CampPet.Camp A phantimal's faction is its unit's Unit.HeroTagID: 1 Lightbearer, 2 Mauler, 3 Wilder, 4 Graveborn, 5 Hypogean, 6 Celestial. CampPet.Camp is a slot, not a faction: camp 1 human, 2 elf, 3 orc, 4 undead, 5 goddevil (Hypogean and Celestial combined). In Seasons 7 and 8 camp 2 held the Wilder and camp 3 the Mauler, and three of five hand-entered factions taken from the camp were wrong (Seralyth Wilder, Crystal Beetle Mauler, Wedge of Matter Celestial).

The full roster across seasons is 15 phantimals in three faction-complete sets of five (unit ids 5101 to 5105, 5106 to 5110, 5111 to 5115), all findable by the camppet skill prefix. Their Unit rows carry no usable SeasonType, so there is no per-unit season number to group by; an archive would iterate camppet-prefixed UnitType == 3 units instead of the CampPet roster.

Skills are hero-shaped. Each phantimal's partition (master id → SkillIndex) holds its abilities plus dev and internal rows. Keep the shown, non-dev rows, group them by DisplaySlot, sort levels by SkillLevel. Levels 2 and up are upgrade deltas ("Increases base damage to …"). The skill's own DisplayName ("Radiant Slash") is the heading; phantimals do not use the hero slot labels.

Five shown skills each. Ultimate, Skill 2, Phantimal Focus, a Spirit Mark and a fifth ability. IsShow false rows are internal sub-skills (Slot or DisplaySlot 100 and up): Season 8's Crystal Beetle carries a sixth, Enchanted Mist (camppetsandboss_atk2b, Slot 100, no Description); Gervan's ultb (Slot 101) is the same kind and is also dev-marked.

The basic attack is hidden too. It is the row at Slot 0 (camppetsandboss_atk). Its first TargetShapeArgs value is how far the phantimal attacks (Season 8: 1 for Seralyth and Crystal Beetle, 20 for the other three), and Unit.DamageType says whether those attacks are physical or magic.

CampPetPhase (6 phases × 5 camps) and CampPetLevel hold per-phase and per-level scaling (phase unlocks gated on IsSeasonPhase(N)); not read. Unit.IconSquare's asset is not in the dump; this repo's extract exports phantimal art (images §6.5).

16  Charms

Charms, the hero-equippable Magic Charms, are internally "Gem".

Link table: GemSuit (1,269 rows). Each row is (HeroID, SuitUnlockQuality 4..12, CasterID 4022, SkillID), the hero-to-charm link. SkillID = base × 100 + SkillLevel, so the nine qualities collapse pairwise onto four skill levels (4 to 5 unlock level 1, 6 to 7 level 2, 8 to 9 level 3, 10 to 12 level 4). Some heroes have SkillID 0 (no charm): released heroes without one (Guywin 39, Joey 40) and dev, disabled or unreleased records; those links are skipped. The charm set is whatever GemSuit references with SkillID ≠ 0 (19 bases, 120 to 138, in Season 8); nothing derives from a season literal, so a season rollover needs no code change.

Skills are in one shared caster file. Caster 4022 → SkillIndex → Skill_<n>.json (Skill_610.json in build 14368, 522 gemskill_* rows keyed by SkillID). The build fails if an emitted base does not resolve exactly SkillLevels 1 to 4, or if two heroes sharing a base disagree on the quality-to-SkillID mapping.

Interpolate each row. Adjacent tiers can share one Description key and differ only in SArg values (SkillIDs 11501 and 11502 share a key with SArg1 25 and 30). Never dedupe tiers by description key.

Tier names: GemQuality (12 rows; Name is a key for qualities 3 and up). The four qualities that first unlock a level name the tiers: 4 "Elite", 6 "Epic", 8 "Legendary", 10 "Mythic"; the "+" qualities between reuse the previous level's skill.

No player-facing names. Every charm row's DisplayName is dev-marked ((不用翻译)…); only descriptions localize. A charm is identified by its slug, the SkillName minus the gemskill_ prefix (gemskill_ep7mpregen → ep7mpregen).

17  Stat and damage codes

Codes seen in EnhanceAttr, PlusAttr and DamageType. There is no in-game table for these meanings; they are inferred from usage and the community's vocabulary.

CodeMeaningWhere seen
BAPSkill PowerEnhanceAttr (253 rows)
UAPUlt PowerEnhanceAttr (126 rows)
ATKAttackPlusAttr, <ATK>
HPHealthPlusAttr, <HP>
ARMArmorPlusAttr, <ARM>
MRMagic ResistPlusAttr, <MR>
CRITCrit ChancePlusAttr
CritPCrit PowerPlusAttr
HAST / ATKHASTHaste / Attack HastePlusAttr
LFSLifesteal (the game's text says "Life Drain")PlusAttr
HEAL / CUREHealing / CurePlusAttr
DODGDodgerare
RESTenacityrare
DmgD / DmgPDamage Reduction / BonusPlusAttr
BLOCKBlockPlusAttr
GainMpRatioMP gainPlusAttr
TeamATK / TeamHPteam-wide statsPlusAttr
PTPenetrationPlusAttr
SUPSupportPlusAttr
ADPhysicalhero and skill DamageType
APMagicalhero and skill DamageType
Holy (capital)true damageskill DamageType
holy (lowercase)an HP-drain mechanic, not true damageskill DamageType; only Thoran's "Soul Plunder"
TRTrueimplied by the schema; not seen in data

DamageType is not a reliable sole signal: the viewer found 142 skills whose description says "true damage" with DamageType: null.

18  Other tables

Not read; schemas noted so they need not be re-investigated.

19  Verified facts

Each of these has been paid for once; do not re-investigate.

20  Deferred

Work that was considered and deliberately left, with what would reopen it.