Custom Roguelike - 9/21/26 (evening) — Embedding the Game Data in the Release Build
Published 2026-09-23
9/21/26 (evening) — Embedding the Game Data in the Release Build
Plan
New branch embed-resources. Release builds bake every file in resources/ into the exe (RON data via include_str!, PNGs via bracket’s embedded_resource!/link_resource!), so a player can no longer open and edit them; debug builds keep reading from disk so tuning stays quick. Modding is to be discussed later. After this: bring the balance bots up to date (backlog item 10), then the balance issues list.
Done: resources embedded in release builds
New assets.rs: ron_text(name) reads resources/<name> from disk in debug builds and from include_str! in release; every RON loader (template, classes, starting kits, tiers, final boss) goes through it. Release builds also call assets::link_sprite_sheets() at the top of main, which include_bytes!s all 21 PNGs and registers them with bracket’s link_resource! under the resources/<name> key with_font looks up. Checked: a release exe copied to an empty folder ran with no panic; cargo test passes. Not yet seen in the game window. A new PNG or RON file must be added to the lists in assets.rs or the release build panics (RON) or fails to find it (PNG).
Bots, step 1: battles run on the real ATB gauge
Backlog item 10 has begun (branch embed-resources, to be renamed or merged before the bot work continues). balance_sim::dungeon_bot::resolve_battle, shared by the dungeon bot, the arena bot and the boss sim, no longer plays “player acts, then every enemy acts”. It runs the real gauge race in Wait mode: every gauge fills at atb_fill_rate (Speed and Speed Potions count), whoever hits ATB_GAUGE_MAX first acts (a tie favours the player), gauges freeze while an action resolves, and time jumps to the next full gauge. First numbers, 5 runs per class: Barbarian 40%, Mage 60%, Rogue 100%, Amazon 60%, Hunter 60% (ChestWeapon route); Arena, 10 runs: Mage 6/10, the rest 10/10. Small samples - not a balance verdict.
Decisions for the rest of the bot work: quests (Shopkeeper, Witch, Blacksmith), buying and using Speed/Defense/Healing Potions and the Blade buff, every dungeon tier with per-tier ledger columns, the demon’s full fight; Arena bot uses abilities, buys and equips between levels, and reports deaths per level; two policies (an average player and an informed one).
Buffs and stealth: a round of playtest requests (branch buff-display)
-
Defense Potion no longer replaces Ice Armor. Both were the same
IceArmoredcomponent (only abadgelabel differed), so drinking one overwrote the other. The potion is nowProvidesEffect::DefenseBoost/ componentDefenseBoosted;resolve_enemy_attacksubtracts both bonuses and wears each down on its own, and each has its own HUD badge. Permanent test:boss_sim::defense_potion_and_ice_armor_stack. The potion’s number is still scaled byice_armor_scale, as before. -
Heal preview. Hovering a carried Healing Potion on the dungeon item bar shows the heal on the health bar: the extra part in green (
draw_pixel_bar_preview) and+Nin green after thecurrent / maxtext (capped at the health missing; nothing when full). The HP bar is now drawn at the end of the HUD system so the hover is known first. -
Stats screen buffs (Item Menu’s Stats box): a green (or red for a loss) total beside the stat: Defense gets the Ice Armor + Defense Potion
+N, Speed gets the Speed Potion’s+N%, Health and Damage get the Blade buff as+N%(the buff is already inside the number). Battle-only buffs (Dodge, Battle Cry) are not shown. -
Hidden player is darker. Stealth and Invisible tint the player
(70, 70, 90)on the map instead of plain grey. - Invisible Cloak can start a fight. Walking into an enemy (or Space next to one) while Invisible now starts a battle with the player’s ATB gauge already full, without Stealth’s triple damage, and uses the cloak up like Stealth does. Before, Invisible blocked the bump.
Not seen in the game yet - please check the hover preview position, the stats colours and the darker tint.
-
Dungeon Map fog.
Mapgainedseen_tiles(set wherever a tile is actually looked at: the player’s moves, the town’s whole-area reveal, the title background).revealed_tilesstill means “known”, so the Dungeon Map still fills it in. A known-but-never-seen tile now draws with a darker tint (50,50,50, walls 0.22 instead of 0.35) than a seen-and-remembered one (DARK_GRAY, 85). Not seen in the game yet - the shade difference may need adjusting.
Playtest fixes on buff-display
-
Heal preview never showed. The HUD system reads the hovered potion’s
Effect, but#[read_component(Effect)]was missing, so the lookup quietly returned nothing (the known legion access-mismatch class - it fails as a silentErr, not a panic, when read throughentry_ref). Added the attribute. -
Weapon damage in the stats. Damage now shows the carried weapon’s flat bonus (
+N) and then the Blade%, both green. -
Buffs survive quit and Continue.
RunSavehas abuffsblock (Ice Armor, Defense Potion and Speed Potion bonus + charges left, Invisible and Stealth moves left;#[serde(default)]so older saves still load), restored onto the player inrestore_run. Extended the permanenta_run_survives_save_and_restoretest to cover it. This closes the “temporary buffs are not saved” part of backlog item 13. -
Bar fill matched to the frame’s channel.
draw_pixel_barfilled across the whole tile span, including the frame’s end caps, so a full bar (and the heal preview) poked slightly past the border and a small heal could look like a full one. Measuredbattle_bar_frame.png: the walls cover pixels 1-5 (left cap) and 26-30 (right cap), so the channel is32 * tiles_w - 12px wide, starting 6px in.bar_channelnow places both the fill and the preview inside it. This applies to every pixel bar (the HP and ATB bars in battle as well), so a full bar now ends at the inner edge of the frame. Permanent test:bar_channel_tests. -
Heal preview is the green bar only. The
+Ntext aftercurrent / maxis gone (the tooltip already says how much a potion heals). -
Battle icons: debuff colour and hover. The number under each buff icon beside the player in battle is cyan for a buff and red for a debuff (today only the final boss’s War Cry weakening you; its
AttackDownshows under the “Stun” icon). Hovering an icon shows a small tooltip with its name and “Buff” (green) or “Debuff” (red); the mouse is read fromAbilityBarMousePos(the 32px grid the icons are drawn on), anddraw_battle_hudreturns the hovered icon sobattle_tickdraws the tooltip last, on top of the other boxes. Not seen in the game yet - the tooltip’s position is a first guess. -
History screen: Deaths, not Kills. The per-class table’s “Kills” column (enemies defeated, which read as something good) is now “Deaths” (
cs.deaths, the player’s own deaths per class). Enemies defeated are still on the “Enemies defeated” line, in the bottom-right list and on each class’s detail page. -
Battle icon hover fixed. The hit test read
AbilityBarMousePos, whose console has 40px cells (32x20), while the battle buff icons are drawn on the 32px grid (40x25), so the mouse had to be down and to the right of the icon. It now uses the plainPointmouse resource (console 0’s 40x25 grid). The tooltip lost its “Buff”/“Debuff” line: the name itself is green for a buff, red for a debuff. -
Battle icon tooltip text centred in its box. With the second line gone the box was three rows high but the text still used the standard inset (2 rows), so it sat on the bottom border. It now uses raw offsets (
text_color_raw(2, 1, ...), box height = text lines + 2, width = name + 4) like the item-bar tooltip.
Bots: branch bots-full-game
buff-display (which contained embed-resources) was merged into master (fast-forward) and the bot work continues on bots-full-game. The smallest useful sim run, one process, one run, one class, lowest priority so the computer stays usable (about 15 s of sim after the build):
SIM_PROCS=1 SIM_RUNS=1 SIM_CLASSES=Rogue nice -n 19 cargo test --release balance_sim::runner::class_survivability_report -- --exact --ignored --nocapture
(--exact keeps the Battle Arena report, whose name contains the same words, from running too.)
Bots, step 2: the town and the quests (balance_sim/town_bot.rs)
The dungeon bot now plays the town like the real game offers it: visit_errand walks to whoever has something to do (a finished quest to hand in, the Blacksmith’s ingot on a route that wants the weapon, or - with no quest active - a giver with a quest to accept; the cat’s hidden quest is skipped for now) and talks through the real talk_to_nearest, with a 400-step budget per visit so an unreachable person can’t pin it. It fetches the active quest’s floor item like it fetches the ingot (wanted_ground_items, and it keeps searching while quest_item_pending). Shopping (town_shop_step): 5 Healing, 2 Defense, 2 Speed, up to 8 Healing, then 2 Dungeon Maps, as gold and stock allow. prebattle_buffs drinks a Defense and a Speed Potion when an enemy is within 7 tiles and the effect isn’t already running. Each run logs an END line to target/sim_log.txt (quests handed in per giver, weapon tier, shop perk, potions left over). First small run (3 Mage runs): 2 won, both handing in Townsman and Townswoman quests and reaching weapon tier 2.
Bots, step 3: every tier, the demon, per-tier columns
-
SIM_TIERtakes a number, a comma list, orall(0 = Dungeon Crawl, 1-4 = “N Blades”, 5 = the final dungeon). Each job now carries its tier (runner::Job), the report has one block per tier and a win rate by class and tier table, and each ledger row has atierfield. -
The 5 Blades floor has no Exit, Blade or Amulet - the run is won when the demon dies - so
floor_goalpanicked (every tier-5 run “crashed”). It now heads for the final boss; the fight is the real one (resolve_battleruns the real boss AI, Stab included). With the newheroscenario inbalance_tests/test.ron(5x health, 2x damage) the bot reaches and fights the demon. -
Small check (
SIM_SCENARIO=hero SIM_TIER=0,4,5, 1 run per cell): Rogue wins Crawl and 4 Blades and dies to the demon; Mage dies on floor 1 at 4 and 5 Blades even at 5x health - one run each, but worth a look when there is a real sample.
Bots, step 4: the Battle Arena bot
arena_bot now buys each level’s better weapon (it used to skip it once holding any weapon, but buying one discards the old), 3 Healing Potions, then whichever of the class’s abilities the counter stocks, then more Healing; in a wave it casts Ice Armor and the Shoot/Trap/Freeze Trap kit like the dungeon bot (battles already share choose_battle_action, so owned techniques are used). The report honours SIM_RUNS (default 10) and SIM_CLASSES and lists deaths per level: L1 x3 (W1 W3 W3), L2 x1 (boss). Small check (4 runs, Mage and Amazon): Mage 3/4, Amazon 4/4. The Arena itself is untouched - it has no Speed/Defense Potions or quests, by design.
Bots, step 5: two players (SIM_SKILL)
SIM_SKILL=informed (default) is what the bots have played so far: techniques kept for tough fights, Defense/Speed Potions bought and drunk before a fight, Arena abilities bought and cast. SIM_SKILL=average plays like a first-time player: a technique is cast as soon as it is owned, only Healing Potions are bought, no pre-fight buffs or kit. Both take quests and use Healing Potions the same way. Small check (4 runs each): Rogue 4/4 either way; Barbarian 2/4 informed, 1/4 average. Every SIM_* setting is written to the results ledger, so the two runs are comparable with balance_tests/compare.py.
Not done yet: the cat quest, mid-fight use of Speed/Defense Potions, and real sample sizes. The commands: the small run above, or e.g. SIM_TIER=all SIM_RUNS=20 SIM_SKILL=informed cargo test --release ... (uses every core unless SIM_PROCS is set).
Refactor: start_battle (branch refactor-battle-start, from master, not merged yet)
player_input.rs had two copies of the “start a battle” code (Space next to an enemy, and walking into one), each with the Stealth / Invisible / plain branches. Both now call one start_battle helper (roster names, ambush for Stealth, full ATB gauge for the Invisible Cloak, using either up); the file is about 35 lines shorter net. New permanent test start_battle_tests::a_battle_opens_by_how_the_player_is_hiding runs the real input system for all three openings. cargo test (31) and cargo build are clean. The branch holds code only (these notes live here, on bots-full-game, so the two branches’ journals cannot conflict). Backlog item 15 updated; the rest of that list is untouched.
Bots: first real numbers (informed player, all tiers, 5 runs per cell, 210 runs, 42 minutes on one core)
Win rate by class and tier (Mage and Rogue include their +sneak variants, so they have 10 runs per cell; the 5 Blades column is understated, see the fix below):
| class | Crawl | 1 Blade | 2 Blades | 3 Blades | 4 Blades | 5 Blades |
|---|---|---|---|---|---|---|
| Barbarian | 20% | 0% | 20% | 40% | 20% | 0%* |
| Mage | 60% | 30% | 10% | 40% | 30% | 0%* |
| Rogue | 100% | 50% | 60% | 90% | 60% | 40%* |
| Amazon | 40% | 40% | 0% | 0% | 0% | 0%* |
| Hunter | 100% | 60% | 40% | 60% | 60% | 0%* |
Overall 39% (82 of 210); by class Rogue 67%, Hunter 53%, Mage 28%, Barbarian 17%, Amazon 13%. Five runs a cell is noisy (one run is 20 points), so read the pattern, not single cells:
- The floor-1 problem. Most Barbarian and Amazon losses are on the first floor (Amazon: 5 of 5 at 2, 3 and 4 Blades). Those runs reach the town little or never: from the run log, Barbarian and Amazon average 0.4 quests and weapon tier 0.4 per run, Rogue 1.5 and 1.5, Hunter 1.2 and 1.3. They die with no Healing, Defense or Speed Potions left, so the potions are being used, not saved.
- Mage dies mostly on floor 1-2 as well; 4 of 42 Mage deaths had an unused Defense Potion. Stealth variants are not clearly better (Crawl 4/5 vs 2/5, but 1 Blade 0/5 vs 3/5): noise at this size.
-
The 5 Blades column was wrong.
floor_goalpanicked once the demon was dead (no goal left, the game only declares the win at the end of the next turn), so winning runs counted as “crashed” (Rogue 2, Hunter 3, Barbarian 1). Fixed: the bot now waits a turn. Recheck: Hunter at 5 Blades, 6 runs: 2 wins (was 0). So the demon fight is beatable. -
Not yet run: the average-player policy, the Battle Arena, or any cell above 5 runs. The raw log of this run is in
target/sim_log.txt(copied to the session scratchpad), and the ledger line is inbalance_tests/results.jsonl.
Also merged into this branch’s notes: the refactor branches (refactor-battle-start, then refactor-oversized-functions on top of it, both code only, not merged): draw_battle_hud (520 lines) is now 11 lines calling six drawing methods; battle_tick lost the Actions box and cursor logic (draw_actions_box, update_menu_cursor), going from ~490 to ~200 lines; the HUD system went from 708 to 452 lines (a shared draw_bar_slots for the three bars, plus the shop tooltip, talk box, buff badges, hover tooltip and health bar). Each step was checked with a sorted-line diff (only additions) and cargo test/cargo build.
Refactors finished and merged into master
The rest of the agreed list is done and both refactor branches were fast-forward merged into master (then master merged into bots-full-game, with no conflicts): draw_class_portrait and draw_bar_slots out of the HUD system, handle_battle_turn out of battle_tick (85 lines now), the town methods to town_state.rs and the console constants to consoles.rs (main.rs 2,428 to 1,503 lines), ProvidesEffect::defense_bonus() / map_defense_bonus() for the Ice Armor / Defense Potion pairs, and assets::SPRITE_SHEETS as the one sprite-sheet table. What is left is in backlog item 15: the bot loop split (after the bots merge), the rest of the big files, the comment trim, and the docs (CLAUDE.md, the Done section).
Refactor while the bots ran: the bot loop (branch refactor-bot-loop, on bots-full-game)
simulate_one_run (~400 lines) is a short driver again: the AwaitingInput decision (heal, Ice Armor, the town, the map, the kit, choosing a target, the step) moved to awaiting_input_step, the loop’s memory to a BotRun struct, the explorer settings to ExploreCfg; the Ice Armor recast and the out-of-combat kit (Shoot, Freeze Trap, Trap) that both bots had their own copy of are recast_ice_armor and use_kit_items in dungeon_bot.rs. One behaviour change: the dungeon bot’s average player now skips the kit too, matching what the notes always said. Checked with small debug-profile runs (Rogue and Barbarian 4/4, Hunter and Amazon 3/4, Arena Mage 2/2), which do not touch the release test binary the big run was using.
Bots: 15-run sample at Dungeon Crawl and 1 Blade (informed player, 150 runs, 31 minutes, one core)
| class | Dungeon Crawl | 1 Blade |
|---|---|---|
| Barbarian | 40% (6/15) | 13% (2/15) |
| Mage | 47% (7/15) | 33% (5/15) |
| Rogue | 100% (15/15) | 80% (12/15) |
| Amazon | 53% (8/15) | 47% (7/15) |
| Hunter | 93% (14/15) | 33% (5/15) |
Overall 54%. At 15 runs a cell one run is 7 points, so these separate the classes, unlike the 5-run table. Where they lose: at Crawl, Barbarian 8 of its 9 losses are on floor 1, Mage 6 of 8, Amazon 6 of 7 (Rogue and Hunter almost never lose); at 1 Blade, 13 of Barbarian’s 13 losses and 10 of Hunter’s 10 are on floor 1. Quests and weapon tier handed in per run: Barbarian 0.6/0.7, Mage 0.8/0.8, Amazon 1.0/1.0, Hunter 1.2/1.3, Rogue 1.7/1.7 - the weak classes die before the town can help. The last full balance pass (2026-09-20, tiers.ron header) had Crawl at about 81% with every class in one band; that was before the bots used the real ATB gauge, quests and potions, so those numbers described a different game than the one the bot plays now.
Docs and comments while the bots ran
CLAUDE.md went from 391 to 297 lines (merged into master from a separate refactor-docs branch): the nine longest gotcha write-ups moved word for word into docs/DEVLOG.md (“Standing gotchas (full write-ups)”), each replaced by a one or two line rule; two new rules were added (release builds embed resources/; how to run the bots without freezing the computer). In consoles.rs the three longest history-style comments were shortened to what a reader needs (code lines unchanged, checked by diffing with comments removed); the originals are in git history. Noticed but not fixed: several console doc comments say “Console N” one lower than their constant (e.g. UI_PANEL_CONSOLE = 47 under “Console 46”), left over from an earlier insertion.
Refactor: the Done section moves out of ideas.md
docs/ideas.md is 264 lines now (the open backlog only, 1,690 before); everything shipped is in the new docs/done.md (1,433 lines, same text). CLAUDE.md‘s rule and the cross-references in ideas.md point at docs/done.md.
Refactors: the big files split (branch refactor-file-splits)
render_helpers.rs, components/animation.rs, screens/battle.rs, map_builder/mod.rs and systems/hud.rs are folders of focused files now, and main()‘s builder chain is window.rs (main.rs 2,428 lines at the start of the day, 1,170 now). Every split re-exports through mod.rs so no caller changed; each was checked by comparing the sorted lines of the original with the new files (only module headers and pub(super) visibility differ), cargo test, cargo build and a launch. Left open in backlog item 15: the next-largest files and the comment trim.
- Never-seen map tiles a little lighter (user, from a screenshot): the tint went from (50,50,50) to (66,66,66), and the legacy wall factor from 0.22 to 0.28; remembered tiles stay at 85 (DARK_GRAY) / 0.35.
Bots: the average player, and what actually kills the bot (15 runs per cell, Crawl and 1 Blade)
| class | Crawl informed | Crawl average | 1 Blade informed | 1 Blade average |
|---|---|---|---|---|
| Barbarian | 40% | 27% | 13% | 13% |
| Mage | 47% | 20% | 33% | 27% |
| Rogue | 100% | 87% | 80% | 53% |
| Amazon | 53% | 13% | 47% | 33% |
| Hunter | 93% | 67% | 33% | 20% |
Overall 54% informed, 36% average. The gap (buying Defense/Speed Potions and drinking them before fights, saving techniques for tough fights, casting the class kit) is largest for Amazon (53% to 13% at Crawl) and Mage (47% to 20%).
New last fight log (in each END died line): the deaths are attrition, not a hard fight. Across the 96 deaths of this run the last fight is an ordinary Orc or Goblin (Orc 32, Goblin 20, Goblin Chieftain 12, the rest a handful each), it is fight number 27 to 44 of the run, and the bot walks into it at 7 to 14% of its health on average. Most are on floor 1 (the log’s “floor 0”): 18 of Barbarian’s 24 deaths, 18 of Mage’s 23, 20 of Amazon’s 23, 15 of Hunter’s 17, 6 of Rogue’s 9. So the classes that lose do so by wearing down over dozens of fights on the first floor with no way to heal but potions, and then dying to a Goblin. Open question for the design: does the real game give a player a way to recover (rest, retreat), and does a real player take that many fights on floor 1? The bot bumps into every enemy it meets.
Bots: the kit-tests scenario (branch stats-x7)
The x7 numbers are baked into classes.ron / template.ron (every class and template number at all six tiers is identical to before - checked by dumping both), stat_scale is 1.0, and the Battle Arena reads the same numbers. New scenario kit-tests (in the new units; scenarios can now add starting-kit items via kit_extra): Barbarian +1 Defense Potion, Mage +1 Healing Potion, Rogue speed 9, Amazon defense 4. Each change touches one class, so one scenario tests all four. Informed player, 15 runs per cell (about 12 points of noise), against the earlier informed run at the same numbers:
| class (change) | Crawl before, after | 1 Blade before, after |
|---|---|---|
| Barbarian (+1 Defense Potion) | 40%, 67% | 13%, 40% |
| Mage (+1 Healing Potion) | 47%, 73% | 33%, 27% |
| Rogue (speed 10 to 9) | 100%, 100% | 80%, 73% |
| Amazon (defense 0 to 4) | 53%, 93% | 47%, 100% |
Reading it: Amazon’s 4 defense is a very large effect (probably too much: 97% overall), the Barbarian’s Defense Potion is a big one, the Mage’s extra Healing Potion helps at Crawl but not at 1 Blade, and Rogue’s speed cut barely registers (he was already at the top). Deaths are still the same pattern: attrition to Goblins, Orcs and Chieftains on floor 1 at 13 to 15% health. The user’s own playtest (2026-09-21): a Barbarian played with good potion use, buying a Defense and a Speed Potion, and avoiding battles, got through the second level - the bot fights every enemy it meets, so real players do better than the bots’ Barbarian numbers.
Comment trim (merged into master)
The dated history in the comments (“direct feedback 2026-09-14…”, “confirmed live…”, “was N, then M”) is being condensed to the reason that is still true, since the journal already has the story. 146 comment blocks (about 2,100 lines) carry that history; five files are done so far - render_helpers/panels.rs (671 to 465 lines), render_helpers/bars.rs, systems/hud/layout.rs (411 to 296), screens/battle/hud.rs (557 to 426) and screens/battle/mod.rs - each checked by diffing the file with its comments removed before and after (identical). A misplaced comment was fixed on the way: PanelBox‘s own doc sat above the inset constants. Still to do: components/animation/scenes.rs, map_builder/theme.rs, systems/hud/mod.rs, screens/end.rs, options.rs, consoles.rs and the rest.
The comment purge and the docs purge
Comments: all 146 history-style blocks (“direct feedback 2026-09-14…”, “confirmed live…”) are condensed to the reason that is still true (about 1,500 lines gone), file by file on master, each checked by diffing the file with its comments removed (identical). A few misplaced docs were put back on their functions and two console labels that disagreed with their constants were fixed. Comments are still about 32% of the lines - the rest explain non-obvious things (shader and console quirks, pixel math) and stay.
Docs: journal.md, ideas.md and done.md untouched. DEVLOG.md went from 1,478 to 437 lines (it keeps the “Known Environment Quirks” and the “Standing gotchas” that CLAUDE.md points to, plus a short “what is where”); its per-session narratives, old “Current state”, “Backlog” and “Working conventions” moved to docs/archive/DEVLOG_sessions_2026-09.md. The finished design plans (quests_town_redesign.md, phase5_plan.md) and the two per-push release_notes_* files moved to docs/archive/ (code comments that cite them were re-pointed). README.md got a current “Current state” and a project structure that matches the code after the splits; CHANGELOG.md got the day’s player-facing changes (buff icons and tooltip, heal preview, stats buffs, saved buff time, map-only tiles, the Invisible Cloak, the Defense Potion stacking, deaths on the History screen, named bleed ticks, full stat numbers). CLAUDE.md got the new doc pointers and a refactor-workflow rule. Working docs went from 13,286 to 11,221 lines with nothing deleted (the archive holds another ~2,100).
Bots: S0, the baseline at all six tiers (informed player, real numbers, 40 runs per cell, 1,200 runs)
Two processes pinned to two cores (taskset -c 22,23, nice -n 10): 765 seconds, about 0.6 s per run (the earlier “12 s a run” figures were inflated by other work competing for the CPU).
| class | Crawl | 1 Blade | 2 Blades | 3 Blades | 4 Blades | 5 Blades (demon) |
|---|---|---|---|---|---|---|
| Barbarian | 35% | 25% | 40% | 25% | 32% | 0% |
| Mage | 58% | 28% | 38% | 35% | 32% | 15% |
| Rogue | 100% | 55% | 72% | 68% | 68% | 55% |
| Amazon | 52% | 15% | 38% | 35% | 28% | 0% |
| Hunter | 85% | 42% | 50% | 52% | 50% | 12% |
Overall 41% (496 of 1,200); by class Rogue 70%, Hunter 49%, Mage 34%, Amazon 28%, Barbarian 26%. At 40 runs a cell one run is 2.5 points. The demon is the wall for everyone but Rogue (Barbarian and Amazon never beat it in 40 tries each). S1 to S3 (the values to test) wait for the user’s read of this baseline.
Bots: S1-S5, chasing a target curve (80/70/60/55/45/40%)
User’s target shape after seeing S0: roughly 80/70/60/55/45/40% across Crawl through 5 Blades, with the explicit caveat that a real player should be able to finish even a tier where the bot only wins 40% of the time - the bot is a lower bound, not the target itself.
S1/S2 (one lever per class, 40 runs/cell): tried Amazon defense 2 vs 3, Mage a Defense Potion vs +7 health, Barbarian a Speed Potion vs +2 speed, Rogue speed 8, Goblin damage 6. Amazon defense 3 came back clearly overtuned (80% overall, climbing instead of declining with tier - the only non-declining class/scenario in the batch). Neither Mage variant moved its number. Barbarian’s Speed Potion barely helped (32%) vs. permanent +2 speed (51%).
Real finding, not noise: instrumented recast_ice_armor and grant_random_battle_loot directly (temporary debug logging, reverted after) to check whether the Mage was actually getting extra Ice Armor copies via loot, since the win-rate numbers felt off against the user’s own play experience. Confirmed the loot mechanic works exactly as designed - Ice Armor drops at the expected ~8% per kill (1/5 of the 40% loot chance, split evenly with Fireball/Burn/Blizzard/Invisible Cloak) and the bot recasts it correctly. Not a bot bug; the class numbers were the real signal.
Tier curve was structurally backwards: tiers.ron kept gold flat (1.0x) through both Crawl and 1 Blade while enemy health already jumped to 1.15x at 1 Blade, and the “5 Blades (Demon)” dungeon had no tier entry of its own - it silently reused 4 Blades’ numbers (a comment in the file said so). Every class in every dataset (S0 included) dipped at 1 Blade then partly recovered at 2 Blades - backwards for a difficulty curve, and the reason S1/S2’s per-tier numbers looked “all over the place” rather than a smooth decline. Gave 5 Blades its own TierEntry (entry_for already clamps to the last entry, so this needed no code change) and started smoothing the early gold/health ramp - fourth sweep, still in tiers.ron‘s own header comment.
S3 (tiers.ron smoothed, class levers reapplied, 80 runs/cell): tight class spread in the dungeon (Barbarian 51%, Mage 39%, Rogue 52%, Amazon 59%, Hunter 58%) vs. S0’s wild 22-80%. 1 Blade still the worst-missed tier (45% vs 70% target). Arena: Mage 0/80, dying almost entirely to Level 1 bosses even after affordable Ice Armor purchases - genuine balance problem, not the stale “bot never buys abilities” doc comment on try_buy_item (checked shop_step, the actual entry point - it does buy weapon, then potions, then abilities off the counter).
S4 (Mage defense -7 -> -3): overshot badly - Mage jumped to 89% overall in the dungeon, instantly the strongest class, while the Arena barely moved (0% -> 7.5%, still nearly all L1 boss deaths). Defense subtracts flat from every hit before any other reduction, so it’s a much more sensitive lever than health or evasion.
S5 (Mage defense -7 -> -5, everything else same as S4, 80 runs/cell): dungeon landed in a real band - Barbarian 46%, Mage 50%, Rogue 53%, Amazon 62%, Hunter 52%. Per-tier average vs. target: Crawl 86% (target 80), 1 Blade 53% (70), 2 Blades 46% (60), 3 Blades 50% (55), 4 Blades 51% (45, now over), 5 Blades 29% (40). 1-2 Blades and 5 Blades still the gap; 3-4 Blades are close or over. Arena Mage still stuck at 1/80 - the defense lever helps the dungeon (where Ice Armor loot stacks over a run) far more than the Arena (zero starting kit, one boss fight in very quickly). Arena may need its own fix rather than riding the dungeon number - flagged, not yet acted on.
Process note: lost real time to a background-job mistake - manually wrapping a long run in nohup ... & disown (rather than just using the harness’s own run_in_background on the actual command) caused two full test runs to silently stack on the same two cores more than once, corrupting one run’s output entirely. Fixed by killing everything, confirming a clean process list, and switching to launching the actual long-running script directly as the backgrounded command with no manual &/nohup wrapping - reliable every time after that.
User authorized more CPU overnight (16-20 cores instead of 2, higher runs/cell) since they won’t be at the keyboard - S6 onward uses that.