mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(understone): rebalance early game and add inn turn refresh
The opening hours were a gold/HP death spiral: a fresh hero spent more gold healing a fight than the kill paid, mid-tier foes out-damaged a starting HP bar, the map gave no read on where harder foes spawned, and a spent day left the player idle until the UTC rollover. This eases the on-ramp across both shipped worlds. Economy - Healer drops from 2 to 1 gold per HP, so topping up no longer outruns income. - Starting purse 20 -> 37: enough to buy the cheapest armor and one potion up front, a one-point DEF bump plus a heal cushion the player chooses to spend. Combat - Tier 2-4 common foes lose 1 ATK each, trimming the burst that could halve or end a fresh hero in a single bout. Rares and the boss are untouched. Wayfinding - The road glyph changes from "=" to a shaded path that reads as one continuous road in every orientation; "=" only looked right horizontally and broke into stacked dashes on vertical runs. Cinder's basalt path gets the same treatment with a crosshatch glyph free in its palette. Rest - Sleeping at the inn now rolls a spent adventurer into a fresh day's turns (it already fully heals). The top-up fires only at zero turns, so it never banks past the daily cap. Verified: full suite green (+2 rest tests, ruff/mypy clean); the greedy balance bot still clears the world 11/12 seeds at an unchanged pace on both packs.
This commit is contained in:
@@ -161,6 +161,35 @@ def test_rest_heals_and_charges(tmp_path: Path, clock: object) -> None:
|
||||
assert "full health" in out.lower()
|
||||
|
||||
|
||||
def test_rest_when_spent_restores_a_fresh_days_turns(tmp_path: Path, clock: object) -> None:
|
||||
"""Sleeping at the inn with no turns left rolls into a fresh day's allowance."""
|
||||
game = _game(tmp_path, clock)
|
||||
game.join("Brandr")
|
||||
player = game.players["Brandr"]
|
||||
daily = game.world.settings.daily_turns
|
||||
player.turns_left = 0 # spent for the day
|
||||
player.hp = 5
|
||||
game.move("Brandr", "", "west", 2) # step into the inn
|
||||
out = game.action("Brandr", "rest", "", "")
|
||||
assert player.turns_left == daily # a fresh day's turns restored
|
||||
assert player.hp == player.max_hp # and fully mended
|
||||
assert f"/{daily} ]" in out # footer reflects the refreshed budget
|
||||
|
||||
|
||||
def test_rest_with_turns_in_hand_never_inflates_the_budget(tmp_path: Path, clock: object) -> None:
|
||||
"""Resting mid-day mends but adds no turns — the top-up only fires at zero."""
|
||||
game = _game(tmp_path, clock)
|
||||
game.join("Brandr")
|
||||
player = game.players["Brandr"]
|
||||
daily = game.world.settings.daily_turns
|
||||
player.turns_left = daily - 3 # turns still in hand
|
||||
player.hp = 5
|
||||
game.move("Brandr", "", "west", 2) # step into the inn
|
||||
game.action("Brandr", "rest", "", "")
|
||||
assert player.turns_left == daily - 3 # unchanged: no farming past the cap
|
||||
assert player.hp == player.max_hp # but the heal still lands
|
||||
|
||||
|
||||
def test_fight_spends_a_turn_and_credits(tmp_path: Path, clock: object) -> None:
|
||||
game = _game(tmp_path, clock)
|
||||
game.join("Brandr")
|
||||
|
||||
@@ -1015,16 +1015,28 @@ class Game:
|
||||
return self._overworld_frame(player, lines=["You step back out into the open air."])
|
||||
|
||||
def _rest(self, player: Player) -> str:
|
||||
# Settle any pending day rollover first, so a rest taken as the first act
|
||||
# of a new day is the ordinary refresh, not the spent-turns top-up below.
|
||||
self._ensure_day(player)
|
||||
cost = self.world.settings.rest_cost
|
||||
if leveling.rest(player, cost):
|
||||
# A night's rest is a private errand, not Herald news; persist only.
|
||||
self._persist(player)
|
||||
return self._location_menu(
|
||||
player, lines=[f"You sleep deeply and wake at full health. (-{cost} gold)"]
|
||||
)
|
||||
if not leveling.rest(player, cost):
|
||||
return self._location_menu(
|
||||
player, lines=[f"You can't afford the {cost}-gold bed. (You have {player.gold}.)"]
|
||||
)
|
||||
# A night at the inn always mends. Once the day's turns are spent it also
|
||||
# rolls the sleeper into a fresh day's allowance, so a spent adventurer can
|
||||
# press on rather than idling until the dawn rollover. The top-up only
|
||||
# fires at zero, so it never banks turns past the daily cap.
|
||||
line = f"You sleep deeply and wake at full health. (-{cost} gold)"
|
||||
if player.turns_left <= 0:
|
||||
player.turns_left = self.world.settings.daily_turns
|
||||
line = (
|
||||
f"You sleep through to a new dawn, waking at full health "
|
||||
f"and ready to venture out anew. (-{cost} gold)"
|
||||
)
|
||||
# A night's rest is a private errand, not Herald news; persist only.
|
||||
self._persist(player)
|
||||
return self._location_menu(player, lines=[line])
|
||||
|
||||
# -- the Vault: bank gold at the inn (safe from ambush) --------------
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"tier": 2,
|
||||
"name": "Goblin",
|
||||
"hp": 12,
|
||||
"atk": 5,
|
||||
"atk": 4,
|
||||
"def": 1,
|
||||
"xp": 18,
|
||||
"gold": 7
|
||||
@@ -30,7 +30,7 @@
|
||||
"tier": 2,
|
||||
"name": "Bandit Scout",
|
||||
"hp": 14,
|
||||
"atk": 6,
|
||||
"atk": 5,
|
||||
"def": 1,
|
||||
"xp": 20,
|
||||
"gold": 9
|
||||
@@ -50,7 +50,7 @@
|
||||
"tier": 3,
|
||||
"name": "Forest Wolf",
|
||||
"hp": 20,
|
||||
"atk": 8,
|
||||
"atk": 7,
|
||||
"def": 2,
|
||||
"xp": 35,
|
||||
"gold": 14
|
||||
@@ -59,7 +59,7 @@
|
||||
"tier": 3,
|
||||
"name": "Bog Stalker",
|
||||
"hp": 22,
|
||||
"atk": 9,
|
||||
"atk": 8,
|
||||
"def": 2,
|
||||
"xp": 38,
|
||||
"gold": 16
|
||||
@@ -79,7 +79,7 @@
|
||||
"tier": 4,
|
||||
"name": "Cave Troll",
|
||||
"hp": 38,
|
||||
"atk": 12,
|
||||
"atk": 11,
|
||||
"def": 4,
|
||||
"xp": 70,
|
||||
"gold": 30
|
||||
@@ -88,7 +88,7 @@
|
||||
"tier": 4,
|
||||
"name": "Barrow Wight",
|
||||
"hp": 35,
|
||||
"atk": 13,
|
||||
"atk": 12,
|
||||
"def": 4,
|
||||
"xp": 65,
|
||||
"gold": 28
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"=": {
|
||||
"key": "road",
|
||||
"glyph": "=",
|
||||
"glyph": "▒",
|
||||
"walkable": true,
|
||||
"encounter_rate": 0.02,
|
||||
"color": "road"
|
||||
|
||||
@@ -100,8 +100,8 @@
|
||||
"settings": {
|
||||
"daily_turns": 10,
|
||||
"rest_cost": 15,
|
||||
"heal_cost_per_hp": 2,
|
||||
"starting_gold": 20,
|
||||
"heal_cost_per_hp": 1,
|
||||
"starting_gold": 37,
|
||||
"starting_weapon": "rusty_dagger",
|
||||
"starting_armor": "cloth_tunic",
|
||||
"start_hp": 20,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"tier": 2,
|
||||
"name": "Ember Imp",
|
||||
"hp": 12,
|
||||
"atk": 5,
|
||||
"atk": 4,
|
||||
"def": 1,
|
||||
"xp": 18,
|
||||
"gold": 7
|
||||
@@ -30,7 +30,7 @@
|
||||
"tier": 2,
|
||||
"name": "Slag Scuttler",
|
||||
"hp": 14,
|
||||
"atk": 6,
|
||||
"atk": 5,
|
||||
"def": 1,
|
||||
"xp": 20,
|
||||
"gold": 9
|
||||
@@ -50,7 +50,7 @@
|
||||
"tier": 3,
|
||||
"name": "Magma Hound",
|
||||
"hp": 20,
|
||||
"atk": 8,
|
||||
"atk": 7,
|
||||
"def": 2,
|
||||
"xp": 35,
|
||||
"gold": 14
|
||||
@@ -59,7 +59,7 @@
|
||||
"tier": 3,
|
||||
"name": "Obsidian Lurker",
|
||||
"hp": 22,
|
||||
"atk": 9,
|
||||
"atk": 8,
|
||||
"def": 2,
|
||||
"xp": 38,
|
||||
"gold": 16
|
||||
@@ -79,7 +79,7 @@
|
||||
"tier": 4,
|
||||
"name": "Basalt Golem",
|
||||
"hp": 38,
|
||||
"atk": 12,
|
||||
"atk": 11,
|
||||
"def": 4,
|
||||
"xp": 70,
|
||||
"gold": 30
|
||||
@@ -88,7 +88,7 @@
|
||||
"tier": 4,
|
||||
"name": "Ashen Wraith",
|
||||
"hp": 35,
|
||||
"atk": 13,
|
||||
"atk": 12,
|
||||
"def": 4,
|
||||
"xp": 65,
|
||||
"gold": 28
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"=": {
|
||||
"key": "basalt",
|
||||
"glyph": "=",
|
||||
"glyph": "▩",
|
||||
"walkable": true,
|
||||
"encounter_rate": 0.02,
|
||||
"color": "road"
|
||||
|
||||
@@ -100,8 +100,8 @@
|
||||
"settings": {
|
||||
"daily_turns": 10,
|
||||
"rest_cost": 15,
|
||||
"heal_cost_per_hp": 2,
|
||||
"starting_gold": 20,
|
||||
"heal_cost_per_hp": 1,
|
||||
"starting_gold": 37,
|
||||
"starting_weapon": "charred_shiv",
|
||||
"starting_armor": "scorched_rags",
|
||||
"start_hp": 20,
|
||||
|
||||
Reference in New Issue
Block a user