class_name AbilityData extends RefCounted ## The ability catalog: every ability as a data row, and every hero kit as a map ## from form and slot to an ability id plus that form's resource pool. Pure static ## data — the single source of truth the simulation reads to equip a hero and the ## executor reads to act. New abilities and heroes are added here, by value, with ## no engine or render coupling, so the whole roster stays unit-testable. ## ## "wildkin" is the schema-proving kit: a generic shapeshifter that exercises the ## whole catalog — all four targeting modes, the three effects, a per-form resource, ## and the human/animal transform. It stays as the reference the schema tests drive. ## ## The first tribe's roster is authored on top of it: the **Solane** — savanna big-cat ## shifters. Three mirror heroes, each a human kit plus an animal kit, built ## from the same DAMAGE/HEAL/TRANSFORM primitives but given distinct identities through ## their targeting mix, their tuning, and their resource economy: ## - **Lion** — a frontline bruiser: short-range poke, a heavy single-target Maul, ## and the deepest self-sustain, on a generous, slow-spending pool. ## - **Cheetah** — a burst skirmisher: long-range pokes and a repeatable single-target ## shred, on a lean, fast-regenerating pool (hit and run). ## - **Hyena** — a zone controller: the widest ground areas in both forms for ## attrition, on a baseline pool. ## ## The opposing tribe, the **Verdani** — jungle venom-and-shadow shifters — is authored ## on the same primitives, a deliberate foil to the Solane archetypes: ## - **Snake** — a venom striker: a long single-target lock, a cheap low-cooldown ## Fang Strike, and a heavy Venom Coil payoff, on a mid-tier pool. ## - **Spider** — a trapper: the longest, widest, lowest-power ground webs in the ## game — a long snaring slow and a brief hard lock — on the deepest, ## slowest-regen pool. ## - **Chameleon** — an ambusher: a short hard skillshot and the single heaviest hit ## in either tribe, on the leanest, fastest-refilling pool. ## In a practice match the player's squad fields the Solane and the bot squad the ## Verdani, so both rosters and all four targeting modes are exercised at once. The ## Verdani's venom and web are now mechanical, not just named: their striking abilities ## carry a lingering status (see AbilitySpec.STATUS_*) — venom is a damage-over-time, web ## a movement slow, and the Spider's nest a brief hard stun — so the bite keeps biting, ## the snare actually snares, and the nest locks. Each such ability trades part of its ## instant power for that lingering effect, so the Verdani lean attrition and control ## where the Solane stay burst. ## Ability rows keyed by catalog id. Each row is parsed on demand into a typed ## AbilitySpec by `spec`; a sparse row leans on the spec defaults. The dictionary's ## insertion order is stable, which keeps any iteration over the roster ## deterministic, like the rest of the simulation. const ABILITIES := { # --- wildkin, human form ------------------------------------------------- 1: { "id": 1, "name": "Spirit Bolt", "form": AbilitySpec.FORM_HUMAN, "slot": 0, "target_kind": AbilitySpec.TARGET_SKILLSHOT, "range": 600.0, "radius": 60.0, # a tight bolt: clips enemies at its landing point "cost": 20, "cooldown_ticks": 30, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 80, }, 2: { "id": 2, "name": "Mend", "form": AbilitySpec.FORM_HUMAN, "slot": 1, "target_kind": AbilitySpec.TARGET_SELF, "cost": 30, "cooldown_ticks": 120, "effect": AbilitySpec.EFFECT_HEAL, "power": 100, }, 3: { "id": 3, "name": "Beast Form", "form": AbilitySpec.FORM_HUMAN, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- wildkin, animal form ------------------------------------------------ 4: { "id": 4, "name": "Pounce", "form": AbilitySpec.FORM_ANIMAL, "slot": 0, "target_kind": AbilitySpec.TARGET_GROUND, "range": 400.0, "radius": 150.0, # an area slam: every enemy inside the circle is struck "cost": 20, "cooldown_ticks": 24, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 60, }, 5: { "id": 5, "name": "Rend", "form": AbilitySpec.FORM_ANIMAL, "slot": 2, "target_kind": AbilitySpec.TARGET_UNIT, "range": 200.0, "cost": 30, "cooldown_ticks": 48, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 120, }, 6: { "id": 6, "name": "Human Form", "form": AbilitySpec.FORM_ANIMAL, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Solane: Lion, human form (a short-range bruiser with deep self-sustain) --- 10: { "id": 10, "name": "Sunfire Lash", "form": AbilitySpec.FORM_HUMAN, "slot": 0, "target_kind": AbilitySpec.TARGET_SKILLSHOT, "range": 450.0, "radius": 70.0, "cost": 25, "cooldown_ticks": 36, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 65, }, 11: { "id": 11, "name": "Mane Guard", "form": AbilitySpec.FORM_HUMAN, "slot": 1, "target_kind": AbilitySpec.TARGET_SELF, "cost": 35, "cooldown_ticks": 150, "effect": AbilitySpec.EFFECT_HEAL, "power": 150, # the deepest heal in the tribe: the bruiser's staying power }, 12: { "id": 12, "name": "Lion Form", "form": AbilitySpec.FORM_HUMAN, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Solane: Lion, animal form (engage area, then a heavy melee burst) -------- 13: { "id": 13, "name": "Pounce", "form": AbilitySpec.FORM_ANIMAL, "slot": 0, "target_kind": AbilitySpec.TARGET_GROUND, "range": 350.0, "radius": 160.0, "cost": 25, "cooldown_ticks": 30, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 70, }, 14: { "id": 14, "name": "Maul", "form": AbilitySpec.FORM_ANIMAL, "slot": 2, "target_kind": AbilitySpec.TARGET_UNIT, "range": 190.0, # melee: the bruiser must close to land its payoff "cost": 35, "cooldown_ticks": 48, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 160, # the hardest single hit in the tribe }, 15: { "id": 15, "name": "Human Form", "form": AbilitySpec.FORM_ANIMAL, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Solane: Cheetah, human form (long pokes on a lean, fast pool) ------------ 20: { "id": 20, "name": "Spear Throw", "form": AbilitySpec.FORM_HUMAN, "slot": 0, "target_kind": AbilitySpec.TARGET_SKILLSHOT, "range": 750.0, # the longest reach in the tribe "radius": 50.0, # but a tight line: it must be aimed "cost": 20, "cooldown_ticks": 24, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 90, }, 21: { "id": 21, "name": "Second Wind", "form": AbilitySpec.FORM_HUMAN, "slot": 1, "target_kind": AbilitySpec.TARGET_SELF, "cost": 25, "cooldown_ticks": 100, "effect": AbilitySpec.EFFECT_HEAL, "power": 80, # a skirmisher's top-up, not the Lion's wall }, 22: { "id": 22, "name": "Cheetah Form", "form": AbilitySpec.FORM_HUMAN, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Solane: Cheetah, animal form (single-target shred, repeatable) ----------- 23: { "id": 23, "name": "Hamstring", "form": AbilitySpec.FORM_ANIMAL, "slot": 0, "target_kind": AbilitySpec.TARGET_UNIT, "range": 280.0, "cost": 15, "cooldown_ticks": 18, # the shortest cooldown in the tribe: harass on repeat "effect": AbilitySpec.EFFECT_DAMAGE, "power": 70, }, 24: { "id": 24, "name": "Killing Blow", "form": AbilitySpec.FORM_ANIMAL, "slot": 2, "target_kind": AbilitySpec.TARGET_UNIT, "range": 220.0, "cost": 35, "cooldown_ticks": 50, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 140, }, 25: { "id": 25, "name": "Human Form", "form": AbilitySpec.FORM_ANIMAL, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Solane: Hyena, human form (the widest ground zone for attrition) --------- 30: { "id": 30, "name": "Bone-Hex", "form": AbilitySpec.FORM_HUMAN, "slot": 0, "target_kind": AbilitySpec.TARGET_GROUND, "range": 600.0, "radius": 190.0, "cost": 30, "cooldown_ticks": 40, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 55, }, 31: { "id": 31, "name": "Scavenge", "form": AbilitySpec.FORM_HUMAN, "slot": 1, "target_kind": AbilitySpec.TARGET_SELF, "cost": 30, "cooldown_ticks": 120, "effect": AbilitySpec.EFFECT_HEAL, "power": 100, }, 32: { "id": 32, "name": "Hyena Form", "form": AbilitySpec.FORM_HUMAN, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Solane: Hyena, animal form (a bite, and a wide pack slam) ---------------- 33: { "id": 33, "name": "Rending Bite", "form": AbilitySpec.FORM_ANIMAL, "slot": 0, "target_kind": AbilitySpec.TARGET_UNIT, "range": 200.0, "cost": 20, "cooldown_ticks": 30, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 90, }, 34: { "id": 34, "name": "Pack Frenzy", "form": AbilitySpec.FORM_ANIMAL, "slot": 2, "target_kind": AbilitySpec.TARGET_GROUND, "range": 320.0, "radius": 210.0, # the widest area in the tribe "cost": 35, "cooldown_ticks": 44, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 60, }, 35: { "id": 35, "name": "Human Form", "form": AbilitySpec.FORM_ANIMAL, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Verdani: Snake, human form (a long venom poke on a precise pool) ---------- 40: { "id": 40, "name": "Venom Spit", "form": AbilitySpec.FORM_HUMAN, "slot": 0, "target_kind": AbilitySpec.TARGET_SKILLSHOT, "range": 650.0, # a long reach, just shy of the Cheetah's signature spear "radius": 55.0, # but a thin line: it must be aimed "cost": 20, "cooldown_ticks": 24, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 50, # trimmed from a pure poke: the rest of the bite is the venom below "status": AbilitySpec.STATUS_DOT, # venom: lingering damage over two seconds "status_power": 6, "status_duration": 120, "status_interval": 30, }, 41: { "id": 41, "name": "Shed Skin", "form": AbilitySpec.FORM_HUMAN, "slot": 1, "target_kind": AbilitySpec.TARGET_SELF, "cost": 30, "cooldown_ticks": 110, "effect": AbilitySpec.EFFECT_HEAL, "power": 90, }, 42: { "id": 42, "name": "Serpent Form", "form": AbilitySpec.FORM_HUMAN, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Verdani: Snake, animal form (the longest single-target lock, then a payoff) --- 43: { "id": 43, "name": "Fang Strike", "form": AbilitySpec.FORM_ANIMAL, "slot": 0, "target_kind": AbilitySpec.TARGET_UNIT, "range": 360.0, # the longest single-target lock in either tribe "cost": 15, "cooldown_ticks": 18, # cheap and fast: harass on repeat "effect": AbilitySpec.EFFECT_DAMAGE, "power": 55, # a lighter strike now that the fang leaves venom "status": AbilitySpec.STATUS_DOT, "status_power": 5, "status_duration": 120, "status_interval": 30, }, 44: { "id": 44, "name": "Venom Coil", "form": AbilitySpec.FORM_ANIMAL, "slot": 2, "target_kind": AbilitySpec.TARGET_UNIT, "range": 300.0, "cost": 35, "cooldown_ticks": 50, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 105, # the heavy payoff, now split between the coil and its deep venom "status": AbilitySpec.STATUS_DOT, # the tribe's strongest venom: a heavy lingering bleed "status_power": 11, "status_duration": 120, "status_interval": 30, }, 45: { "id": 45, "name": "Human Form", "form": AbilitySpec.FORM_ANIMAL, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Verdani: Spider, human form (the longest, widest web for attrition) ------- 50: { "id": 50, "name": "Web Snare", "form": AbilitySpec.FORM_HUMAN, "slot": 0, "target_kind": AbilitySpec.TARGET_GROUND, "range": 620.0, "radius": 200.0, "cost": 30, "cooldown_ticks": 38, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 45, # the lowest per-hit power in either tribe: attrition, now with a snare "status": AbilitySpec.STATUS_SLOW, # web: the strongest, longest slow — the trapper's lock "status_power": 45, # a 45% slow "status_duration": 150, }, 51: { "id": 51, "name": "Silk Mend", "form": AbilitySpec.FORM_HUMAN, "slot": 1, "target_kind": AbilitySpec.TARGET_SELF, "cost": 30, "cooldown_ticks": 120, "effect": AbilitySpec.EFFECT_HEAL, "power": 100, }, 52: { "id": 52, "name": "Spider Form", "form": AbilitySpec.FORM_HUMAN, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Verdani: Spider, animal form (a close bite, then the widest nest) ---------- 53: { "id": 53, "name": "Venom Bite", "form": AbilitySpec.FORM_ANIMAL, "slot": 0, "target_kind": AbilitySpec.TARGET_UNIT, "range": 210.0, "cost": 20, "cooldown_ticks": 30, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 65, # a lighter bite, the rest delivered as venom "status": AbilitySpec.STATUS_DOT, "status_power": 5, "status_duration": 120, "status_interval": 30, }, 54: { "id": 54, "name": "Web Nest", "form": AbilitySpec.FORM_ANIMAL, "slot": 2, "target_kind": AbilitySpec.TARGET_GROUND, "range": 340.0, "radius": 220.0, # the widest area in either tribe "cost": 35, "cooldown_ticks": 60, # a longer recharge: a hard lock is a deliberate engage, not spam "effect": AbilitySpec.EFFECT_DAMAGE, "power": 35, # trimmed hard — the nest's payoff is now the lock, not the hit "status": AbilitySpec.STATUS_STUN, # web: a brief hard lock (no move, cast, or attack) "status_duration": 30, # half a second frozen }, 55: { "id": 55, "name": "Human Form", "form": AbilitySpec.FORM_ANIMAL, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Verdani: Chameleon, human form (a short, hard skillshot on a lean pool) ---- 60: { "id": 60, "name": "Tongue Lash", "form": AbilitySpec.FORM_HUMAN, "slot": 0, "target_kind": AbilitySpec.TARGET_SKILLSHOT, "range": 380.0, # short: the ambusher fights up close "radius": 60.0, "cost": 25, "cooldown_ticks": 30, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 95, # a heavy poke for its range }, 61: { "id": 61, "name": "Blend", "form": AbilitySpec.FORM_HUMAN, "slot": 1, "target_kind": AbilitySpec.TARGET_SELF, "cost": 25, "cooldown_ticks": 100, "effect": AbilitySpec.EFFECT_HEAL, "power": 75, # a skirmisher's top-up, not a wall }, 62: { "id": 62, "name": "Chameleon Form", "form": AbilitySpec.FORM_HUMAN, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, # --- Verdani: Chameleon, animal form (a cheap dart, then the heaviest ambush) --- 63: { "id": 63, "name": "Color Dart", "form": AbilitySpec.FORM_ANIMAL, "slot": 0, "target_kind": AbilitySpec.TARGET_SKILLSHOT, "range": 300.0, "radius": 50.0, "cost": 15, "cooldown_ticks": 20, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 60, }, 64: { "id": 64, "name": "Ambush", "form": AbilitySpec.FORM_ANIMAL, "slot": 2, "target_kind": AbilitySpec.TARGET_UNIT, "range": 200.0, # melee: the payoff for closing the gap "cost": 35, "cooldown_ticks": 52, "effect": AbilitySpec.EFFECT_DAMAGE, "power": 165, # the single heaviest hit in either tribe }, 65: { "id": 65, "name": "Human Form", "form": AbilitySpec.FORM_ANIMAL, "slot": 3, "target_kind": AbilitySpec.TARGET_SELF, "cost": 0, "cooldown_ticks": 60, "effect": AbilitySpec.EFFECT_TRANSFORM, }, } ## Bot stance per kit: how a bot positions a hero it drives. BRAWL — the default for ## any kit that names no stance — closes the gap and shifts toward whichever form can ## land a hit. KITE holds the kit's ranged poke and keeps the enemy at arm's length, ## fighting hit-and-run from its skillshot band rather than committing to melee. Read by ## BotController and stamped onto the hero at equip; a player-driven hero ignores it. const STANCE_BRAWL := 0 const STANCE_KITE := 1 ## Hero kits keyed by kit id. A kit names, per form, the resource pool (`max` and ## the regen interval `regen_ticks` — one resource point restored every that many ## ticks, 0 for none) and the slot-to-ability-id bar. A hero equipped with a kit ## starts in human form with that form's resource full. Integer regen on a tick ## interval keeps resource growth deterministic, like the cooldown counters. const KITS := { "wildkin": { "resource": { # Human "Focus" and animal "Ferocity": same shape, distinct pools the # transform swaps between, so each stance meters its own casts. AbilitySpec.FORM_HUMAN: {"max": 100, "regen_ticks": 12}, AbilitySpec.FORM_ANIMAL: {"max": 100, "regen_ticks": 12}, }, "abilities": { AbilitySpec.FORM_HUMAN: {0: 1, 1: 2, 3: 3}, AbilitySpec.FORM_ANIMAL: {0: 4, 2: 5, 3: 6}, }, }, # --- Solane (savanna big-cats), the v0.1 mirror tribe --------------------- "lion": { # A bruiser: a generous pool that spends slowly, to back the deep heal and # the heavy Maul. "resource": { AbilitySpec.FORM_HUMAN: {"max": 120, "regen_ticks": 10}, AbilitySpec.FORM_ANIMAL: {"max": 120, "regen_ticks": 10}, }, "abilities": { AbilitySpec.FORM_HUMAN: {0: 10, 1: 11, 3: 12}, AbilitySpec.FORM_ANIMAL: {0: 13, 2: 14, 3: 15}, }, }, "cheetah": { # A skirmisher: a lean pool that refills fast, to chain cheap pokes and the # low-cooldown Hamstring. A kiter — it holds its long Spear Throw range. "stance": STANCE_KITE, "resource": { AbilitySpec.FORM_HUMAN: {"max": 80, "regen_ticks": 8}, AbilitySpec.FORM_ANIMAL: {"max": 80, "regen_ticks": 8}, }, "abilities": { AbilitySpec.FORM_HUMAN: {0: 20, 1: 21, 3: 22}, AbilitySpec.FORM_ANIMAL: {0: 23, 2: 24, 3: 25}, }, }, "hyena": { # A zone controller: a baseline pool feeding the wide ground areas. "resource": { AbilitySpec.FORM_HUMAN: {"max": 100, "regen_ticks": 12}, AbilitySpec.FORM_ANIMAL: {"max": 100, "regen_ticks": 12}, }, "abilities": { AbilitySpec.FORM_HUMAN: {0: 30, 1: 31, 3: 32}, AbilitySpec.FORM_ANIMAL: {0: 33, 2: 34, 3: 35}, }, }, # --- Verdani (jungle venom-and-shadow), the opposing tribe ---------------- "snake": { # A striker: a precise mid-tier pool, between the Cheetah's lean and the # Hyena's baseline, to feed the cheap Fang Strike and the heavy Coil. "resource": { AbilitySpec.FORM_HUMAN: {"max": 90, "regen_ticks": 9}, AbilitySpec.FORM_ANIMAL: {"max": 90, "regen_ticks": 9}, }, "abilities": { AbilitySpec.FORM_HUMAN: {0: 40, 1: 41, 3: 42}, AbilitySpec.FORM_ANIMAL: {0: 43, 2: 44, 3: 45}, }, }, "spider": { # A trapper: the deepest pool on the slowest regen, to sustain the wide, # cheap-per-cast webs over a long attrition. "resource": { AbilitySpec.FORM_HUMAN: {"max": 110, "regen_ticks": 13}, AbilitySpec.FORM_ANIMAL: {"max": 110, "regen_ticks": 13}, }, "abilities": { AbilitySpec.FORM_HUMAN: {0: 50, 1: 51, 3: 52}, AbilitySpec.FORM_ANIMAL: {0: 53, 2: 54, 3: 55}, }, }, "chameleon": { # An ambusher: the leanest pool on the fastest regen, to land a burst and # refill for the next one — the most boom-and-bust economy of either tribe. A # kiter — it darts in and out at its Tongue Lash range rather than brawling. "stance": STANCE_KITE, "resource": { AbilitySpec.FORM_HUMAN: {"max": 70, "regen_ticks": 7}, AbilitySpec.FORM_ANIMAL: {"max": 70, "regen_ticks": 7}, }, "abilities": { AbilitySpec.FORM_HUMAN: {0: 60, 1: 61, 3: 62}, AbilitySpec.FORM_ANIMAL: {0: 63, 2: 64, 3: 65}, }, }, } ## The tribes: each tribe's hero roster, in seating order. The single source of which ## heroes form which tribe — the client reads it to seat a tribe-vs-tribe match, and the ## roster order fixes each hero's squad slot. The wildkin reference kit is deliberately ## in no tribe. v0.1 ships two tribes; a match pairs one against another (see ## `opposing_tribe`). const TRIBE := { "solane": ["lion", "cheetah", "hyena"], "verdani": ["snake", "spider", "chameleon"], } ## The typed spec for a catalog id. Parses the row on demand — the catalog is small ## and the executor caches nothing, so a spec is always read fresh by value. static func spec(id: int) -> AbilitySpec: return AbilitySpec.from_dict(ABILITIES.get(id, {})) ## Whether an ability id exists in the catalog. static func has_ability(id: int) -> bool: return ABILITIES.has(id) ## A kit definition by id, or an empty dictionary if unknown. static func kit(kit_id: String) -> Dictionary: return KITS.get(kit_id, {}) ## The tribe a hero kit belongs to, or "" if the kit is in no tribe (the wildkin reference ## kit, or an unknown name). A pure lookup over the roster data. static func tribe_of(kit_id: String) -> String: for tribe in TRIBE: if (TRIBE[tribe] as Array).has(kit_id): return tribe return "" ## A hero kit's seat index within its tribe (0..n-1, the order it sits in `TRIBE`), or -1 ## if the kit is in no tribe (the wildkin reference kit, or an unknown name). The renderer ## shades a hero's team colour by this index so squadmates read apart; a pure roster lookup. static func roster_index(kit_id: String) -> int: var tribe := tribe_of(kit_id) if tribe == "": return -1 return (TRIBE[tribe] as Array).find(kit_id) ## The tribe a given tribe is matched against — the next other tribe in declaration order. ## v0.1 fields exactly two, so this is simply "the other one"; returns `tribe` itself if ## it is the only tribe defined. static func opposing_tribe(tribe: String) -> String: for other in TRIBE: if other != tribe: return other return tribe