The pipeline processes raw SWTOR client assets into structured JSON node dumps, combat capability profiles, mirror-matching databases, and tabular CSV indexes.
SWTOR Client Archives (.tor) + Jedipedia Definition Tables (gom.js, fnv1a64.js)
│
▼
[ 1. Pipeline Orchestrator (main.py) ]
│ ├── extract_relevant_files()
│ ├── traverse_combat_graph()
│ └── write_node_dump()
│
┌───────────────────────────┴───────────────────────────┐
▼ ▼
Raw Node Tree (/abl, /tal, /apc) Initial Pass-1 CSV
(data/extracted/) (ability_data.csv)
│ │
└───────────────────────────┬───────────────────────────┘
▼
[ 2. Analytical Engine (matching.py) ]
│ ├── find_spec_exclusive_abilities()
│ ├── extract_all_matching_attributes()
│ └── run_mirror_matching()
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
spec_exclusive_abilities.json spec_abilities_ spec_abilities_strict_
matching_data.json cascade_mirrors.json
│ │
└───────────────────────────┬───────────────────────────┘
▼
[ 3. Finalization (main.py) ]
│ ├── Pass-2 CSV Injection (mirror_fqn, exclusive_to_spec)
│ └── extract_icons() (Texture asset extraction)
│
▼
Final Production Artifacts
main.py controls extraction, graph dependency resolution, attribute parsing, and final file persistence.
- Entry Points: Discovers roots across disciplines (
dis.), advanced player classes (apc.), baseline origin story abilities, and item/relic/adrenal definitions. - Dependency Walking:
traverse_combat_graph()recursively walks references (effEffect,ablEffectIDs,conEntitySpec) while resolving FNV-1a 64-bit hashed tags and GOM field identifiers. - Disk Output:
write_node_dump()structures nodes into categorized folders (/abl,/tal,/apc,/dis).
Inspects the resolved field tree and action definitions to classify combat behavior:
# Field Identification Constants
NAME_FIELD_ID = "4611686102842470023" # locTextRetrieverMap
GCD_FIELD_ID = "4611686019453829630" # ablGlobalCooldownTime
IS_PASSIVE_FIELD_ID = "4611686019453829615" # ablIsPassive
COMBAT_ID_LOOKUP_KEY = "15685385242400905286" # Global Combat ID key inside locTextRetrieverMap- Attack Type Classification:
- Melee / Ranged: Triggered if
effAction_WeaponDamageis present in sub-effects, evaluated againstablCombatMode(staCombatModeMeleevs.staCombatModeRanged). - Tech / Force: Triggered if
effAction_SpellDamagecontainseffParam_SpellTypewith value"2"(Tech) or"3"(Force). - Combines into compound strings (e.g.,
Melee/Tech,Ranged/Force, orNone).
- Melee / Ranged: Triggered if
- Passive Evaluation (
is_passive): Checks ifablIsPassiveis present and explicitly set toTrue. - Global Combat ID (
global_combat_id): TraverseslocTextRetrieverMaplooking for lookup key15685385242400905286and extracts the associated 64-bit integer ID string. - Tactical Flags: Inspects effect action blocks to flag
is_interrupt(effAction_AbilityInterrupt),grants_absorb(effAction_AbsorbDamage),its_revive(offer_revive), andignore_alacrity.
- Pass 1 (Bootstrap): Writes
ability_data.csvwith resolved names and tooltips. This output is consumed bymatching.pyfor semantic text comparisons. - Pass 2 (Enrichment): Reads the generated
spec_exclusive_abilities.jsonandspec_abilities_strict_cascade_mirrors.jsonto populatemirror_fqnandexclusive_to_spec.
matching.py performs data mining on the extracted JSON graph without modifying raw assets.
Identifies abilities and passives locked to exactly one discipline across the entire game.
-
Step 1 (Global Aggregation): Walks
/apcpackages, isolating non-base abilities across all Advanced Classes and disciplines intoglobal_ability_spec_counts. -
Step 2 (Exclusivity Filtering): An ability is marked exclusive if and only if its global appearance count equals 1:
$$\text{Global Spec Count}(A) = 1 \quad \text{and} \quad A \notin \text{Base Class Abilities}$$ - Shared Discipline Handling: Shared archetype abilities (e.g., Creeping Terror appearing in both Hatred Assassin and Madness Sorcerer) register a count of 2 and are excluded from the single-spec mapping.
-
Replacement Mapping: Captures replacement chains from
ablAbilityReplacementInfo.jsonto link baseline abilities with their upgraded counterparts (e.g., Serrated Bolt replacing Charged Bolts).
Constructs an analytical database (spec_abilities_matching_data.json) across every ability and talent node:
- Combat Invariants: Cooldowns, cast times, channel durations, energy/force costs, max range, and target arc.
- Talent Modifiers: Extracts
talStatInfoStatenums and numerictalStatInfoStatValueentries fromtalRankList. - Sub-Effect Disk Fingerprints: Measures child file counts and byte sizes (
<slug>_X_Y.json) to quantify the underlying effect tree. - Description Tokens: Normalizes token types and multiplier tuples from
ablDescriptionTokens.
Mirror matching runs through an elimination cascade. Candidate pools are filtered through sixteen ordered criteria.
Incoming Candidate Pool (Opposing Faction)
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Structural Checks │
│ • replacement_graph_inversion │
│ • shared_cooldown_timers │
│ • exact_base_slug │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 2. Combat & Talent Profiles │
│ • talent_rank_stats / talent_root_stats / talent_values_only │
│ • ability_complete_profile / ability_core_combat_stats │
│ • range_and_arc / energy_cost │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 3. Semantic & Filesystem Heuristics │
│ • tooltip_similarity (with numeric/Jaccard tie-breaking) │
│ • child_files_disk_profile (with 2% gap disambiguation) │
│ • description_tokens / action_names │
│ • root_file_disk_size / shared_icon_exact │
└─────────────────────────────────────────────────────────────────────┘
│
▼
Candidate Pool Size Evaluated:
• Size == 1: Verify Reverse Pass
• Size > 1: Apply Next Filter
• Size == 0: Reject Filter Step
| Priority | Filter Identifier | Invariant & Logic Applied |
|---|---|---|
| 1 | replacement_graph_inversion |
Resolves deduced replacements bridged from Pass 1 matches. |
| 2 | shared_cooldown_timers |
Match on shared numeric cooldown timer spec ID: |
| 3 | exact_base_slug |
Match on identical terminal FQN slug (e.g., abl.trooper.guard abl.bounty_hunter.guard). |
| 4 | talent_rank_stats |
Exact equality of sorted stat modification enums and numeric values. |
| 5 | talent_root_stats |
Exact equality of root talStatType and talStatValue. |
| 6 | talent_values_only |
Fallback matching sorted modifier values when stat enums are generic. |
| 7 | ability_complete_profile |
Exact match on cast time, channel, cooldown, cost, range, arc, target rule, and combat mode. |
| 8 | ability_core_combat_stats |
Exact match on cooldown, cast, channel, and range. |
| 9 | range_and_arc |
Exact match on max_range and target_arc. |
| 10 | energy_cost |
Match on identical non-zero resource consumption. |
| 11 | tooltip_similarity |
Semantic Jaccard text overlap |
| 12 | child_files_disk_profile |
Identical child counts, individual file sizes |
| 13 | description_tokens |
Exact sequence match of description token types and multipliers. |
| 14 | action_names |
Exact match on sub-effect action name lists. |
| 15 | root_file_disk_size |
Root JSON file size variation |
| 16 | shared_icon_exact |
Match on identical .dds texture asset references. |
-
Tooltip Tie-Breaking: If multiple candidates pass
tooltip_similarity, ties are broken by:- Exact matching of ordered numeric sequences extracted from the tooltip text.
- Exact set equality of numeric tokens.
- Top-ranked candidate selection if its Jaccard score leads the runner-up by
$\Delta J \ge 0.05$ .
-
Disk Profile Tie-Breaking: If multiple candidates pass
child_files_disk_profile, the closest byte-size match is chosen only if the gap to the next-closest candidate exceeds$2%$ of the total file size:$$\frac{\delta_{\text{runner-up}} - \delta_{\text{closest}}}{\max(Size_A, 1)} > 0.02$$ -
Bidirectional Consensus: An association
$(A, B)$ is accepted only when forward elimination ($A \rightarrow B$ ) and reverse elimination ($B \rightarrow A$ ) independently resolve to the exact same pair.
Primary tabular export generated in data/extracted/ability_data.csv:
| Column Name | Type | Description |
|---|---|---|
fqn |
string |
Fully Qualified Node Name (e.g., abl.sith_inquisitor.spike). |
global_combat_id |
string |
Game engine localized combat ID (from lookup key 15685385242400905286). |
name |
string |
In-game localized name. |
tooltip |
string |
In-game localized descriptive tooltip. |
icon |
string |
Icon asset stem (maps to icons/<icon>.dds). |
attack_type |
string |
Attack category (Melee, Ranged, Tech, Force, or combinations). |
ablGlobalCooldownTime |
float |
GCD duration (empty if off-GCD, -1 for standard GCD or >0 for a fixed value). |
mirror_fqn |
string |
Cross-faction mirror node FQN. |
exclusive_to_spec |
string |
Formatted discipline identifier (class.spec) if truly exclusive. |
is_interrupt |
bool |
True if ability interrupts enemy spell casts. |
grants_absorb |
bool |
True if ability grants a damage absorption shield. |
its_revive |
bool |
True if ability offers in-combat or out-of-combat resurrection. |
ignore_alacrity |
bool |
True if ability cooldown/cast time ignores alacrity stat scaling. |
is_passive |
bool |
True if node is an innate passive (ablIsPassive == True). |
Hierarchy mapping Advanced Classes and specs to exclusive node IDs and replacement references:
{
"agent.operative": {
"concealment": [
{
"id": "abl.agent.skill.concealment.veiled_strike",
"replaces": "abl.agent.shiv"
}
]
}
}Verified 1:1 cross-faction mirror mapping table:
[
{
"rep_folder": "abl/jedi_consular",
"imp_folder": "abl/sith_inquisitor",
"rep_name": "Mind Snap",
"rep_fqn": "abl.jedi_consular.mind_snap",
"imp_name": "Jolt",
"imp_fqn": "abl.sith_inquisitor.jolt",
"reason": "ability_complete_profile"
}
]- Locate
# --- Field Constants ---inmain.pyand register the field identifiers:NEW_TRAIT_FIELD_ID = "4611686XXXXXXXXXXXX" NEW_TRAIT_FIELD_NAME = "ablNewTraitName"
- Add an evaluation method to
AbilityFeatureExtractor:def check_new_trait(self, record: NodeRecord) -> bool: val = get_field_val(record, NEW_TRAIT_FIELD_NAME, NEW_TRAIT_FIELD_ID) return val is True
- Register the method in
TRAIT_EXTRACTORS:The column will automatically be included inTRAIT_EXTRACTORS: dict[str, Callable[[AbilityFeatureExtractor, NodeRecord], bool]] = { ... "new_trait_column": lambda ext, rec: ext.check_new_trait(rec), }
ability_data.csvheaders and evaluated during extraction.
- Open
matching.pyand implement the filter function:def check_custom_property(a: dict, b: dict) -> bool: return bool(a.get("custom_val") and a.get("custom_val") == b.get("custom_val"))
- Register the filter name in
ALL_FILTER_STEPS:ALL_FILTER_STEPS = [ ... "custom_property_step", ... ]
- Insert the filter inside
get_candidate_filters()at the desired priority tier:if node_a.get("custom_val"): filters.append(("custom_property_step", check_custom_property))