Skip to main content

Progression

Progression

Status: COMPLETE — Full design documented

Last updated: October 31, 2026

Dark Tower -- Progression & Character System

Status: COMPLETE — Full design documented

Subsystem: XP/leveling, character creation, classes, races, stats, powers, skills, paragon, companions, gods Primary sources: , , , enums in llitd.model.darktower.enums.character.*


XP and Leveling

Source:

XP Curve (Exponential)

XP required per level:
  Levels 1-10:   round(1000 * 1.2^(level-1))   // Fast early: 1000, 1200, 1440, 1728...
  Levels 10-100: round(5000 * 1.15^(level-10))  // Exponential mid: starts ~6,192 for level 11
  Levels 100+:   round(100000 * 1.12^(level-100)) // Paragon: starts ~2.6M for level 101

XP Requirements Table

Level XP for This Level Cumulative XP
2 1,000 1,000
5 1,728 ~6,100
10 5,160 ~25,000
11 6,192 ~31,000
25 ~41,000 ~260,000
50 ~819,000 ~6.2M
100 ~8.1M ~74M
101 ~100,000 ~74.1M
200 ~9.3M ~100M+
500 Level cap --

Level Calculation

calculateLevelFromValue(xp):
  Iterates from level 1 to 500 (hard cap)
  Subtracts each level's XP requirement
  Returns the level where remaining XP is insufficient for next

Level Cap

Maximum level: 500 (enforced in the while loop: while (level < 500))

Legacy migrate note: characters imported from the old Dark Citadel skill do not land at raw legacy levels (those are uncapped and can exceed 3000). Migration scales to a ceiling of 400 (80% of the DT cap). See character-migration.md §1.3.

Claiming Earned Upgrades (the "upgrade" command)

Upgrades are slot-based, not point-based (Powers at level 5/50/100, Stat at 15, Skill at 35, Companions at 10/20/30/40, Gods at 25+; mandatory PRIMARY_CLASS / SECONDARY_CLASS first). A slot earned by leveling stays open until the player fills it. Two paths reach the picker:

  • Auto-prompt — after a combat round, endAfterRound calls hasUpgradeToSpend, which defers a non-mandatory prompt while the player is in combat or in the first three sessions so it never interrupts a fight or the opening tutorial.
  • Explicit request — when the player says "upgrade", attemptToUpgrade(userInitiated) resolves the slot directly and HONORS the request even when the auto-prompt would defer: an open earned slot opens the picker regardless of session count, and a slot blocked only by active combat is EXPLAINED ("finish your current battle first, say upgrade once the fight is over") rather than reported as "no upgrades to spend". Mandatory class slots still surface during combat so a classless character is never trapped. An explicit request with nothing earned still speaks the helpful "no upgrades to spend ... you are level N" line.

The misleading "no upgrades to spend" for an established player who had a long-open slot was fb 9009 (fixed 2026-06-15; scenario_test 2105).

The upgrade filter picker paginates any oversized option list. When a chosen filter -- notably the companion "All" filter for a leveled roster -- yields more options than the 35-tag voice cap can number, the picker shows one page of UPGRADE_OPTION_PAGE_SIZE options plus "More Options" and "Previous Options" nav that re-enter the same filter at the adjacent page (the page index rides in the filter key), so every option stays reachable instead of dead-ending past the cap (OVER_CAP_OPTIONS; real-player error_log 187213, ; scenario_test 2395). Mirrors the viewCompanions paging.

Reset & Respec System

Status: SHIPPED 2026-06-28 (DC->DT feature migration). Source: engines/handlers/darktower/builtin/.

A player can clear part of their chosen build so they can pick it again -- the respec feature carried over (and re-monetized) from the legacy Dark Citadel game, where it was a near-free gold action. The player says "reset" to open the Reset menu, or names a dimension directly ("reset powers"). Each reset is charged in diamonds (the player-facing name for Dark Tower special tokens -- see economy.md § Reset / Respec — Diamond Sink).

What can be reset

Option Clears Diamond cost
Powers the chosen power slots (chosenPowerFirst/Second/Third) 5
Classes primary + secondary class and their derived power/stat/skill fields 5
Stats the chosen stat (chosenStatFirst) 5
Skills the chosen skill (chosenSkill) 5
Companions the fresh companion picks (the starter Douglas is re-granted; hero-progressed and on-mission companions are preserved) 5
Everything all of the above at once 15

The very first respec is FREE (any dimension, one time only — see Mechanics); the costs above apply to every reset after the first.

What is preserved (never reset)

Level, experience, gear (equipped and owned), race, paragon, currency, clues, and story progress. Reset clears ONLY the level-up chosen build slots. Gear-derived and class-derived powers/stats/skills re-derive automatically on the next read, so a reset simply frees the chosen slots for the player to re-pick from the upgrade menu.

Mechanics and invariants

  • Free first respec (onboarding grace). A new player's very first respec is FREE for any dimension, so a first mis-pick is forgivable. Gated on respecCount == 0; respecCount increments on every reset that actually clears something, so every later reset is diamond-gated. The free reset never consumes a held reset token. Modeled on legacy RESET_COOLDOWN_COUNT, simplified to a single free first use.
  • Diamond charge plus shortfall. After the free first reset, a reset checks canAffordDiamonds(cost); on shortfall it routes the player to the diamond purchase flow via (ShortfallCurrencyType.SPECIAL_TOKENS, player-facing "diamonds"). It NEVER charges for a no-op -- if the player has nothing to reset in the chosen dimension, it says so and skips the charge.
  • Reset token versus diamonds is the player's choice. A full "Everything" reset can be paid with a held reset token OR diamonds. When the player holds a token, the handler presents an explicit choice rather than auto-consuming the token. A single token costs 12 diamonds (below the 15-diamond direct cost), a bundle of three costs 30; both surface in the reset menu, and the single token is also offered in the Token Shop. See economy.md § Reset / Respec — Diamond Sink.
  • Companion hero progress is never reset away. A recruited companion is also a combat hero — its hero level, hero experience, and any in-flight timed mission ride on the companion object. A companion reset re-rolls the fresh companion picks but PRESERVES any hero-progressed or on-mission companion in its slot (a wiped slot is never re-derived, so wiping it would silently destroy that earned progress). A respec re-rolls a build slot; it never deletes timed-mission hero progress.
  • Class-skill teardown. Class power and stat derive from the class object at read time, but the class skill is stored on its own field (classSkill / secondaryClassSkill). Clearing a class therefore nulls all eight class fields, or a phantom class skill would linger in the skill list.
  • Hit-point recompute. Class, the toughness stat, the extra-hit-points power, and companion health boosts all feed max hit points, so any reset recomputes max HP via (the same call level-up uses) and clamps current HP down to the new max. A reset never heals.
  • Starter companion. Resetting companions clears the wipeable slots and re-grants the always-available starter (Douglas the Dog) via addBasicCompanions when slot one ends up empty, so the player is never left with an empty party (hero-progressed and on-mission companions stay in place).
  • Blocked states. Reset is gated by blockedFromGeneralActions -- no resetting mid-combat or mid-special-state.

Relationship to "change class"

Reset is the sanctioned re-roll. The free "change class" upgrade path remains a no-op for an already-classed character (see the Multi-Character Support note that classes are permanent via the free upgrade path) -- that guard exists so a player does not sideline their main chasing a free class swap. The paid Reset is the deliberate, diamond-gated way to clear a class (or any build dimension) and pick again from scratch.

NLU and wiring

DARK_TOWER_RESET is dispatched to (bound to Alexa StartOverIntent and the Dark Tower reset command surface). Menu options re-enter DARK_TOWER_RESET carrying the dimension key (POWER / CLASS / STAT / SKILL / COMPANION / ALL), read via getKeyOrValue.

Regression

game_test.scenario_tests -- reset a seeded build dimension and assert the chosen slot is cleared and diamonds are deducted (authored via add_scenario_test).

Starting Loadout

Source: Character creation flow (derived from defaults and creation handler)

Attribute Value Notes
HP computed maxHp = (45 + level*4) * class multiplier + bonuses (e.g., Warrior L1 Fighter type x1.4 = 69). getCharacter() initializes a placeholder 100/100; .calculateHitPoints recomputes max once class is picked AND clamps currentHp <= maxHp (invariant — DT-2026-0508-012/010/021).
Level 1
Experience 900 Almost enough for level 2 (need 1000)
Main Hand Sword (WEAPON_SWORD_STANDARD) Starting melee weapon, equipped in startGame()
Ranged Bow and Arrows (WEAPON_BOW_STANDARD) Starting ranged weapon, equipped in startGame()
Chest Armor Leather Armor ARMOR_LEATHER (CHEST slot), equipped in startGame()
Health Potions 3 ITEM_HEALING_POTION in ownedItemsMap, usable in combat, cap 3
Gold 2,000
Diamonds 200
Wood 600
Iron 500
Insanity 0
Start Card 30000 Main Story start
Inventory 20 slots maxInventorySpace = 20 (default)
Companions Douglas Your Dog Slot one: COMPANION_PET_DOG (COMBAT_BUFFER special, granted via addBasicCompanions())
Difficulty EASY Default

Character Structure

Source:

Identity

characterKey: String           // Unique character identifier
characterName: String          // Set once on creation
pendingCharacterName: String   // During name confirmation flow
awaitingNameConfirmation: Boolean  // Waiting for yes/no

Build (Dual-Class System)

Primary class:
  primaryClass: DarkTowerClass
  classPower: DarkTowerPower
  classStat: DarkTowerStat
  classSkill: DarkTowerSkill

Secondary class:
  secondaryClass: DarkTowerClass
  secondaryClassPower: DarkTowerPower
  secondaryClassStat: DarkTowerStat
  secondaryClassSkill: DarkTowerSkill

Additional choices:
  chosenStatFirst: DarkTowerStat
  chosenPowerFirst/Second/Third: DarkTowerPower  // Up to 3 chosen powers
  chosenSkill: DarkTowerSkill
  chosenRace: DarkTowerRace

Gear-Provided Build Extensions

gearPowers: List<DarkTowerPower>           // Powers granted by equipment
gearSkills: List<DarkTowerSkill>           // Skills granted by equipment
gearStats: List<DarkTowerStat>             // Stats granted by equipment
activeEnchantments: List<DarkTowerEnchantments>  // Active enchantment effects
activeSetBonuses: Map<String, Long>          // Set ID -> piece count

Classes

Source: DarkTowerClassTypes

Class Categories

Category Types HP Bonus
Fighter/Leader FIGHTER classes, LEADER classes +40% HP
Psion/Healer PSION, HEALER classes +20% HP
Scoundrel/Caster SCOUNDREL, CASTER classes +10% HP
Other All others +3% HP

Sub-Class Types

Source: DarkTowerSubClassTypes

FIGHTER, LEADER, SCOUNDREL, CASTER, PSION, HEALER (used for HP calculation grouping via DarkTowerClass.getClassType())

Full Class List (57 classes)

Basic: BARBARIAN, BARD, CAPTAIN, CLERIC, KNIGHT, MEDIC, MONK, PALADIN, PILOT, PIRATE, PSYCHIC, RANGER, ROGUE, SMUGGLER, SOLDIER, SPACE_MARINE, WARLOCK, WARMONGER, WARRIOR, WILD_MAGE, WIZARD, SPACE_PIRATE, BEAST_MASTER, DRAGON_LORD, SHAMAN, DRUID

Race-Specific: DWARVEN_BATTLEMASTER, ELVEN_STRATEGIST, ORCISH_RAIDER, WEREWOLF_SHADOW_WALKER, VAMPIRE_LORD

God-Specific: ELYSIUMS_CHAMPION, POSEIDONS_GUIDE, PERRIKS_GLADIATOR, MERRIS_PROTECTOR

Advanced: HAWK_TAMER, DRAGON_TAMER, MONSTER_TAMER, BLOOD_KNIGHT, DEATH_KNIGHT, ARCANE_ARCHER, LORE_MASTER, ASSASSIN, ARCANE_TRICKSTER, PSYCHIC_KNIGHT, SOUL_THIEF, MARKSMAN, HEAVY_GUNNER, DEMOLITIONS_EXPERT, TINKERER, NECROMANCER, WHIRLING_DERVISH, WITCH_DOCTOR, VIKING

Strategic: STRATEGIST, GRAND_STRATEGIST, MASTER_STRATEGIST

Races (14 Types)

Source: DarkTowerRaceTypes

HUMAN, ELF, HALF_ELF, DWARF, ORC, HALF_ORC, GOBLIN, GNOME, HALF_ANGEL, HALF_DRAGON, HALF_DEMON, VAMPIRE, WEREWOLF, CYBORG

Each race provides:

  • raceHitBoost: Flat hit chance bonus (applied twice if level >= 100)
  • raceArmorBoost: Flat AC bonus (applied twice if level >= 100)
  • raceDamageBoost: Percentage damage bonus
  • raceSkillBoostAll: Skill bonus multiplied by level bonus

Stats (18 Types)

Source: DarkTowerStatTypes

Primary Stats

CHARISMA, CONSTITUTION, DEFENSE, DEXTERITY, INTELLIGENCE, STRENGTH, WISDOM

Secondary Stats

TOUGHNESS, AGILITY, WILLPOWER, FELLOWSHIP, WEAPON_SKILL, BALLISTIC_SKILL, MAGICAL_SKILL, PSYCHIC_SKILL, RESEARCH_MASTERY, TRAINING_MASTERY

Stat Impact Summary

Stat Combat Effect
STRENGTH +5 melee hit, +30% melee damage
DEXTERITY +5 ranged hit
AGILITY +30% ranged damage
DEFENSE +2 AC
TOUGHNESS +35% HP

Powers (148 Types)

Source: DarkTowerPowerTypes

Players can choose up to 3 powers plus class powers. Powers provide diverse combat abilities:

Key Combat Powers

Power Effect
ORCISH_RAGE +45% damage
WEAPON_MASTER +30% melee damage
MASTER_ARCHER +30% ranged damage
DWARVEN_PROWESS +40% melee damage, +2 hit
ELVEN_ARCHERY +40% ranged damage, +2 hit
BARDIC_KNOWLEDGE +13 skill bonus, +10 min success floor
FORTUNATE_STREAK Re-roll one failed skill check
MASTER_OF_DESTINY % chance to resist punishment effects
GRAND_STRATEGIST_POWER % chance to resist punishment effects
ESCAPE_COMBAT (special) +25 flee chance bonus
EXTRA_HIT_POINTS (special) +35% HP

Hunter Powers (Enemy-Type Bonus)

Power Target Type Damage Bonus
DEMON_HUNTER DEMON enemies +25%
UNDEAD_HUNTER UNDEAD enemies +25%
HUMANOID_HUNTER HUMANOID enemies +25%

Skills (39 Types)

Source: DarkTowerSkillTypes

ANIMAL_HANDLING, ARCANA, ATHLETICS, BLUFF, CHEMISTRY, CLIMB, COMMON_LORE, COMPUTERS, DEMOLITION, DIPLOMACY, DISCERN_CIPHER, ENDURANCE, HISTORY, INITIATIVE, INSIGHT, INTERROGATE, INTIMIDATE, INVESTIGATE, LEADERSHIP, LINGUISTICS, LUCK, MEDICINE, OCCULT_LORE, PERCEPTION, PERFORM, PILOTING, PSIONICS, REACTION, RELIGION, SCHOLASTIC_LORE, SLEIGHT_OF_HAND, STEALTH, STREETWISE, SURVIVAL, SWIM, WARFARE

Meta types: ANY, ALL, NONE

Skill check bonus: +16 for owning the skill, +16 if skill matches stat = +32 total if fully matched.

Paragon System

Source: paragon fields, DarkTowerParagonTypes, DarkTowerAssignParagon,

Earning Paragon Points

Points = ceil((level - 100) / 10.0)  // 1 point per 10 levels above 100
Level 100: 0 points
Level 110: 1 point
Level 200: 10 points
Level 500: 40 points (maximum regular paragon points)
Level 500+: Special paragon points unlock (1 per 500 levels)

Assigning Paragon Points

Saying paragon opens assignment. Dispatch is DB-driven: DARK_TOWER_ASSIGN_PARAGON and DARK_TOWER_ASSIGN_PARAGON_SPECIAL both resolve to darkTowerAssignParagon (process_action_handlers rows 280 and 299, both enabled).

Normal points first. If the character has any unspent normal point, the menu is the six stat options (armor, damage, skill, crit chance, hit points, critical damage) — including at level 500 and above. Specials do not pre-empt that menu. A 500-plus character with unspent normals can assign them.

Specials stay, and they record themselves. Special points are offered only when a special remains and no normal remains. Each special is a PARAGON-category enchantment (PARAGON_HIT_BONUS and its siblings). Picking one grants that enchantment onto activeEnchantments and records the matching CLUE_PARAGON_* clue. specialParagonAssigned counts those clues, so the spend sticks and a later visit does not offer the same special. assignParagonSpecial is live code; commented-out grant and option loops are not kept. If the catalog has no unowned special left to pick, the handler does not render an empty menu.

Error does not grant. If assignParagonSpecial is invoked with no special left to spend, it speaks the error and returns without adding a clue or an enchantment.

Paragon Stat Caps

Source: paragon fields

Paragon Stat Field Max Notes
Damage % paragonDamagePercent 20
Armor % paragonArmorPercent 10
Skill % paragonSkillPercent 10
Crit Chance % paragonCriticalChancePercent 10
Crit Damage % paragonCriticalDamagePercent Unlimited Deep endgame sink
Hit Points % paragonHitPointsPercent 10 Each point = +1% max HP

Note: DarkTowerParagonTypes has 6 values: ARMOR, DAMAGE, CRITICAL, CRITICAL_DAMAGE, SKILL, HIT_POINTS.

Design Intent

Crit damage is the only uncapped paragon stat. Players who push to level 500 can dump all remaining points into crit damage for meaningful endgame scaling. This rewards sustained play without breaking early/mid game balance.

Companion System

Source: companion fields

Slots

companionOne through companionSix (6 companion slots)
mount: DarkTowerEnemy  // Mount is technically an enemy type

Companion Impact

Source: DarkTowerCompanion model fields,

Companions provide two categories of bonuses:

Passive Stat Bonuses (From Companion Data Model)

Each companion has data-driven percentage boosts stored in DarkTowerCompanion:

Field Effect Typical Values
healthBoostPercent Added to HP percentage multiplier Per-companion, data-defined
goldPercentBoost Increases gold earnings Per-companion, data-defined
experiencePercentBoost Increases XP earnings Per-companion, data-defined
tokenPercentBoost Increases special token earnings Per-companion, data-defined
companionPotionBoost Increases potion effectiveness in combat Per-companion, data-defined

Combat Ability Scaling (Calculated from Level)

Source: — all values scale with companionEffectiveLevel = characterLevel + companionBoostedLevelPerks

Ability Type Chance Range Effect Range Notes
BLOCK 15-30% -- Block one attack per round (capped at 30%)
DAMAGE (attack chance) 20-40% -- Chance to attack each round, for every companion whatever its special. The lead companion, the first in the party, attacks every round, so a party is never silent; only the rest roll, because every companion certain to hit would end most fights before the player acts, the damage being a percent of the enemy's health
DAMAGE_DONE 5 + up to 50 -- Percent of max damage, percentOfCapped(8, level, 50) + 5
HEAL (heal chance) 15-30% -- Chance to heal each round
HEAL_DONE 3 + up to 20 -- Percent of max HP healed, percentOfCapped(3, level, 20) + 3
SKILL (boost chance) 20-40% -- Chance to boost specific skill checks
SKILL_DONE 4 + up to 35 -- Percent boost to skill check, percentOfCapped(5, level, 35) + 4

Special Ability Effectiveness (By Companion Type)

Source:

Special Type Effect Scaling Max
ASSASSIN level / 10 kills 10 kills
HEALER Full heal (effectiveness = level) --
HIT_BUFFER / ARMOR_BUFFER level / 8 + 3 flat bonus 15
DAMAGE_BUFFER / SKILL_BUFFER level / 4 + 8 percent bonus 30%
CRITICAL_BUFFER level * 1.25 + 10 crit damage 50%
DRAGONS_BREATH level + 8 AOE fire damage, plus a 6-round fire DoT applied twice (non-stacking; the double application keeps the higher of two duration rolls) Uncapped
DAZE/DISARM/COMBAT_BUFFER level / 8 + 1 count 5
ACID/FIRE/POISON/CROSSBOW level / 8 + 1 DOT stacks 5

Special Ability Activation Chance

Source:

Special Type Chance Scaling
ASSASSIN, HEALER 100% Always activates
DAZE/DISARM/HIT_BUFFER/DAMAGE_BUFFER/ARMOR_BUFFER/SKILL_BUFFER/DRAGONS_BREATH/COMBAT_BUFFER 5-15% 5 + percentOf(5, level), capped 15%
ACID/FIRE/POISON/CROSSBOW/CRITICAL_BUFFER 10-25% 10 + percentOf(8, level), capped 25%

Companion Level Boosting

Source:

Base companion bonus is +10 to effective level. Additional bonuses:

Source Bonus
Base +10
Early-game aid (character level < 10) +(10 - characterLevel), decays by 1 per level to 0 at level 10
SIGNIFICANT_COMPANION power +5 + (3 * weapon boost)
RALLY power +5 + (3 * weapon boost)
Auxiliary trinket (companion-specific) +15
Necklace trinket (companion-specific) +15
Enchantment companion bonus Variable
BLUE rarity companion +10
PURPLE rarity companion +20
EMERALD rarity companion +30

Starter Companion

New characters receive Douglas Your Dog (COMPANION_PET_DOG) in slot one via addBasicCompanions(), called during startGame() on first character creation (both the real-name confirmation path and the quick-start path). The companion has COMBAT_BUFFER special and no clue requirement.

Because early fights are the hardest for a brand-new player who has only this one starter companion, the starter (and any companion held below level 10) receives a decaying early-game effective-level bonus (see the Early-game aid row in Companion Level Boosting above). The bonus is largest at level 1 and reaches zero by level 10, so it eases the opening fights without affecting mid or late game balance.

The starter is a free companion that sits outside the level-gated slot accounting. Earned companion slots unlock at levels 10, 20, 30, and 40, so a level-1 character has zero earned slots. therefore exempts the starter (COMPANION_PET_DOG) when it reconciles filled-versus-available companion slots — without that exemption the starter is counted as occupying earned slot one and is stripped the first time abilities reconcile after creation, leaving the player with no companion (DT-2026-0603-003, regression scenario_test 1855).

Viewing and Choosing Companions

Players inspect their party and pick a new companion through , wired to the DARK_TOWER_PICK_CHAT_COMPANION action. NLU phrases that reach it include "choose companion", "pick companion", "select companion", "my companions", "view companions", "who is traveling with me", "my party", and "characters to travel with". The handler speaks the current roster, reports companion-slot status (slots unlock at levels 10, 20, 30, 40), and -- when a slot is open -- lists every currently selectable companion as a pick option that reuses the upgrade-apply path (DARK_TOWER_UPGRADE with key ITEM_COMPANION_{enumKey}). Companions gated behind a capture clue (for example the exploding creatures, which require CLUE_FOUND_EXPLODING_*) are not selectable until captured; when nothing is selectable the handler explains that path instead of showing an empty list.

The DARK_TOWER_PICK_CHAT_COMPANION action previously had no row in process_action_handlers, so all of those phrases dead-ended with "that command isn't available" -- a player could neither see nor choose companions outside combat (the in-combat status sheet was the only place companions surfaced). Regression: game_test.scenario_tests 1836.

Dismissing and Swapping a Companion

The same menu offers Dismiss a Companion and Swap a Companion whenever the party holds a companion that can leave. They are free; a full reset of every slot stays the paid reset, and a full party is offered it beside the swap. Both run through viewCompanions, with the verb in the option key or in the player's words. Only "dismiss Helga" reaches the change by name from anywhere outside combat, through its one free-form row; "release companion" and the swap, change and switch words are exact rows that open the list, because bare swap routes to using an item.

  • Dismiss names who can leave, or acts at once on a named companion: the companion goes and its slot opens. A name is whole words of the companion's name, so a word that only fits inside one asks who.
  • Swap does the same and opens the companion picker on the same turn. With no other companion unlocked it changes nothing and says so, so a swap never leaves a player with an empty slot and no one to fill it.
  • The starter stays. COMPANION_PET_DOG is free and sits outside the slot budget, so sending it away frees nothing.
  • A companion on a mission stays until the mission is collected, and does not swing in combat while it is away.
  • Hero progress is asked about first. A companion with hero level or hero experience ( is false) is named with the level it would lose, and only a yes sends it away, the same rule the reset holds to.
  • Blocked mid-combat by blockedFromGeneralActions, as the reset is.

Gods System

Source: DarkTowerGodTypes, DarkTowerGodDomain

8 Gods

God Associated Domain(s)
ELYSIUM --
MERRIS --
DESTIN --
TORRENT --
PERRIK --
APOLLO --
VENUS --
POSEIDON --

Domain Conflict System

Source: DarkTowerGodDomain

5 opposing domain pairs -- gods with opposing domains cannot coexist in active slots:

Domain Opposes
LIGHT DARK
ORDER CHAOS
LIFE DEATH
WAR PEACE
NATURE ARCANE
conflictsWith(other): return this.getOpposingDomain() == other

God Data Model

Source:

Each god has:

  • primaryEnumKey: DarkTowerGodTypes identifier
  • name: Display name
  • description: Lore text
  • prayerTypes: DarkTowerEffectTypes — the effect granted when praying
  • weaponOfGod: DarkTowerWeaponTypes — the sacred weapon associated with this god
  • domain: DarkTowerGodDomain — one of 10 domains (affects passive bonuses)

God Slots

  • Active slots: 4 maximum (activeGodOne through activeGodFour)
  • Level requirement: 25 (minimum to pray, per getMinimumLevel())
  • Devotion tracking: Map<String, Integer> devotionLevels (god name -> devotion 0-100)
  • Prayer locations: 8 free explore points (one per god, e.g., ELYSIUM_PRAYER_POINT)
  • Sacred weapons: Each god has an associated weapon (5% drop chance at max devotion at own temple)

Prayer System

Source:

Praying does the following:

  1. Grants the primary god's prayer effect (addEffect(prayerTypes))
  2. Increases devotion by +5 per prayer (capped at 100)
  3. Sacred weapon drop: At a god's own temple with 100% devotion, 5% chance to receive the sacred weapon (one-time only, checked via isOwned())

Devotion System

Source:

Devotion is a 0-100 scale per god, tracked in .

Devotion Tiers

Range Tier Significance
90-100 Zealot Full passive bonuses, can request blessings (95% success)
70-89 Devout Strong bonuses, can request blessings (70-89% success)
50-69 Faithful Moderate passive bonuses
30-49 Follower Minor passive bonuses
10-29 Novice Minimal passive bonuses
0-9 Agnostic No bonuses

Divine Blessings (Active Ability)

Source:

Players with 70%+ devotion (Devout or Zealot tier) can request blessings from their primary god:

  • Success rate: Equal to devotion level (capped at 95%)
  • Devotion cost: -10% (Devout) or -5% (Zealot)
  • Effect: Grants the god's prayer effect with a domain-themed narrative
  • Each domain provides a thematic blessing (e.g., WAR = damage boost, PEACE = defense boost, LIFE = regeneration)

Devotion Passive Bonuses

Source:

The primary god's domain determines which passive bonuses scale with devotion. Bonuses are percentage multipliers applied to gameplay systems:

Domain Primary Bonus Novice (30%) Follower (50%) Devout (70%) Zealot (90%)
ARCANE Experience +5% +10% +15% +25%
NATURE Gold find +10% +20% +30% +50%
WAR Damage +5% +10% +15% +20%
DARK Damage +5% +10% +15% +20%
CHAOS Damage +5% +10% +15% +20%
PEACE Defense +5% +10% +15% +20%
LIGHT Defense +5% +10% +15% +20%
LIFE Healing +10% +20% +35% +50%
All others XP (minor) +2% +5% +8% +12%
All others Gold (minor) +2% +5% +8% +12%

Critical Hit Bonus (Domain-Specific)

Domain 30% Dev 50% Dev 70% Dev 90% Dev
CHAOS +5% +7% +10% +15%
ORDER -- +3% +5% +8%

God Registration

Source:

  • Must be at a temple (KEEP_LOOKUP_POINT with god prayer point)
  • God is assigned to first available slot (1-4)
  • Domain conflict check prevents opposing domains from coexisting
  • If all slots full, must remove a god first ()
  • Removing a god applies a devotion penalty (percentage reduction)

Multi-Character Support

Each account can have multiple characters with independent progress. The character management menu supports:

  • Add new character
  • Delete character
  • Switch between characters

Legacy Dark Citadel character migration (LOCKED design — not shipped)

Players with a linked email can eventually migrate one merged character from the legacy Dark Citadel skill into Dark Tower. Canonical design: character-migration.md. Non-negotiables:

  • One-shot + confirm — post-migrate Dark Citadel play does not transfer again.
  • Level scale — legacy is uncapped (live max ~3400); DT hard cap is 500. Migrated level = scaled progress with ceiling 400 (80% of DT cap). See scaling constants in the migration doc.
  • Permanent season currency — soft currency never lands in a competitive CURRENT_SEASON bucket.
  • Free migration slot — successful migrate may bump maxCharacterSlots by 1 so the new character fits without a diamond charge.
  • Discord — migration problems/bug reports point players at Discord + report bug.

This is player-state migration (different project from legacy-feature-migration-audit.md, which brings back systems like heroes/invasions/research).

Delete is a hard delete of the persisted row. removes the character key from the account's characterKeys list and calls , which evicts the Redis cache entry AND deletes the dark_citadel.character_dark_citadel row. Character membership has no FK — a character exists only because some account's characterKeys JSON array names it — so a cache-only eviction (the pre-fix behavior) left the row permanently orphaned with nothing pointing at it once the key was dropped from the list, indistinguishable from live progress by any timestamp-based orphan check.

Each character maintains separate: state, inventory, story progress, clues, equipment, level, XP.

Character Switch and Create Recovery (no data loss)

Creating a character moves the account currentCharacterKey to the new level-1 hero; the prior character is never deleted, only out of view. A player who creates a second character can perceive total loss of their leveled main (player feedback 9016 and 9014), so the recovery UX below is mandatory.

  • Create (.createNewGameCharacter): after the creation confirmation, the player is told their previous character is safe, named with its level, and given the exact phrase, say switch character, to return to it.
  • Switch (.switchPlayer): names the character switched to with its level so the player can see when they have rotated back to their main, and points at saying switch character again to keep cycling.
  • Discoverability: switch character is a tier-4 DARK_TOWER NLU command, with recovery synonyms such as my characters, other character, character menu, and character list, and it is listed in the help command menu whenever the account owns more than one character.
  • Change class is not a re-roll (.handleUpgradeAction): classes are permanent once chosen. When a fully-classed character says change class, the game explains classes cannot be changed and that creating a new character is not required, so players never sideline their main chasing a class change. An open class slot, a new hero primary or an unlocked secondary, still opens the picker. Resetting is distinct from changing: the paid Reset feature (see the Reset & Respec System section above) deliberately clears a chosen class (and other build dimensions) for a diamond cost so the player can pick again from scratch -- the free change-class path stays a no-op, but the paid reset is the sanctioned re-roll. A full "Everything" reset may also be paid with a reset token -- a diamond-purchasable, stockpileable respec currency ("buy reset token"); see economy.md for the token economics.

Regression coverage: JUnit DarkTowerPlayerGeneratorTest covers the create and switch announcements; the multi-character precondition is not seedable in the single-command scenario framework (see scenario id 125), so scenario_tests 2102 and 2103 delegate to that JUnit class.

Slot Purchase & Persistence

  • Source: , .{createNewGameCharacter,switchPlayer,purchaseCharacterSlot}, .
  • Max slots: Stored on (default 1). Progressive diamond pricing per : slot 2 = 100, 3 = 250, 4 = 500, 5 = 1000, 6 = 2000, 7 = 5000, then 10,000 * (slotCount - 6).
  • Save-before-reload rule: Any handler that mutates (characterKeys, currentCharacterKey, maxCharacterSlots) MUST call before finalizeCharacter / populateDarkTowerProperties runs. The populate path re-fetches from Redis; without the prior save, in-memory mutations are wiped and saved character records become orphaned (no account's characterKeys list references them). Regression: fb 941 / .

Insanity System

Source: , DarkTowerStatus.INSANITY

Mechanic

Insanity is a percentage (0-100) stored in insanityLevel.

Escalation (between INSANITY-status cards):

0-50 insanity:   +1d6 per card transition
51-80 insanity:  +1d4 per card transition
81-100 insanity: +1d2 per card transition

At 100%: Clears to 0, redirects to card 15653 ("Landing Point Insane").

Exit reset: Leaving INSANITY-status cards (moving to non-INSANITY card) resets insanity to 0.

Other sources: Punishment types INSANITY, INSANITY_MINOR, INSANITY_MAJOR can add insanity. Reward types INSANITY_HEAL, INSANITY_HEAL_MINOR, INSANITY_HEAL_MAJOR reduce it.

Difficulty scaling: INSANITY-status cards get +0 to +7 difficulty bonus based on current insanity level.

Notification throttling: 10-minute cooldown on insanity messages. Four tiers: LOW (0-10), MINOR (11-50), MAJOR (51-90), CRITICAL (91+).

Active Effects

Source:

Map<DarkTowerEffectTypes, Long> activeEffects  // Effect type -> stack count

Persistent effects (survive between combats) tracked by type and stack count. Different from combat-only positives/negatives which are cleared after each fight.

Learn Codex (the "research menu")

Source: , DarkTowerLearnableRegistry, FreeTextDeclarationAction.DARK_TOWER_LEARN

The Learn Codex is the in-game encyclopedia. Saying "learn" opens a category menu, and the player can browse a category ("learn weapons") or look up any object by name ("learn about longsword"). This is the system player Lauri (lauri2023@gmx.de, 2026-06-27) called the "research menu" -- a place to read up on the game's content. It is DISTINCT from the Research System below (research slots and the perk tree): the codex is read-only knowledge, the Research System is a progression mechanic.

How it works

Concern Behavior
Entry "learn" with no key -> handleCategorySelection lists the browsable categories.
Categories The DarkTowerFeatureTypes the registry can enumerate: Weapons, Armor, Enemies, Items, Skills, Classes, Companions, Trinkets, Gods, Gems, Powers, Stats (the last three restored 2026-06-28). Each routes through DarkTowerLearnableRegistry.findByType.
Direct lookup "learn about {name}" -> findAllMatchesByKey ranks matches (exact enum, name, plural, contains) and reads the best, or lists choices on a tie.
Filtered lookup a filter key ("ARMOR_GLOVES", "RED_GEM") -> findByFilter via each object's CanBeFiltered.matchesFilter.
Pagination five per page; canonical More and Previous options (page-in-key, option-lists-and-pagination.md vocabulary); boundary guard re-renders page one past the end; "choose different category" returns to the menu.
What is learnable any object implementing both CanBeLearnedAbout and CanBeDiscoveredByKey.

Category parity (2026-06-28, player Lauri)

The rebuilt codex menu had dropped three browsable categories the registry could already serve: Gems, Powers, and Stats. All three model types (DarkTowerGem, DarkTowerPower, DarkTowerStat) implement CanBeLearnedAbout and have wrapper getters (getAllStoryGems / getAllStoryPowers / getAllStoryStats), so they were reachable by direct name lookup but missing from the browse menu. They are now registered in findByType, added to getAllLearnableObjects (so cross-type key search covers them), and listed in the category menu.

Collectibles and Races browse (2026-06-28): both are now browsable. DarkTowerCollectible gained CanBeLearnedAbout (it already had CanBeDiscoveredByKey) and DarkTowerRace gained both learnable interfaces; both are registered in findByType + getAllLearnableObjects and listed in the category menu, sourced from getAllStoryCollectibles / getAllStoryRaces.

Gear Sets and Effects browse (2026-06-28): both are now browsable. DarkTowerSet and DarkTowerEffect gained the learnable interfaces and are registered in findByType (STORY_SET / EFFECT) + getAllLearnableObjects and listed in the menu, sourced from getAllStorySets / getAllStoryEffects. The codex now browses 16 categories -- every DarkTowerFeatureTypes with backing content.

Not applicable: RECIPE remains a DarkTowerFeatureTypes value with NO backing content -- the wrapper service has no getAllStoryRecipes and there is no DarkTowerRecipe model, so there is nothing to browse. Wiring it would require first creating the recipe content model + wrapper getter. This is the codex half of the larger weapon-categorization work (a player-defined weapon category and sort restore is the matching open item -- see economy/exploration combat-filter notes).

Weapon type browse (2026-06-28, player Lauri)

Lauri also asked for "weapon types ... make your own categories." The codex's weapon category now opens as a TYPE PICKER -- Bladed, Blunt, Ranged, Other -- instead of paging the entire ~480-weapon list, so the player can browse by kind the way Dark Citadel allowed.

The bug it fixes: DarkTowerWeapon.matchesFilter derived the type bucket from weaponClassType, which is HANDEDNESS (one-handed / two-handed / shield), so it produced ONE_HANDED_WEAPONS and never matched the codex's BLADED_WEAPONS / BLUNT_WEAPONS / RANGED_WEAPONS / OTHER_WEAPONS keys -- weapon-type browsing was dead. The category is now DERIVED from the weapon's range and damage type:

Bucket Rule
RANGED_WEAPONS darkTowerWeaponRangeType == RANGED (wins first: a piercing bow is still ranged)
BLADED_WEAPONS melee and damageType is SLASHING or PIERCING
BLUNT_WEAPONS melee and damageType is BLUDGEONING
OTHER_WEAPONS everything else (elemental or shield melee weapons)

Both fields are populated on every weapon content row, so the four buckets are well-filled. A second fix went in alongside: parsed the page with a fixed three-way split("_"), which broke on any underscore-bearing filter key (BLADED_WEAPONS, ARMOR_GLOVES) -- "More Bladed Weapons" would have paged nothing. It now parses the page off the END.

Still deferred: player-DEFINED named weapon categories (a personal favorites / custom-bucket system) and the rarity / gear-score SORT selector were both lost in the rewrite and are net-new builds, not part of this fix. The voice readout of a weapon's learnAbout() still speaks the raw handedness enum ("Type: ONE_HANDED") -- a separate voice-polish item.

Research System

Source:

Map<DarkTowerResearchSlots, DarkTowerResearchDetails> researchSlots

Research slots are keyed by DarkTowerResearchSlots enum, each containing research progress details. Unlocks abilities/perks via a research tree with prerequisites.

Research Stations: Buy-Only, Not Captured

Status: LOCKED (2026-05-12). Research stations are unlocked through paragon progression and slot purchases, NOT by capturing world-map zones. The earlier capture-point design (where players competed for PLAYER_RESEARCH_BOOST and similar capture-point zone types out in the free-explore world) is permanently retired.

Rationale. Player feedback rounds 718-721 (Round 3 of work.task.id=19054) flagged that capture-based research stations would let strong players hoard contested zones and lock weaker players out of progression. The capture-point system was already disabled in code; this lock formalizes the buy-only model as the canonical design. deleted the 6 dead PLAYER_*_BOOST enum values from DarkTowerFreeExplorePoint, the dead isCaptureWorldPointType() utility, and supporting comment stubs.

What players see. No change in prod. Research slots remain VIP-gated and purchasable. No capture mechanic for research exists. Do not reintroduce capture-style world-map contention for research progression — if a future design wants player-vs-player resource competition, route it through invasion assault points (which are designed for that) or guild systems, not research.

Research: troop training -- SHIPPED (2026-06-28)

The first migrated research slice is TROOP TRAINING (the old "Train Soldiers"), built on a pure cost util. From the research menu ("research") a player picks "train troops" and trains any of five troop types -- soldiers, archers, artillery, knights, cannons -- each a DarkTowerResearchType.TRAINING slot.

Concern Behavior
Start "train soldiers" charges an escalating GOLD cost (, triangular in the slot's completed count) and stamps timeUntilFinished = now + trainingMinutes.
Status the training menu shows each slot: idle (cost + minutes for the next batch), in progress (minutes remaining), or ready.
Collect "collect troops" awards troopsPerBatch of the slot's troop collectible for every finished batch, raises the slot's amountResearched, and clears the timer -- so the next batch costs more, takes longer, and yields slightly more.
Defaults (tunable) base 500 gold / 5 minutes / 5 troops at count zero, all in the tunable block.

is pure + JUnit-pinned (); the real-time countdown is not asserted against a live clock (hermetic-test rule). Code: + .

ISP value (leverages the EXISTING catalog, owner guidance 2026-06-28): training makes the army REAL, which makes the EXISTING VIP-gated extra slots worthwhile -- RESEARCH_SLOT_VIP and BUILDING_RESEARCH_SLOT_VIP (gated by CLUE_VIP_SECOND_* clues a VIP/ISP purchase grants) give a player MORE concurrent research and training capacity, so owning the existing VIP ISP now buys real value. The win is boosting the EXISTING ISP, not a new product (memory feedback_isp_wiring_leverages_existing_isps).

Keep building upgrades -- ALSO SHIPPED (2026-06-28): a player upgrades any of the eight keep buildings (stables, armory, bakehouse, walls, chapel, moat, barracks, silo) via "upgrade buildings" then "upgrade walls". Each upgrade charges escalating gold (), runs a real-time countdown, and on collect PERSISTS a higher level in , read by -- so the keep handler's reward scaling now actually improves (the previously-dead read-side is live). Levels are PER-BUILDING and permanent (the "better than Dark Citadel" choice: a building never loses its level); the two building research SLOTS cap how many upgrade AT ONCE -- one for everyone, a second unlocked by the EXISTING VIP building slot -- so the VIP ISP buys real concurrency value (the ISP win is on the existing catalog, not a new product). getUpgradeLevel reads buildingLevels first with a backward-compatible fallback to the old slot scan, so existing characters (empty buildingLevels) see no change until they upgrade. JUnit pins buildingCost / buildingMinutes.

Still to build (the lifecycle spec below): the 2 research-slot PERK projects (character upgrades), plus character-stat / army research effects. SHIPPED 2026-06-29 (see the subsection below): the speed-up consumable, content-driven RESEARCH_BUILDING costs debiting gold, wood, and iron, the guild tavern as a raisable building, and an interim troop keep-defense hit-point bonus.

Research 4X completion -- SHIPPED (2026-06-29)

Four migration gaps closed, all built on the existing research backbone:

  • Speed-up consumable. The research menu's "speed up" option opens a speed-up menu listing every in-progress training or building timer. Picking one consumes an owned ITEM_SPEED_UP_BOOST_* (one, five, or sixty minutes) against that slot's timeUntilFinished -- subtracting the boost minutes, or finishing the slot now if the boost covers the rest. It prefers the smallest owned boost that finishes the remaining time, otherwise the largest, so a sixty-minute boost is not wasted on a one-minute job. Wired in with new key branches on DARK_TOWER_RESEARCH (no new action), reusing hasItem / removeItems. This closes the sink the menu already advertised.
  • Guild tavern is raisable. GUILD_TAVERN joined the upgradeable keep-building set, so the one building with a live combat read-side -- adds two percent hit points per tavern level -- can now actually be levelled through the building menu.
  • Data-driven building costs (RESEARCH_BUILDING rule_type). Per-building costs moved out of the single hardcoded base into content: each keep building has a row in dark_citadel.rule_dark_citadel_common with rule_type='RESEARCH_BUILDING' and a rule_data JSON of baseGold / baseWood / baseIron / minutesToLevel, loaded by the new DarkTowerResearchBuildingRegistry (the same single-table rule pattern as enemies / weapons). A building upgrade now debits gold AND wood (WOODEN_LOG) AND iron (IRON_SHEET) -- the resources the keep already produces. Gold escalates triangularly; wood, iron, and minutes escalate linearly. A building with no row falls back to a default def, so existing characters always upgrade. The Java DarkTowerUpgradeTypes enum stays the stable KEY (the rule_id). Rows authored on prod and local.
  • The building menu is a summary. "Upgrade buildings" says what is ready, what is upgrading, the cheapest upgrade the player can start now in gold, wood and iron, and how many can start; "building costs" reads every building's level and next cost ().
  • Troop sink -- interim keep defense. Trained troops (soldiers, archers, artillery, knights, cannons) now give a small, capped hit-point bonus read live in calculateHitPoints (DarkTowerResearchBuildingRegistry.keepDefenseHpPercent: about one tenth of a percent per troop, capped at ten percent at one hundred troops), so an army has an interim defensive payoff. Hero troop-command remains the future full sink.

Pure cost + troop curves are JUnit-pinned (); the real-time countdowns are not asserted against a live clock (hermetic-test rule). Still open after this slice: the 2 research-slot PERK projects (character-stat upgrades) and concrete army / character research effects.

Research lifecycle -- the migration target (owner-flagged not-migrated; design TBD 2026-06-28)

The slots map, the DarkTowerResearchSlots / DarkTowerResearchType / DarkTowerResearchDetails model, and the hub all EXIST, but the system is SCAFFOLDING ONLY: is informational (it mentions open slots and routes to Character / Perks) -- there is no way to START a research project, run its countdown, spend a speed-up, or COLLECT it. already READS a buildingLevel for reward scaling, but nothing ever WRITES it, so that read-side is dead. This is the owner's "research system" gap, distinct from the Learn Codex (which Lauri called the "research menu" -- see the Learn Codex section above).

Was (old Dark Citadel 4X layer): an async build / train / research layer -- nine slots (5 training, 2 research, 2 building; VIP unlocks extras). Lifecycle: pay the cost up front -> a real-time countdown -> optional speed-up items -> collect and level up. Old refs: engines/handlers/story/research/** (, , buildings/AStoryUpgradeBasic, troops/); service/story/; utils/story/ (triangular cost formula :39-47); story_character.research_time.

Build stages (each its own focused change -- NOT yet built):

  1. (pure: the triangular calculateCost(level, base) plus a minutesToLevel curve) + JUnit. Cost and time scale with the target level.
  2. Content: port the building / research defs to a RESEARCH_BUILDING rule_type in rule_dark_citadel_common (baseGold / baseTokens / baseWood / baseIron / minutesToLevel), so costs and effects are data, not hardcode.
  3. The slot lifecycle on DarkTowerResearchDetails: START (debit the cost through the cache-writeback money path, stamp timeUntilFinished), STATUS (time remaining), COLLECT (once the clock has passed, apply the level-up), plus a speed-up consumable.
  4. Concrete effects: the first and simplest is leveling a building -> write buildingLevel, which the existing read-side picks up automatically (the audit's "activating the write lights up the read-side"). Then character-stat research, army (+armor / +attack), and troops.
  5. Rewrite the hub from informational to actionable + register the start / collect / speed-up NLU actions.

Open design decisions (owner's call before building):

  • Cost + time curve -- how steep is the triangular cost, and how long are the countdowns (minutes that scale how)? This sets the whole pace of the 4X layer.
  • What research grants -- which concrete boosts (building reward scaling, character stats, army combat bonus, troop counts) and how large.
  • ISP integration -- the Step-3 idea: an EXTRA research slot (or a research speed-up) as a diamond-purchasable ISP perk, the same lever as the reset-token idea. Routes through .

Why this is a careful, dedicated build: it is a multi-component async system (cost util + content + slot lifecycle + effects + NLU) whose pacing knobs are owner-taste calls, and the real-time countdown makes it awkward to test hermetically (a collect test must seed a past timeUntilFinished). Build it deliberately once the curve and effects are decided, not rushed.

Statistics Tracking

Source: stats fields, written by DarkTowerStatsUtility.

Only the trackers with a live writer remain. The trackers whose data nothing ever read (chapter starts/ends, aggregate combat damage, item use, crafting, playtime, session count) were removed as write-only dead code, along with tower-wave tracking — the "tower defense" handler runs a single combat, not a multi-wave loop, so there was no wave-completion event to record.

Tracker Key Format Purpose Consumer
enemyKills {enemyType}_{difficulty} Kill counts by type Inspect screen + title gate
actionUsage action_name Special action use count; CAPTURE counts successful captures Power-recharge cap + crowned-title gate + the 25-capture milestone
checkStats {checkType}_{difficulty} Skill check pass/fail counts Recorded (no surfaced consumer yet)
cardVisits / cardTransitions card_id / {fromCard}_{toCard} Card visit + navigation tracking Recorded (no surfaced consumer yet)
fishingStats fish_type Times caught Recorded (no surfaced consumer yet)
recentLoot Deque Last 200 loot events (circular) "last rewards" command (populated directly by the reward path, not via a tracker)

towerStats and globalStats getters remain on the cache (the title gate still reads towerStats.highestWaveReached) but have no writer; they stay empty until a real tower-wave loop and a session/playtime timer exist. Rebuild the removed trackers from this table when a consumer for their data is added.


Power Progression & Balance Curve

New characters unlock powers via level gates, class training, and clue rewards. The balance guardrails in combat.md (Rules 1-5) govern in-fight behaviour; the progression rules below govern which powers a player may access at a given level.

Level Gates (from DB `rule_data.levelRequired`)

Level Powers Unlocked (sample)
1 Class power only (e.g. SNEAK_ATTACK for Rogue)
5 INVISIBILITY_TRIGGER
6 CHARM_MONSTERS
10 DODGE, FLIGHT_TRIGGER, CHANCE_INVISIBILITY
15 DOMINATE_MONSTER

No levelRequired gate is lower than the combat caps permit. Low-level powers (e.g. starting class SNEAK_ATTACK which is already UP_ONLY capped at 2 uses/combat) are intentionally less powerful than level-15 abilities.

Power Curve — Newbie Powers Should Never Outperform Late-Game

When adding a power, check:

  • Is this power accessible at level < 10? Its per-combat ceiling must be strictly lower than a comparable level-15+ power.
  • Does it match the power-tier of the cost? Class-locked and clue-locked powers should be stronger than picked powers at the same level.
  • Does an early-access version exist that is functionally equivalent to a late-access version? If so, the early version must be weaker (lower damage multiplier, narrower enemy cap, shorter duration).

The Sneak-Attack Family (Post-Audit Status)

Three powers implement stealth-like behaviour. The audit confirmed each stays balanced under the new caps:

Power Level Special Per-Combat Cap Notes
SNEAK_ATTACK (Backstab) 1 (class) UP_ONLY 2 Massive damage but enemies still on the field next round
ASSASSINATE 1 (class) UP_ONLY 2 Same pattern; higher damage multiplier reflects class cost
INVISIBILITY_TRIGGER 5 INVISIBLE_BOOST 2 + enemy-count gate Now capped at ≤2 enemies AND ≤2 uses per combat
CHANCE_INVISIBILITY 10 INVISIBLE_BUFF (passive 25%) — Passive proc; remains uncapped because it cannot be deliberately chained

Crucially, resting, defeating the last enemy, or fleeing all clear actionUsage counters via clearCombat() — so caps do not bleed across encounters. The player is never permanently denied a power, only prevented from looping it within one fight.

Class-Gated Power Picker — Withering Touch Gap ( review, fb 9683)

Status: FIXED (2026-07-16).

Player report: Withering Touch is missing from the reset/upgrade Powers picker for the Warlock class ("the wall class" in the player's phrasing). Content-data check (dark_citadel.rule_dark_citadel_common) confirmed the prerequisite chain SHOULD resolve cleanly:

  • POWER_WITHERING_TOUCH — sub_type: CASTER, canBePicked: 1, darkTowerPowerTypesRequired: LIFE_DRAIN.
  • CLASS_WARLOCK — sub_type: CASTER, classPowerTrained: LIFE_DRAIN.

A Warlock trains LIFE_DRAIN as its class power, which is the exact prerequisite Withering Touch requires, so it should surface as pickable once the player has that class power. The content row exists and was correctly gated — the gap was a picker/filter bug, not missing content.

Root cause. DarkTowerPower.hasPrerequisitePower (backing canBeSelectedBy, the filter every power in the "All" picker is checked against) only scanned the character's chosen/gear power slots (chosenPowerFirst/Second/Third + gearPowers) for the required prerequisite power. A class's trained power (e.g. Warlock -> LIFE_DRAIN) is never materialized onto / secondaryClassPower — those fields are only ever nulled by Reset, never written by class selection — so a class-granted prerequisite could never be recognized, even though combat's own "has this power" check () correctly reads primaryClass.getClassPowerTrained(). Any power whose prerequisite is a class-trained power (not a picked one) was unreachable in the picker.

Fix. DarkTowerPower.hasPrerequisitePower now also checks character.getPrimaryClass().getClassPowerTrained() and character.getSecondaryClass().getClassPowerTrained() against darkTowerPowerTypesRequired, matching the source set already uses for combat. The private powerStream helper backing isAlreadyChosen/hasPrerequisitePower was also extended to include gearPowers (it previously covered only the three chosen slots), for consistency with the same canonical power-source set.

Verified live (Warlock, level 30): "upgrade" -> "all" -> "more options" now lists Withering Touch among the 30 selectable powers (was 29, missing it, before the fix); selecting it by name confirms "You've chosen Withering Touch." Regression guard: scenario_test 2819.

Title System

Status: SHIPPED (2026-05-16) — and the title cache fields are live; regression coverage is the fb-917 suite (scenario_tests 1181-1195). Full design in economy.md § Title Shop.

Identity Extension

Two new character cache fields support active and owned titles:

currentTitle: String                // Active title, prefixed to character name in narrator copy
ownedTitles: Set<String>            // Permanently unlocked titles, purchased from Title Shop

Display Name Pattern

When narrator copy references the player, it should call , which returns currentTitle + " " + characterName when a title is set, or just characterName otherwise.

Use getDisplayName() in: welcome / greeting copy, achievement announcements, level-up announcements, paragon milestone announcements, world events, chat broadcasts (chat-shortcut commands from 19084), auction win announcements.

Stay first-name-only in: NPC card dialog (close interpersonal voice), internal monologue / cinematic narration where formal address would break tone.

Acquisition

Titles are acquired exclusively through the Title Shop (gold cost + milestone gate). They cannot be earned directly; the underlying milestone (level threshold, devotion 100%, raid clear, enemy kill count) is the eligibility check that lets a title appear in the rotation, but the player still pays gold to claim it. This keeps gold-for-prestige distinct from gold-for-stat (declined in round 3).

Cross-References

  • Catalog, pricing ladder, rotation mechanic, NLU surface, regression tests: see economy.md.
  • Voice rules for displayed names: see voice-tags.md.

Paragon points begin at level fifty

Paragon points begin at PARAGON_START_LEVEL, fifty, and a point comes every ten levels past it: max(0, (level - 50) / 10). A player below it hears the level they start at. is the definition.