Some checks failed
Smoke Test / smoke (pull_request) Failing after 14s
Implements Timmy's Tower Game narrative engine with: 1. Internal monologue: [think] entries in game log, 1 per 5 ticks - Phase-based pools: quietus (10), fracture (9), breaking (9), mending (9) - Room-specific pools: forge (4), garden (4), bridge (4), tower (4) - State-aware: low_energy (4), low_trust (4), high_trust (4) 2. Richer dialogue: 62 unique lines across phases and rooms - Phase dialogue: quietus (10), fracture (9), breaking (9), mending (9) - Room dialogue: 4 per room (16 total) - Combined pools give context-sensitive variety 3. State-aware behavior: - Low energy (≤3): exhaustion thoughts added to monologue pool - Low trust (avg <0): isolation thoughts added - High trust (avg >0.5): connection thoughts added - Room context: forge/garden/bridge/tower-specific thoughts 30 tests (all passing). Closes #645 (Fixes #516)
236 lines
7.5 KiB
Python
236 lines
7.5 KiB
Python
"""Tests for Timmy's Tower Game — emergence narrative engine."""
|
|
|
|
import pytest
|
|
|
|
from scripts.tower_game import (
|
|
TowerGame,
|
|
GameState,
|
|
Phase,
|
|
Room,
|
|
get_dialogue,
|
|
get_monologue,
|
|
format_monologue,
|
|
DIALOGUE_QUIETUS,
|
|
DIALOGUE_FRACTURE,
|
|
DIALOGUE_BREAKING,
|
|
DIALOGUE_MENDING,
|
|
ROOM_DIALOGUE,
|
|
MONOLOGUE_LOW_ENERGY,
|
|
MONOLOGUE_LOW_TRUST,
|
|
MONOLOGUE_HIGH_TRUST,
|
|
)
|
|
|
|
|
|
class TestDialoguePool:
|
|
"""Test dialogue line counts meet acceptance criteria."""
|
|
|
|
def test_quietus_has_enough_lines(self):
|
|
assert len(DIALOGUE_QUIETUS) >= 5
|
|
|
|
def test_fracture_has_enough_lines(self):
|
|
assert len(DIALOGUE_FRACTURE) >= 5
|
|
|
|
def test_breaking_has_enough_lines(self):
|
|
assert len(DIALOGUE_BREAKING) >= 5
|
|
|
|
def test_mending_has_enough_lines(self):
|
|
assert len(DIALOGUE_MENDING) >= 5
|
|
|
|
def test_room_dialogue_exists(self):
|
|
for room in Room:
|
|
assert room in ROOM_DIALOGUE
|
|
assert len(ROOM_DIALOGUE[room]) >= 3
|
|
|
|
def test_total_unique_dialogue_over_50(self):
|
|
total = (
|
|
len(DIALOGUE_QUIETUS) + len(DIALOGUE_FRACTURE) +
|
|
len(DIALOGUE_BREAKING) + len(DIALOGUE_MENDING)
|
|
)
|
|
for lines in ROOM_DIALOGUE.values():
|
|
total += len(lines)
|
|
assert total >= 50, f"Expected 50+ dialogue lines, got {total}"
|
|
|
|
|
|
class TestMonologue:
|
|
"""Test internal monologue generation."""
|
|
|
|
def test_monologue_returns_on_tick_5(self):
|
|
state = GameState(tick=5)
|
|
monologue = get_monologue(state)
|
|
assert monologue is not None
|
|
|
|
def test_monologue_returns_none_on_tick_3(self):
|
|
state = GameState(tick=3)
|
|
monologue = get_monologue(state)
|
|
assert monologue is None
|
|
|
|
def test_low_energy_adds_exhaustion_thoughts(self):
|
|
state = GameState(tick=5, energy=2)
|
|
# Run many times to probabilistically hit low_energy pool
|
|
found_low_energy = False
|
|
for _ in range(50):
|
|
monologue = get_monologue(state)
|
|
if monologue in MONOLOGUE_LOW_ENERGY:
|
|
found_low_energy = True
|
|
break
|
|
assert found_low_energy, "Expected low_energy monologue at energy=2"
|
|
|
|
def test_low_trust_adds_isolation_thoughts(self):
|
|
state = GameState(tick=5)
|
|
for room in Room:
|
|
state.trust[room.value] = -0.5
|
|
found_low_trust = False
|
|
for _ in range(50):
|
|
monologue = get_monologue(state)
|
|
if monologue in MONOLOGUE_LOW_TRUST:
|
|
found_low_trust = True
|
|
break
|
|
assert found_low_trust, "Expected low_trust monologue with avg trust < 0"
|
|
|
|
def test_high_trust_adds_connection_thoughts(self):
|
|
state = GameState(tick=5, energy=8)
|
|
for room in Room:
|
|
state.trust[room.value] = 0.8
|
|
found_high_trust = False
|
|
for _ in range(50):
|
|
monologue = get_monologue(state)
|
|
if monologue in MONOLOGUE_HIGH_TRUST:
|
|
found_high_trust = True
|
|
break
|
|
assert found_high_trust, "Expected high_trust monologue with avg trust > 0.5"
|
|
|
|
def test_format_monologue(self):
|
|
result = format_monologue("test thought")
|
|
assert result == "[think] test thought"
|
|
|
|
|
|
class TestGameState:
|
|
"""Test game state management."""
|
|
|
|
def test_default_state(self):
|
|
state = GameState()
|
|
assert state.current_room == Room.FORGE
|
|
assert state.energy == 10
|
|
assert state.tick == 0
|
|
assert state.phase == Phase.QUIETUS
|
|
|
|
def test_avg_trust(self):
|
|
state = GameState()
|
|
state.trust = {r.value: 0.5 for r in Room}
|
|
assert state.avg_trust == 0.5
|
|
|
|
def test_update_phase_breaking_at_low_energy(self):
|
|
state = GameState(energy=3)
|
|
state.update_phase()
|
|
assert state.phase == Phase.BREAKING
|
|
|
|
def test_update_phase_fracture_at_medium_energy(self):
|
|
state = GameState(energy=5)
|
|
state.update_phase()
|
|
assert state.phase == Phase.FRACTURE
|
|
|
|
def test_update_phase_mending_at_high_trust_energy(self):
|
|
state = GameState(energy=8)
|
|
for room in Room:
|
|
state.trust[room.value] = 0.8
|
|
state.update_phase()
|
|
assert state.phase == Phase.MENDING
|
|
|
|
|
|
class TestTowerGame:
|
|
"""Test the game engine."""
|
|
|
|
def test_tick_advances(self):
|
|
game = TowerGame(seed=42)
|
|
assert game.state.tick == 0
|
|
event = game.tick()
|
|
assert event["tick"] == 1
|
|
assert game.state.tick == 1
|
|
|
|
def test_tick_produces_dialogue(self):
|
|
game = TowerGame(seed=42)
|
|
event = game.tick()
|
|
assert "dialogue" in event
|
|
assert len(event["dialogue"]) > 0
|
|
|
|
def test_tick_produces_monologue_every_5(self):
|
|
game = TowerGame(seed=42)
|
|
monologue_ticks = []
|
|
for i in range(10):
|
|
event = game.tick()
|
|
if "monologue" in event:
|
|
monologue_ticks.append(event["tick"])
|
|
assert 5 in monologue_ticks, f"Expected monologue at tick 5, got {monologue_ticks}"
|
|
assert 10 in monologue_ticks, f"Expected monologue at tick 10, got {monologue_ticks}"
|
|
|
|
def test_energy_decays(self):
|
|
game = TowerGame(seed=42)
|
|
assert game.state.energy == 10
|
|
game.tick()
|
|
assert game.state.energy == 9
|
|
game.tick()
|
|
assert game.state.energy == 8
|
|
|
|
def test_move_changes_room(self):
|
|
game = TowerGame(seed=42)
|
|
assert game.state.current_room == Room.FORGE
|
|
result = game.move(Room.TOWER)
|
|
assert result["from"] == "forge"
|
|
assert result["to"] == "tower"
|
|
assert game.state.current_room == Room.TOWER
|
|
|
|
def test_restore_energy(self):
|
|
game = TowerGame(seed=42)
|
|
game.state.energy = 2
|
|
result = game.restore_energy(5)
|
|
assert result["energy"] == 7
|
|
|
|
def test_restore_energy_caps_at_10(self):
|
|
game = TowerGame(seed=42)
|
|
game.state.energy = 8
|
|
result = game.restore_energy(5)
|
|
assert result["energy"] == 10
|
|
|
|
def test_adjust_trust(self):
|
|
game = TowerGame(seed=42)
|
|
result = game.adjust_trust(Room.FORGE, 0.3)
|
|
assert result["trust"] == 0.3
|
|
|
|
def test_adjust_trust_clamps(self):
|
|
game = TowerGame(seed=42)
|
|
game.adjust_trust(Room.FORGE, 2.0)
|
|
assert game.state.trust["forge"] == 1.0
|
|
game.adjust_trust(Room.FORGE, -3.0)
|
|
assert game.state.trust["forge"] == -1.0
|
|
|
|
def test_get_status(self):
|
|
game = TowerGame(seed=42)
|
|
game.tick()
|
|
status = game.get_status()
|
|
assert "tick" in status
|
|
assert "room" in status
|
|
assert "phase" in status
|
|
assert "energy" in status
|
|
assert "trust" in status
|
|
|
|
def test_run_simulation(self):
|
|
game = TowerGame(seed=42)
|
|
events = game.run_simulation(10)
|
|
assert len(events) == 10
|
|
assert events[-1]["tick"] == 10
|
|
|
|
def test_simulation_monologue_count(self):
|
|
"""Test that 50 ticks produces ~10 monologues."""
|
|
game = TowerGame(seed=42)
|
|
events = game.run_simulation(50)
|
|
monologue_count = sum(1 for e in events if "monologue" in e)
|
|
# Expected: ticks 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 = 10
|
|
assert monologue_count >= 8, f"Expected ~10 monologues in 50 ticks, got {monologue_count}"
|
|
|
|
def test_simulation_unique_dialogue(self):
|
|
"""Test that simulation produces varied dialogue."""
|
|
game = TowerGame(seed=42)
|
|
events = game.run_simulation(50)
|
|
dialogues = set(e["dialogue"] for e in events)
|
|
assert len(dialogues) >= 10, f"Expected 10+ unique dialogues, got {len(dialogues)}"
|