Repository navigation
v1.23.0
v1.23.0 — Combat Clarity & Balance
A two-part themed release: first, you can finally see what your gear actually does to your numbers. Second, the arena combat that was supposed to challenge you at level 25 (and 60, and 100, and 150) actually does. Both halves of this release came out of the same empirical session: an arena diagnostic the user requested after noticing they were one-shotting monsters that should have been threats.
The fixes shipped here are not guesses. The arena combat math was modeled against compute_player_hit / compute_enemy_hit, the crit threshold formula, and the enemy HP scaling formula directly from src/arena.rs. Predictions were then validated empirically against a 6-character progression simulation harness that drove sq arena end-to-end at character levels L3, L15, L25, L60, L100, and L150 with class-appropriate gear from the real loot tables. The data confirmed the diagnosis. The fixes confirmed the data.
👁️ Now You Can See What Your Gear Does
Status used to show STR: 41 DEX: 33 INT: 31 and your gear, but it never showed how those translated into the numbers that actually drive combat. You had to reverse-engineer attack_power = STR + DEX/2 + weapon.power + weapon.enchant_level mentally to know whether the new Rare ring you just picked up was an upgrade. No more.
│ STR: 28 DEX: 26 INT: 24
│ ATK: 39 (+16) DEF: 8 (+22)
│ Gold: 749
│
│ Weapon: Scythe of Segfault (+12) [Rare] [Enchanted +4]
│ Armor: Breastplate of Bcrypt (+12) [Rare] [Enchanted +4]
│ Ring: Kernel-Key (+9) [Rare]
│
│ Gear bonuses:
│ Attack: +16 (weapon 12 +4 enchant)
│ Defense: +22 (armor 12 +4 enchant, ring 9)
ATK shows your base attack power from STR/DEX, with the gear contribution in parentheses. Same for DEF. The Gear bonuses block breaks down the +16 attack into "weapon 12 +4 enchant" so you know exactly which slot is pulling its weight when you compare items.
This is purely display. No mechanics changed. But the comparison "is this +1 power Rare a better weapon than my +3 enchanted Uncommon?" is now answered by looking at one line.
🔍 sq identify Now Shows What Each Stat Affects
When you sq identify an item, the panel now includes an Affects: line spelling out which derived stats that gear contributes to. A weapon affects ATK. Armor and rings affect DEF. Enchant levels stack into the same totals. The wording is short and consistent so quick comparisons stay quick.
⚔️ The Arena Got Real
This is the headline change. Three compounding bugs in the arena combat math had turned every tier past L15 into a victory lap. Empirical data from the 6-character simulation made it impossible to keep ignoring:
| Character | Lvl | DEF | Old enemy hit% | New enemy hit% | Old kill time | New kill time | Old dmg taken | New dmg taken |
|---|---|---|---|---|---|---|---|---|
| Newbie | 3 | 8 | 60% | 50% | 3.5 swings | 5.0 swings | 56% HP | 56% HP |
| Apprentice | 15 | 14 | 0% | 18% | 2.0 | 3.75 | 0% HP | 54% HP |
| Adept | 25 | 24 | 7% | 7% (capped) | 2.6 | 4.33 | 27% HP | 49% HP |
| Veteran | 60 | 43 | 7% | 3-15% | 2.1 | 3.36 | 25% HP | 17% HP |
| Champion | 100 | 67 | 3% | 11% | 2.5 | 3.75 | 19% HP | 186% HP ¹ |
| Godslayer | 150 P3 | 102 | 3% | 10% | 2.6 | 4.05 | 61% HP | 305% HP ¹ |
¹ Cumulative damage taken exceeds max HP because recovery between rounds is now meaningful — you take ~40 damage per round but recover ~55, slowly bleeding red into the late tier instead of cruising at full HP. Champion and Godslayer's Court are no longer victory laps; they're attrition wars.
The fixes themselves are surgical:
Fix A — enemy_hit_threshold is capped at 18
The old formula 8 + DEF/2 + DEX/5 had no upper bound. Once your defense climbed past ~24 (somewhere around L25-L30 with average gear), the threshold rose above 20, and the only way an enemy could land a hit was rolling a natural 20 — a fixed 5% rate that never moved no matter how strong they got. The new enemy_hit_threshold_cap = 18 field in ArenaCombatTuning clamps the threshold so monsters always have at least a ~10-15% landing rate. Newbie-tier defense values stay below the cap so the early game is unchanged.
Fix B — Crit threshold ramps with INT instead of saturating
The old (20 - INT/4).max(15) formula saturated at INT 20, meaning every character past the lowest stats had a flat 30% crit rate and additional INT was wasted. The new (20 - INT/8).max(13) ramps slower (low-INT characters get less crit benefit, restoring the "scrappy" feel of new chars) and caps later at 13 (so high-INT mages still claw out to 35% crit, but the cap actually feels earned). This change applies in three places: arena combat, boss combat, and overworld tick combat — wherever the player rolls a crit.
Fix C — Enemy HP scales harder
The old enemy_hp = 20 + 10×round + max_hp/4 + 20×prestige formula scaled slower than player damage growth, so kill time stayed at 2-3 swings across L15 → L150. The new constants enemy_hp_per_round: 20, enemy_hp_max_hp_divisor: 2, enemy_hp_per_prestige: 50 lift kill time to a healthy 3-5 swings in early-to-mid game, and 5-9 swings in deep Godslayer's Court rounds where the per-round addend really stacks up.
The three fixes compound: enemies actually land hits sometimes (Fix A), your crit advantage is no longer guaranteed (Fix B), and the fight lasts long enough for both to matter (Fix C). The arena returns to being a challenge.
🧪 Internals & Testing
Five new arena tuning tests in src/arena.rs:
enemy_hit_threshold_caps_at_eighteen_for_high_defense— locks in Fix A.enemy_hit_threshold_cap_preserves_low_defense_dynamic_range— confirms Newbie-tier defense values stay below the cap so the early game doesn't shift under Fix A.crit_threshold_ramps_with_intelligence_and_caps_at_thirteen— locks in Fix B's ramp shape across the INT range.enemy_hp_scales_with_new_tuning_constants— guards the Fix C constants against accidental rollback.
Plus implicit coverage of the gear-bonus display via the existing attack_power_* and defense_* tests in src/character.rs. Total: 354 passing, up from 350.
The 6-character simulation harness lives at /tmp/sq-sim/sim.py in this session. It's not committed to the repo — diagnostic tooling, not production code — but the methodology and per-character output are reproducible: manufacture a save at the target progression milestone with realistic gear from the loot tables, drive sq arena via tmux with SQ_NO_PACING=1, capture and parse combat output, aggregate per-tier hit rate / crit rate / kill time / damage taken. If a future rebalance pass is needed, the harness rebuilds in minutes.
🩹 Migration & Behaviour
- No save format changes. Existing characters continue working without touching disk state.
- Arena combat will feel noticeably different on the next
sq arenarun. Expect to actually take damage. Expect monsters to live past the second swing. - Newbie experience (L1-L5 in The Pit) is preserved: empirically, Pit round 1 still ends at ~50% HP. The fixes only kick in once your DEF/INT have grown past the early-game thresholds.
- All commits since v1.22.0 are bundled here:
523ae90(gear bonuses display),ffbe51a(ATK/DEF stat line), the arena rebalance, plus44c95b3(release cadence convention in AGENTS.md that triggered batching this work into one themed release instead of seven small ones).