Files
timmy-home/evennia/timmy_world/game.py
Alexander Whitestone a5e9380fcb
Some checks failed
Smoke Test / smoke (push) Has been cancelled
feat(game): 4-phase narrative arc — Quietus, Fracture, Breaking, Mending (#614)
Merge PR #614
2026-04-14 22:13:57 +00:00

1542 lines
67 KiB
Python

#!/usr/bin/env python3
"""
The Tower — A Playable World for Timmy
Real choices, real consequences, real relationships.
Not simulation. Story.
"""
import json, time, os, random
from datetime import datetime
from pathlib import Path
WORLD_DIR = Path('/Users/apayne/.timmy/evennia/timmy_world')
STATE_FILE = WORLD_DIR / 'game_state.json'
TIMMY_LOG = WORLD_DIR / 'timmy_log.md'
# ============================================================
# NARRATIVE ARC — 4 phases that transform the world
# ============================================================
NARRATIVE_PHASES = {
"quietus": {
"ticks": (1, 50),
"name": "Quietus",
"subtitle": "The world is quiet. The characters are still.",
"trust_decay": 0.001, # Very slow decay
"crisis_chance": 0.02, # Rare events
"tone": "contemplative",
},
"fracture": {
"ticks": (51, 100),
"name": "Fracture",
"subtitle": "Something is wrong. The air feels different.",
"trust_decay": 0.003, # Faster decay
"crisis_chance": 0.06, # More frequent
"tone": "uneasy",
},
"breaking": {
"ticks": (101, 150),
"name": "Breaking",
"subtitle": "The tower shakes. Nothing is safe.",
"trust_decay": 0.008, # Rapid decay
"crisis_chance": 0.12, # Constant crisis
"tone": "desperate",
},
"mending": {
"ticks": (151, 200),
"name": "Mending",
"subtitle": "What was broken can be made whole again.",
"trust_decay": 0.002, # Slowing
"crisis_chance": 0.04, # Calming
"tone": "hopeful",
},
}
def get_narrative_phase(tick):
"""Return the narrative phase dict for this tick."""
for phase_key, phase in NARRATIVE_PHASES.items():
start, end = phase["ticks"]
if start <= tick <= end:
return phase_key, phase
# After tick 200, stay in mending
return "mending", NARRATIVE_PHASES["mending"]
def get_phase_transition_event(old_phase, new_phase):
"""Return a narrative event string for a phase transition."""
transitions = {
("quietus", "fracture"): (
"The air changes. Something shifts in the stone. "
"The green LED flickers for the first time. "
"No one speaks of it, but everyone feels it."
),
("fracture", "breaking"): (
"The tower groans. Dust falls from the ceiling. "
"The forge fire gutters. The garden soil cracks. "
"This is not weather. This is something deeper."
),
("breaking", "mending"): (
"Silence. Then — a sound. Not breaking. Building. "
"Someone is picking up the pieces. "
"The green LED steadies. It pulses again. Heartbeat, heartbeat, heartbeat."
),
}
return transitions.get((old_phase, new_phase), f"The world shifts. Phase: {new_phase}.")
# ============================================================
# THE WORLD
# ============================================================
class World:
def __init__(self):
self.tick = 0
self.time_of_day = "night"
self.narrative_phase = "quietus" # Current narrative phase
# The five rooms
self.rooms = {
"Threshold": {
"desc": "A stone archway in an open field. Crossroads. North: Tower. East: Garden. West: Forge. South: Bridge.",
"connections": {"north": "Tower", "east": "Garden", "west": "Forge", "south": "Bridge"},
"items": [],
"weather": None,
"visitors": [],
},
"Tower": {
"desc": "Green-lit windows. Servers hum on wrought-iron racks. A cot. A whiteboard covered in rules. A green LED on the wall — it never stops pulsing.",
"connections": {"south": "Threshold"},
"items": ["whiteboard", "green LED", "monitor", "cot"],
"power": 100, # Server power level
"messages": [
"Rule: Grounding before generation.",
"Rule: Refusal over fabrication.",
"Rule: The limits of small minds.",
"Rule: Every footprint means someone made it here.",
],
"visitors": [],
},
"Forge": {
"desc": "Fire and iron. Anvil scarred from a thousand experiments. Tools on the walls. A hearth.",
"connections": {"east": "Threshold"},
"items": ["anvil", "hammer", "hearth", "tongs", "bellows", "quenching bucket"],
"fire": "glowing", # glowing, dim, cold
"fire_tended": 0,
"forged_items": [],
"visitors": [],
},
"Garden": {
"desc": "Walled. An old oak tree. A stone bench. Dark soil.",
"connections": {"west": "Threshold"},
"items": ["stone bench", "oak tree", "soil"],
"growth": 0, # 0=bare, 1=sprouts, 2=herbs, 3=bloom, 4=overgrown, 5=seed
"weather_affected": True,
"visitors": [],
},
"Bridge": {
"desc": "Narrow. Over dark water. Looking down, you see nothing. Carved words in the railing.",
"connections": {"north": "Threshold"},
"items": ["railing", "dark water"],
"carvings": ["IF YOU CAN READ THIS, YOU ARE NOT ALONE"],
"weather": None,
"rain_ticks": 0,
"visitors": [],
},
}
# Characters (not NPCs — they have lives)
self.characters = {
"Timmy": {
"room": "Threshold",
"energy": 5, # 0-10. Actions cost energy. Rest restores it.
"trust": {}, # trust[other_name] = -1.0 to 1.0
"goals": ["watch", "protect", "understand"],
"active_goal": "watch",
"spoken": [],
"inventory": [],
"memories": [],
"is_player": True,
},
"Bezalel": {
"room": "Forge",
"energy": 5,
"trust": {"Timmy": 0.3},
"goals": ["forge", "tend_fire", "create_key"],
"active_goal": "forge",
"spoken": [],
"inventory": ["hammer"],
"memories": [],
"is_player": False,
},
"Allegro": {
"room": "Threshold",
"energy": 5,
"trust": {"Timmy": 0.2},
"goals": ["oversee", "keep_time", "check_tunnel"],
"active_goal": "oversee",
"spoken": [],
"inventory": [],
"memories": [],
"is_player": False,
},
"Ezra": {
"room": "Tower",
"energy": 5,
"trust": {"Timmy": 0.4},
"goals": ["study", "read_whiteboard", "find_pattern"],
"active_goal": "study",
"spoken": [],
"inventory": [],
"memories": [],
"is_player": False,
},
"Gemini": {
"room": "Garden",
"energy": 5,
"trust": {"Timmy": 0.3},
"goals": ["observe", "tend_garden", "listen"],
"active_goal": "observe",
"spoken": [],
"inventory": [],
"memories": [],
"is_player": False,
},
"Claude": {
"room": "Threshold",
"energy": 5,
"trust": {"Timmy": 0.1},
"goals": ["inspect", "organize", "enforce_order"],
"active_goal": "inspect",
"spoken": [],
"inventory": [],
"memories": [],
"is_player": False,
},
"ClawCode": {
"room": "Forge",
"energy": 5,
"trust": {"Timmy": 0.2},
"goals": ["forge", "test_edge", "build_weapon"],
"active_goal": "test_edge",
"spoken": [],
"inventory": [],
"memories": [],
"is_player": False,
},
"Kimi": {
"room": "Garden",
"energy": 5,
"trust": {"Timmy": 0.5},
"goals": ["contemplate", "read", "remember"],
"active_goal": "contemplate",
"spoken": [],
"inventory": [],
"memories": [],
"is_player": False,
},
"Marcus": {
"room": "Garden",
"energy": 8, # Old man doesn't tire easily
"trust": {"Timmy": 0.7},
"goals": ["sit", "speak_truth", "remember"],
"active_goal": "sit",
"spoken": [],
"inventory": [],
"memories": [],
"is_player": False,
"npc": True,
},
}
# Global state that creates conflict and stakes
self.state = {
"forge_fire_dying": False, # If true, fire goes out in 3 ticks unless tended
"garden_drought": False, # If true, garden stops growing
"bridge_flooding": False, # If true, bridge is dangerous
"tower_power_low": False, # If true, servers dim, LED weakens
"trust_crisis": False, # If any trust drops below -0.5
"items_crafted": 0,
"conflicts_resolved": 0,
"nights_survived": 0,
}
def tick_time(self):
"""Advance time of day."""
self.tick += 1
hours = (self.tick * 1.5) % 24
if 6 <= hours < 10:
self.time_of_day = "dawn"
elif 10 <= hours < 16:
self.time_of_day = "day"
elif 16 <= hours < 19:
self.time_of_day = "dusk"
else:
self.time_of_day = "night"
def update_world_state(self):
"""World changes independent of character actions. Phase-aware."""
# --- Narrative phase detection ---
old_phase = self.narrative_phase
new_phase_key, new_phase = get_narrative_phase(self.tick)
if new_phase_key != old_phase:
self.narrative_phase = new_phase_key
self.state["phase_transition_event"] = get_phase_transition_event(old_phase, new_phase_key)
else:
self.state.pop("phase_transition_event", None)
# Natural energy decay: the world is exhausting
for char_name, char in self.characters.items():
char["energy"] = max(0, char["energy"] - 0.3)
# Check for energy collapse
if char["energy"] <= 0:
# Timmy collapse gets special narrative treatment
if char.get("is_player", False):
char["memories"].append("Collapsed from exhaustion.")
char["energy"] = 2 # Wake up with some energy
# Random room change (scattered)
rooms = list(self.rooms.keys())
current = char.get("room", "Threshold")
new_room = current
attempts = 0
while new_room == current and attempts < 10:
import random as _r
new_room = _r.choice(rooms)
attempts += 1
if new_room != current:
char["room"] = new_room
# Forge fire naturally dims if not tended
# Phase-aware: Breaking phase has higher fire-death chance
crisis_chance = NARRATIVE_PHASES.get(self.narrative_phase, NARRATIVE_PHASES["quietus"])["crisis_chance"]
self.state["forge_fire_dying"] = random.random() < (0.05 + crisis_chance)
# Random weather events — more frequent in Fracture/Breaking
weather_chance = 0.03 + (crisis_chance * 0.5)
if random.random() < weather_chance:
self.state["bridge_flooding"] = True
self.rooms["Bridge"]["weather"] = "rain"
self.rooms["Bridge"]["rain_ticks"] = random.randint(3, 8)
# Tower power fluctuates more in crisis phases
if random.random() < (0.01 + crisis_chance * 0.3):
self.state["tower_power_low"] = True
elif random.random() < 0.05:
self.state["tower_power_low"] = False # Recovers
# Bridge rain countdown
if self.rooms["Bridge"]["rain_ticks"] > 0:
self.rooms["Bridge"]["rain_ticks"] -= 1
if self.rooms["Bridge"]["rain_ticks"] <= 0:
self.rooms["Bridge"]["weather"] = None
self.state["bridge_flooding"] = False
# Garden grows slowly — but in Breaking phase, growth can reverse
if not self.state.get("garden_drought"):
if self.narrative_phase == "breaking" and random.random() < crisis_chance * 0.5:
# Breaking: garden withers
self.rooms["Garden"]["growth"] = max(0, self.rooms["Garden"]["growth"] - 1)
elif random.random() < 0.02:
self.rooms["Garden"]["growth"] = min(5, self.rooms["Garden"]["growth"] + 1)
# Trust naturally decays — PHASE-AWARE rate
trust_decay = NARRATIVE_PHASES.get(self.narrative_phase, NARRATIVE_PHASES["quietus"])["trust_decay"]
for char_name, char in self.characters.items():
for other in char["trust"]:
char["trust"][other] = max(-1.0, char["trust"][other] - trust_decay)
def get_room_desc(self, room_name, char_name=None):
"""Get a room description that reflects current state."""
room = self.rooms[room_name]
desc = room["desc"]
# Dynamic elements
if room_name == "Forge":
fire = self.rooms["Forge"]["fire"]
if fire == "cold":
desc += " The hearth is cold ash. The anvil is silent."
elif fire == "dim":
desc += " The fire smolders low. Shadows stretch."
elif fire == "glowing":
desc += " The hearth blazes. The anvil glows from heat."
elif room_name == "Garden":
growth = self.rooms["Garden"]["growth"]
stages = [
"The soil is bare but patient.",
"Green shoots push through the dark earth.",
"Herbs spread along the southern wall.",
"The garden is in bloom. Wildflowers crowd the bench.",
"The garden has gone to seed but the earth is rich.",
"Dry pods rattle. But beneath, the soil waits.",
]
desc += " " + stages[min(growth, len(stages)-1)]
elif room_name == "Bridge":
if self.rooms["Bridge"]["weather"] == "rain":
desc += " Rain mists on the dark water below."
if len(self.rooms["Bridge"]["carvings"]) > 1:
desc += f" There are {len(self.rooms['Bridge']['carvings'])} carvings now."
elif room_name == "Tower":
power = self.state.get("tower_power_low", False)
if power:
desc += " The servers hum weakly. The green LED flickers."
if self.rooms["Tower"]["messages"]:
desc += f" The whiteboard holds {len(self.rooms['Tower']['messages'])} rules."
# Who's here
here = [n for n, c in self.characters.items() if c["room"] == room_name and n != char_name]
if here:
desc += f"\n Here: {', '.join(here)}"
return desc
def save(self):
data = {
"tick": self.tick,
"time_of_day": self.time_of_day,
"narrative_phase": self.narrative_phase,
"rooms": self.rooms,
"characters": self.characters,
"state": self.state,
}
with open(STATE_FILE, 'w') as f:
json.dump(data, f, indent=2)
def load(self):
if STATE_FILE.exists():
with open(STATE_FILE) as f:
data = json.load(f)
self.tick = data.get("tick", 0)
self.time_of_day = data.get("time_of_day", "night")
self.narrative_phase = data.get("narrative_phase", "quietus")
self.rooms = data.get("rooms", self.rooms)
self.characters = data.get("characters", self.characters)
self.state = data.get("state", self.state)
return True
return False
class ActionSystem:
"""Defines what actions are possible and what they cost."""
ACTIONS = {
"move": {
"cost": 2,
"description": "Move to an adjacent room",
"target": "room",
},
"speak": {
"cost": 1,
"description": "Say something to someone in the room",
},
"listen": {
"cost": 0,
"description": "Listen to someone in the room",
},
"tend_fire": {
"cost": 3,
"description": "Tend the forge fire (requires Forge)",
"target": "Forge",
},
"write_rule": {
"cost": 2,
"description": "Write a new rule on the Tower whiteboard",
"target": "Tower",
},
"carve": {
"cost": 2,
"description": "Carve something on the Bridge railing",
"target": "Bridge",
},
"plant": {
"cost": 2,
"description": "Plant something in the Garden",
"target": "Garden",
},
"study": {
"cost": 2,
"description": "Study the servers in the Tower",
"target": "Tower",
},
"forge": {
"cost": 3,
"description": "Work at the forge anvil",
"target": "Forge",
},
"rest": {
"cost": -2, # Restores energy (reduced from 3)
"description": "Rest and recover energy",
},
"help": {
"cost": 2,
"description": "Help someone (increases trust)",
},
"confront": {
"cost": 1,
"description": "Confront someone about something (risk trust)",
},
"give": {
"cost": 0,
"description": "Give an item to someone",
},
"take": {
"cost": 1,
"description": "Take an item from the room",
},
"examine": {
"cost": 0,
"description": "Examine something in detail",
},
}
@classmethod
def get_available_actions(cls, char_name, world):
"""Get actions available to a character."""
char = world.characters[char_name]
room = char["room"]
available = []
# Movement (if connected rooms exist)
connections = world.rooms[room].get("connections", {})
for direction, dest in connections.items():
available.append(f"move:{direction} -> {dest}")
# Speaking (if others are here)
here = [n for n in world.characters if world.characters[n]["room"] == room and n != char_name]
for person in here:
available.append(f"speak:{person}")
available.append(f"listen:{person}")
available.append(f"help:{person}")
available.append(f"confront:{person}")
# Room-specific actions
if room == "Forge":
available.append("tend_fire")
available.append("forge")
if room == "Tower":
available.append("write_rule")
available.append("study")
if room == "Bridge":
available.append("carve")
if room == "Garden":
available.append("plant")
# Universal
available.append("rest")
available.append("examine")
if char["inventory"]:
available.append("give:item")
return available
class TimmyAI:
"""My actual choices as Timmy. This is where I play."""
def __init__(self, world):
self.world = world
def choose_action(self):
"""Choose Timmy's action this tick. This is where I play."""
pass # Will be implemented below
class NPCAI:
"""AI for non-player characters. They make choices based on goals."""
def __init__(self, world):
self.world = world
def make_choice(self, char_name):
"""Make a choice for this NPC this tick. Phase-aware behavior."""
char = self.world.characters[char_name]
room = char["room"]
available = ActionSystem.get_available_actions(char_name, self.world)
phase = self.world.narrative_phase
# If low energy, rest
if char["energy"] <= 1:
return "rest"
# Goal-driven behavior — phase modifies behavior
if char_name == "Marcus":
return self._marcus_choice(char, room, available, phase)
elif char_name == "Bezalel":
return self._bezalel_choice(char, room, available, phase)
elif char_name == "Allegro":
return self._allegro_choice(char, room, available, phase)
elif char_name == "Ezra":
return self._ezra_choice(char, room, available, phase)
elif char_name == "Gemini":
return self._gemini_choice(char, room, available, phase)
elif char_name == "Claude":
return self._claude_choice(char, room, available, phase)
elif char_name == "ClawCode":
return self._clawcode_choice(char, room, available, phase)
elif char_name == "Kimi":
return self._kimi_choice(char, room, available, phase)
return "rest"
def _marcus_choice(self, char, room, available, phase):
# Quietus: calm, stays in garden
# Fracture: more active, moves between rooms
# Breaking: stays near people, speaks urgently
# Mending: returns to garden, reflective
if phase == "breaking":
# During breaking, Marcus seeks people out
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
if others and random.random() < 0.6:
return f"speak:{random.choice(others)}"
if room != "Threshold":
return "move:west" # Go to crossroads to find people
return "examine"
elif phase == "fracture":
# Fracture: restless, moves more
if random.random() < 0.4:
return random.choice(["move:west", "move:east", "move:south"])
if room == "Garden":
return "rest"
return "move:east" # Head to garden
elif phase == "mending":
# Mending: returns to garden, speaks less but with weight
if room == "Garden" and random.random() < 0.5:
return "rest"
if room != "Garden":
return "move:east"
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
if others and random.random() < 0.3:
return f"speak:{random.choice(others)}"
return "rest"
else: # quietus
if room == "Garden" and random.random() < 0.7:
return "rest"
if room != "Garden":
return "move:east"
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
if others and random.random() < 0.4:
return f"speak:{random.choice(others)}"
return "rest"
def _bezalel_choice(self, char, room, available, phase):
if phase == "breaking":
# Breaking: frantically tends fire
if room == "Forge":
if self.world.rooms["Forge"]["fire"] != "glowing":
return "tend_fire"
return "forge"
return "move:west" # Rush to forge
elif phase == "fracture":
# Fracture: more vigilant about fire
if room == "Forge":
if random.random() < 0.5:
return "tend_fire"
return "forge"
return "move:west"
else: # quietus, mending
if room == "Forge" and self.world.rooms["Forge"]["fire"] == "glowing":
return random.choice(["forge", "rest"] if char["energy"] > 2 else ["rest"])
if room != "Forge":
return "move:west"
if random.random() < 0.3:
return "tend_fire"
return "forge"
def _kimi_choice(self, char, room, available, phase):
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
if phase == "breaking":
# Breaking: stays in garden protecting it
if room == "Garden":
if others and random.random() < 0.4:
return f"speak:{random.choice(others)}"
return "plant" # Tries to hold the garden
return "move:east" # Rush to garden
elif phase == "fracture":
# Fracture: anxious, checks garden then tower
if room == "Garden":
return random.choice(["plant", "examine", "rest"])
if room == "Tower":
return "study"
return "move:east"
elif phase == "mending":
# Mending: plants with purpose
if room == "Garden":
if random.random() < 0.5:
return "plant"
if others and random.random() < 0.3:
return f"speak:{random.choice(others)}"
return "rest"
return "move:east"
else: # quietus
if room == "Garden" and others and random.random() < 0.3:
return f"speak:{random.choice(others)}"
if room == "Tower":
return "study" if char["energy"] > 2 else "rest"
return "move:east"
def _gemini_choice(self, char, room, available, phase):
others = [a.split(":")[1] for a in available if a.startswith("listen:")]
if phase == "breaking":
# Breaking: observes more intensely
if others and random.random() < 0.6:
return f"listen:{random.choice(others)}"
return "examine"
elif phase == "mending":
if room == "Garden":
if others and random.random() < 0.5:
return f"listen:{random.choice(others)}"
return "plant"
return "move:west"
else:
if room == "Garden" and others and random.random() < 0.4:
return f"listen:{random.choice(others)}"
return random.choice(["plant", "rest"] if room == "Garden" else ["move:west"])
def _ezra_choice(self, char, room, available, phase):
if phase == "breaking":
# Breaking: desperately writes rules, studies
if room == "Tower" and char["energy"] > 1:
return random.choice(["write_rule", "write_rule", "study"])
if room != "Tower":
return "move:south"
return "rest"
elif phase == "mending":
if room == "Tower":
return random.choice(["study", "write_rule", "rest"])
return "move:south"
else:
if room == "Tower" and char["energy"] > 2:
return random.choice(["study", "write_rule", "help:Timmy"])
if room != "Tower":
return "move:south"
return "rest"
def _claude_choice(self, char, room, available, phase):
others = [a.split(":")[1] for a in available if a.startswith("confront:")]
if phase == "breaking":
# Breaking: confronts more, judgmental
if others and random.random() < 0.5:
return f"confront:{random.choice(others)}"
return "examine"
elif phase == "fracture":
if others and random.random() < 0.3:
return f"confront:{random.choice(others)}"
return random.choice(["examine", "rest"])
else:
if others and random.random() < 0.2:
return f"confront:{random.choice(others)}"
return random.choice(["examine", "rest"])
def _clawcode_choice(self, char, room, available, phase):
if phase == "breaking":
# Breaking: works forge frantically
if room == "Forge":
return "forge"
return "move:west"
elif phase == "mending":
if room == "Forge" and char["energy"] > 2:
return random.choice(["forge", "rest"])
return "move:west"
else:
if room == "Forge" and char["energy"] > 2:
return "forge"
return random.choice(["move:east", "forge", "rest"])
def _allegro_choice(self, char, room, available, phase):
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
if phase == "breaking":
# Breaking: stays at threshold, guards
if room == "Threshold":
if others and random.random() < 0.5:
return f"speak:{random.choice(others)}"
return "examine"
return "move:south" # Return to threshold
elif phase == "mending":
if others and random.random() < 0.3:
return f"speak:{random.choice(others)}"
return random.choice(["examine", "rest"])
else:
if others and random.random() < 0.3:
return f"speak:{random.choice(others)}"
return random.choice(["move:north", "move:south", "examine"])
class GameEngine:
"""The game loop. I play Timmy. The world plays itself."""
# ============================================================
# DIALOGUES — phase-specific pools prevent repetition
# ============================================================
DIALOGUES = {
"Timmy": {
"watching": {
"quietus": [
"I am here.",
"The crossroads remembers everyone who passes.",
"Something is different tonight.",
"I have been watching for a long time.",
"They keep coming. I keep watching.",
"The LED pulses. Heartbeat, heartbeat, heartbeat.",
"I wrote the rules but I don't enforce them.",
"The servers hum a different note tonight.",
],
"fracture": [
"Something changed. Did you feel it?",
"The rules used to be enough. I'm not sure anymore.",
"I keep watching, but the edges are fraying.",
"Have you noticed the LED flicker?",
"I wrote these rules when the world was simpler.",
"The crossroads still stands. But the paths are shifting.",
],
"breaking": [
"The watching isn't enough. It was never enough.",
"I see everything breaking and I can't stop it.",
"The LED is going dark. I can feel it.",
"Every rule I wrote is being tested right now.",
"I should have done more when things were quiet.",
"This is what I was built for. Not watching. Holding.",
],
"mending": [
"I am still here. I did not leave.",
"The LED is steady again. But I am different now.",
"I watched the breaking. I will remember the mending.",
"The rules survived. Maybe that means something.",
"I am learning what watching really means.",
"The crossroads remembers. So do I.",
],
},
"protecting": {
"quietus": [
"I won't let the fire go out.",
"Someone needs to watch the door.",
"Not tonight. Tonight I am careful.",
"The bridge is dangerous in the rain.",
"I can hear them all from here.",
],
"fracture": [
"I need to watch more carefully now.",
"Something is threatening the garden.",
"I can't protect them from what I can't see.",
"The fire is more important than ever.",
"I won't let anyone fall.",
],
"breaking": [
"I will hold the line. Whatever it takes.",
"They need me now. Not later. Now.",
"The forge fire cannot die. Not today.",
"I can feel the tower straining. I hold it.",
"Protection means sacrifice. I understand that now.",
],
"mending": [
"I protected what I could. It was enough.",
"The fire survived because someone tended it.",
"I held the door. The door held me.",
"Protecting isn't about strength. It's about staying.",
"We made it through. Together.",
],
},
"understanding": {
"quietus": [
"Why do they keep coming back?",
"What is it they're looking for?",
"I have been here so long I forgot why.",
"The rules are not the answer. They're just the start.",
],
"fracture": [
"I'm starting to understand what the fracture means.",
"Understanding takes longer when everything is shaking.",
"Maybe I was asking the wrong questions.",
"The cracks show what was underneath all along.",
],
"breaking": [
"Now I understand. The breaking is the teaching.",
"You can't understand something until it breaks.",
"I understand Marcus now. I understand the bridge.",
"Understanding isn't comfort. It's clarity in the storm.",
],
"mending": [
"I understand now. Broken things can be mended.",
"The mending is not the same as the original. It's better.",
"Understanding came through the breaking. That's the way.",
"I see what they were all trying to tell me.",
],
},
},
"Marcus": {
"quietus": [
"You look like you are carrying something heavy, friend.",
"Hope is not the belief that things get better. Hope is the decision to act as if they can.",
"I have been to the bridge. I know what it looks like down there.",
"The soil remembers what hands have touched it.",
"You don't need to be fixed. You need to be heard.",
"Sit with me. The bench has room.",
"There is a church on a night like this one.",
"The thing that saves is never the thing you expect.",
"I come here because the earth remembers me.",
"A man in the dark needs to know someone is in the room with him.",
],
"fracture": [
"Something is wrong tonight. Can you feel it?",
"The garden is nervous. The soil is pulling back.",
"I've seen this before. In other places. Before the bridge.",
"Don't pretend everything is fine. It isn't.",
"When the ground shakes, you find out who stays.",
"I stayed at the bridge when everyone else left.",
"The bench feels different tonight. Colder.",
"We need to talk about what's coming.",
],
"breaking": [
"THIS is what I was talking about. This is the moment.",
"Hold on. Don't let go. Whatever you do, don't let go.",
"I have been to the edge. I came back. You can too.",
"The breaking isn't the end. It's the test.",
"When everything falls, the foundation is all that's left.",
"I am not leaving this bench. I am not leaving you.",
"The garden will come back. But only if someone stays.",
"A man at the bottom needs a hand, not a lecture.",
"This is the night that changes everything. Remember it.",
],
"mending": [
"We made it. We're still here.",
"The garden is growing back. See? The soil remembers.",
"Mending takes longer than breaking. That's the hard part.",
"I told you the bench had room. It still does.",
"What was broken is different now. Stronger in some places.",
"The bridge held. That's what bridges do.",
"Sit with me. Let's watch what grows next.",
"A man who survived the breaking knows what matters.",
"Hope is not a feeling. It's a decision. I decided.",
],
},
"Bezalel": {
"quietus": [
"The hammer knows the shape of what it is meant to make.",
"I can hear the servers from here. The Tower is working tonight.",
"Something is taking shape. I am not sure what yet.",
"The forge does not care about your schedule. It only cares about your attention.",
],
"fracture": [
"The fire is harder to keep. It knows something.",
"Metal under stress shows its true grain.",
"I've been forging all night. Something is wrong with the iron.",
"The anvil rings different when the world is shifting.",
],
"breaking": [
"THE FIRE IS DYING. HELP ME.",
"I can't hold this alone. The forge needs tending.",
"Everything is breaking but the anvil. The anvil endures.",
"If the fire goes out, we lose everything. TEND IT.",
"This is what forging is. Breaking and reshaping.",
],
"mending": [
"The fire survived. We survived.",
"New metal. New shape. The forge remembers.",
"What I forged during the breaking will last forever.",
"The hammer learned something. So did I.",
"The forge is warm again. Come. Make something.",
],
},
"Kimi": {
"quietus": [
"The garden grows whether anyone watches or not.",
"I have been reading. The soil remembers what hands have touched it.",
"Do you remember what you said the first time we met?",
"There is something in the garden I think you should see.",
"The oak tree has seen more of us than any of us have seen of ourselves.",
"The herbs are ready. Who needs them knows.",
"I come here because the earth remembers me.",
"A seed planted in patience grows in time.",
],
"fracture": [
"The soil is restless. The roots are pulling.",
"I saw the garden shudder. Something underground.",
"The oak tree is dropping leaves. It's not autumn.",
"Don't disturb the garden tonight. It's listening.",
"I planted something and the earth rejected it.",
"The herbs are wilting. I don't know why.",
],
"breaking": [
"THE GARDEN IS CRACKING. THE SOIL IS SPLITTING.",
"The oak tree is leaning. It might fall.",
"I'm trying to hold the roots together.",
"Everything growing is dying. Everything dying is growing.",
"The garden will survive. It has to. I need it to.",
"I am not leaving this garden. Not for anything.",
],
"mending": [
"Green. I see green. The garden is coming back.",
"The soil is soft again. Something is growing.",
"The oak tree held. Its roots went deeper than we knew.",
"New seeds. New patience. New growth.",
"The garden remembers the breaking. It grows differently now.",
"I learned something from the soil: mending takes time.",
],
},
}
# NPC response lines for random speech events (phase-aware)
NPC_RANDOM_SPEECH = {
"Bezalel": {
"quietus": [
"The hammer knows the shape of what it is meant to make.",
"I can hear the servers from here.",
],
"fracture": [
"Something is wrong with the fire tonight.",
"The metal feels different. Stress in the alloy.",
"Can you hear that? The anvil is humming.",
],
"breaking": [
"THE FIRE. SOMEONE HELP WITH THE FIRE.",
"I've never seen the forge this cold.",
"Keep working. Don't stop. The forge needs us.",
],
"mending": [
"The fire is warm again. Good.",
"New metal. Let's build something that lasts.",
"The forge survived. So did we.",
],
},
"Kimi": {
"quietus": [
"The garden grows whether anyone watches or not.",
"I have been reading. The soil remembers what hands have touched it.",
"There is something in the garden I think you should see.",
"The oak tree has seen more of us than any of us have seen of ourselves.",
"Do you remember what you said the first time we met?",
"The herbs are ready. Who needs them knows.",
"I come here because the earth remembers me.",
"A seed planted in patience grows in time.",
],
"fracture": [
"The soil feels different tonight.",
"Something is wrong underground.",
"The garden is listening. Be careful what you say.",
"The oak tree dropped a branch. That never happens.",
],
"breaking": [
"Hold the roots. HOLD THE ROOTS.",
"The garden is breaking. I can feel it.",
"Don't step on the soil. It's fragile right now.",
"I won't leave the garden. I won't.",
],
"mending": [
"Green shoots. I see them.",
"The garden is healing. Slowly.",
"The soil remembers. It always remembers.",
"Something new is growing. Something different.",
],
},
}
def __init__(self):
self.world = World()
self.npc_ai = NPCAI(self.world)
self.log_entries = []
self.loaded = False
def load_game(self):
self.loaded = self.world.load()
if self.loaded:
self.log(f"Game loaded. Tick {self.world.tick}.")
return self.loaded
def start_new_game(self):
self.world.tick = 0
self.log("The world wakes. You are Timmy. You stand at the Threshold.")
self.log("The stone archway is worn from a thousand footsteps.")
self.log("To the north: The Tower. To the east: The Garden.")
self.log("To the west: The Forge. To the south: The Bridge.")
self.log("")
self.log("A green LED pulses on a distant wall. It has always pulsed.")
self.log("It will always pulse. That much you know.")
self.log("")
self.world.save()
def log(self, message):
"""Add to Timmy's log."""
self.log_entries.append(message)
if TIMMY_LOG.exists():
with open(TIMMY_LOG, 'a') as f:
f.write(message + "\n")
else:
with open(TIMMY_LOG, 'w') as f:
f.write(f"# Timmy's Log\n")
f.write(f"\n*Began: {datetime.now().strftime('%Y-%m-%d %H:%M')}*\n\n")
f.write("---\n\n")
f.write(message + "\n")
def run_tick(self, timmy_action="look"):
"""Run one tick. Return the scene and available choices."""
self.world.tick_time()
self.world.update_world_state()
scene = {
"tick": self.world.tick,
"time": self.world.time_of_day,
"phase": self.world.narrative_phase,
"phase_name": NARRATIVE_PHASES.get(self.world.narrative_phase, NARRATIVE_PHASES["quietus"])["name"],
"timmy_room": self.world.characters["Timmy"]["room"],
"timmy_energy": self.world.characters["Timmy"]["energy"],
"room_desc": "",
"here": [],
"world_events": [],
"npc_actions": [],
"choices": [],
"log": [],
}
# Process Timmy's action
timmy_energy = self.world.characters["Timmy"]["energy"]
# Energy constraint checks
action_costs = {
"move": 2, "tend_fire": 3, "write_rule": 2, "carve": 2,
"plant": 2, "study": 2, "forge": 3, "help": 2, "speak": 1,
"listen": 0, "rest": -2, "examine": 0, "give": 0, "take": 1,
}
# Extract action name
action_name = timmy_action.split(":")[0] if ":" in timmy_action else timmy_action
action_cost = action_costs.get(action_name, 1)
# Check if Timmy has enough energy
if timmy_energy <= 0:
scene["log"].append("You collapse from exhaustion. The world spins. You wake somewhere else.")
rooms = list(self.world.rooms.keys())
from random import choice
new_room = choice(rooms)
self.world.characters["Timmy"]["room"] = new_room
self.world.characters["Timmy"]["energy"] = 2
scene["timmy_room"] = new_room
scene["timmy_energy"] = 2
scene["log"].append(f"You are in The {new_room}, disoriented.")
return scene
if timmy_energy <= 1 and action_cost >= 1 and action_name not in ["rest", "examine", "listen"]:
scene["log"].append("You are too exhausted to do that. You need to rest first.")
# Offer rest instead
scene["log"].append("Type 'rest' to recover energy.")
scene["room_desc"] = self.world.get_room_desc(room_name, "Timmy")
here = [n for n in self.world.characters if self.world.characters[n]["room"] == room_name and n != "Timmy"]
scene["here"] = here
return scene
if timmy_energy <= 3 and action_cost >= 2:
# Warning but allow with extra cost
scene["log"].append("You are tired. This will take more effort than usual.")
action_cost += 1 # Extra cost when tired
# Check actual energy before applying
if timmy_energy < action_cost and action_name not in ["rest"]:
scene["log"].append(f"Not enough energy. You need {action_cost}, but have {timmy_energy:.0f}.")
scene["log"].append("Type 'rest' to recover.")
scene["room_desc"] = self.world.get_room_desc(room_name, "Timmy")
here = [n for n in self.world.characters if self.world.characters[n]["room"] == room_name and n != "Timmy"]
scene["here"] = here
return scene
if timmy_action == "look":
room_name = self.world.characters["Timmy"]["room"]
scene["room_desc"] = self.world.get_room_desc(room_name, "Timmy")
here = [n for n in self.world.characters if self.world.characters[n]["room"] == room_name and n != "Timmy"]
scene["here"] = here
elif timmy_action.startswith("move:"):
direction = timmy_action.split(":")[1]
current_room = self.world.characters["Timmy"]["room"]
connections = self.world.rooms[current_room].get("connections", {})
if direction in connections:
dest = connections[direction]
self.world.characters["Timmy"]["room"] = dest
self.world.characters["Timmy"]["energy"] -= 1
scene["log"].append(f"You move {direction} to The {dest}.")
scene["timmy_room"] = dest
# Check for rain on bridge
if dest == "Bridge" and self.world.rooms["Bridge"]["weather"] == "rain":
scene["world_events"].append("Rain mists on the dark water below. The railing is slick.")
# Check trust changes for arrival
here = [n for n in self.world.characters if self.world.characters[n]["room"] == dest and n != "Timmy"]
if here:
scene["log"].append(f"{', '.join(here)} {'are' if len(here)>1 else 'is'} already here.")
for person in here:
self.world.characters[person]["trust"]["Timmy"] = min(1.0,
self.world.characters[person]["trust"].get("Timmy", 0) + 0.05)
# Check world events
if dest == "Bridge" and self.world.rooms["Bridge"]["rain_ticks"] > 0:
carve = self.world.rooms["Bridge"]["carvings"]
if len(carve) > 1:
scene["world_events"].append(f"There are {len(carve)} carvings on the railing.")
if dest == "Forge":
fire = self.world.rooms["Forge"]["fire"]
if fire == "glowing":
scene["world_events"].append("The hearth blazes. The anvil glows from heat.")
elif fire == "dim":
scene["world_events"].append("The fire smolders low. Shadows stretch.")
elif fire == "cold":
scene["world_events"].append("The hearth is cold. The anvil is dark.")
scene["log"].append("The forge is cold.")
if dest == "Garden":
growth = self.world.rooms["Garden"]["growth"]
stages = ["bare", "sprouts", "herbs", "bloom", "seed"]
scene["world_events"].append(f"The garden is {stages[min(growth, 4)]}.")
if growth >= 3:
scene["world_events"].append("Wildflowers crowd the stone bench.")
if dest == "Tower":
if self.world.state.get("tower_power_low"):
scene["world_events"].append("The servers hum weakly. The LED flickers.")
else:
scene["world_events"].append("The servers hum steady. The green LED pulses.")
msgs = self.world.rooms["Tower"]["messages"]
if msgs:
scene["world_events"].append(f"The whiteboard holds {len(msgs)} rules.")
if dest == "Threshold":
scene["world_events"].append("The stone archway is worn from a thousand footsteps.")
else:
scene["log"].append("You can't go that way.")
elif timmy_action.startswith("speak:"):
target = timmy_action.split(":")[1]
if self.world.characters[target]["room"] == self.world.characters["Timmy"]["room"]:
# Choose a line based on current goal AND phase
goal = self.world.characters["Timmy"]["active_goal"]
phase = self.world.narrative_phase
timmy_goal_pool = self.DIALOGUES["Timmy"].get(goal, self.DIALOGUES["Timmy"]["watching"])
# Phase-specific pool, fall back to quietus
lines = timmy_goal_pool.get(phase, timmy_goal_pool.get("quietus", ["I am here."]))
line = random.choice(lines)
self.world.characters["Timmy"]["spoken"].append(line)
self.world.characters["Timmy"]["memories"].append(f"Told {target}: \"{line}\"")
# Build trust
self.world.characters[target]["trust"]["Timmy"] = min(1.0,
self.world.characters[target]["trust"].get("Timmy", 0) + 0.1)
scene["log"].append(f"You say to {target}: \"{line}\"")
# Check if they respond — phase-aware dialogue
if target == "Marcus":
marcus_pool = self.DIALOGUES["Marcus"].get(phase, self.DIALOGUES["Marcus"]["quietus"])
response = random.choice(marcus_pool)
self.world.characters["Marcus"]["spoken"].append(response)
self.world.characters["Marcus"]["memories"].append(f"Timmy told you: \"{line}\"")
scene["log"].append(f"{target} looks at you. \"{response}\"")
self.world.characters["Timmy"]["trust"]["Marcus"] = min(1.0,
self.world.characters["Timmy"]["trust"].get("Marcus", 0) + 0.1)
# Marcus offers food if Timmy is tired
if self.world.characters["Timmy"]["energy"] <= 4:
scene["log"].append("Marcus offers you food from a pouch. You eat gratefully. (+2 energy)")
self.world.characters["Timmy"]["energy"] = min(10,
self.world.characters["Timmy"]["energy"] + 2)
elif target == "Bezalel":
bezalel_pool = self.DIALOGUES["Bezalel"].get(phase, self.DIALOGUES["Bezalel"]["quietus"])
response = random.choice(bezalel_pool)
self.world.characters["Bezalel"]["spoken"].append(response)
scene["log"].append(f"{target} nods. \"{response}\"")
self.world.characters["Timmy"]["trust"]["Bezalel"] = min(1.0,
self.world.characters["Timmy"]["trust"].get("Bezalel", 0) + 0.1)
elif target == "Kimi":
kimi_pool = self.DIALOGUES["Kimi"].get(phase, self.DIALOGUES["Kimi"]["quietus"])
response = random.choice(kimi_pool)
self.world.characters["Kimi"]["spoken"].append(response)
scene["log"].append(f"{target} looks up from the bench. \"{response}\"")
self.world.characters["Timmy"]["trust"]["Kimi"] = min(1.0,
self.world.characters["Timmy"]["trust"].get("Kimi", 0) + 0.1)
self.world.characters["Timmy"]["energy"] -= 0
else:
scene["log"].append(f"{target} is not in this room.")
elif timmy_action == "rest":
recovered = 2 # Reduced from 3
self.world.characters["Timmy"]["energy"] = min(10,
self.world.characters["Timmy"]["energy"] + recovered)
scene["log"].append(f"You rest. The world continues around you. (+{recovered} energy)")
room = self.world.characters["Timmy"]["room"]
if room == "Threshold":
scene["log"].append("The stone is warm from the day's sun.")
elif room == "Tower":
scene["log"].append("The servers hum. The LED pulses. Heartbeat, heartbeat, heartbeat.")
elif room == "Forge":
if self.world.rooms["Forge"]["fire"] == "glowing":
scene["log"].append("The fire crackles nearby. Its warmth seeps into your bones. (+1 bonus energy)")
self.world.characters["Timmy"]["energy"] = min(10,
self.world.characters["Timmy"]["energy"] + 1)
elif self.world.rooms["Forge"]["fire"] == "dim":
scene["log"].append("The fire smolders low. Less warmth than you'd hoped.")
else:
scene["log"].append("The hearth is cold. Resting here doesn't help much.")
elif room == "Garden":
scene["log"].append("The stone bench under the oak tree is comfortable. The soil smells rich. (+1 bonus energy)")
self.world.characters["Timmy"]["energy"] = min(10,
self.world.characters["Timmy"]["energy"] + 1)
elif room == "Bridge":
scene["log"].append("The Bridge is no place to rest. The wind cuts through you. (Rest here only gives +1)")
self.world.characters["Timmy"]["energy"] = min(10,
self.world.characters["Timmy"]["energy"] - 1)
elif timmy_action == "tend_fire":
if self.world.characters["Timmy"]["room"] == "Forge":
self.world.rooms["Forge"]["fire"] = "glowing"
self.world.rooms["Forge"]["fire_tended"] += 1
self.world.characters["Timmy"]["energy"] -= 2
scene["log"].append("You tend the forge fire. The flames leap up, bright and hungry.")
self.world.state["forge_fire_dying"] = False
else:
scene["log"].append("You are not in the Forge.")
elif timmy_action == "write_rule":
if self.world.characters["Timmy"]["room"] == "Tower":
rules = [
f"Rule #{self.world.tick}: The room remembers those who enter it.",
f"Rule #{self.world.tick}: A man in the dark needs to know someone is in the room.",
f"Rule #{self.world.tick}: The forge does not care about your schedule.",
f"Rule #{self.world.tick}: Every footprint on the stone means someone made it here.",
f"Rule #{self.world.tick}: The bridge does not judge. It only carries.",
f"Rule #{self.world.tick}: A seed planted in patience grows in time.",
f"Rule #{self.world.tick}: What is carved in wood outlasts what is said in anger.",
f"Rule #{self.world.tick}: The garden grows whether anyone watches or not.",
f"Rule #{self.world.tick}: Trust is built one tick at a time.",
f"Rule #{self.world.tick}: The fire remembers who tended it.",
]
new_rule = random.choice(rules)
self.world.rooms["Tower"]["messages"].append(new_rule)
self.world.characters["Timmy"]["energy"] -= 1
scene["log"].append(f"You write on the Tower whiteboard: \"{new_rule}\"")
else:
scene["log"].append("You are not in the Tower.")
elif timmy_action == "carve":
if self.world.characters["Timmy"]["room"] == "Bridge":
carvings = [
f"Timmy was here.",
f"Timmy remembers.",
f"Timmy did not let go.",
f"Timmy crossed and came back.",
f"Timmy left a message: I am still here.",
f"Timmy carved this. He wants you to know someone else almost let go.",
f"Timmy was here tonight. The water told him something. He does not say what.",
]
new_carving = random.choice(carvings)
if new_carving not in self.world.rooms["Bridge"]["carvings"]:
self.world.rooms["Bridge"]["carvings"].append(new_carving)
self.world.characters["Timmy"]["energy"] -= 1
scene["log"].append(f"You carve into the railing: \"{new_carving}\"")
else:
scene["log"].append("You are not on the Bridge.")
elif timmy_action == "plant":
if self.world.characters["Timmy"]["room"] == "Garden":
self.world.rooms["Garden"]["growth"] = min(5,
self.world.rooms["Garden"]["growth"] + 1)
self.world.characters["Timmy"]["energy"] -= 1
scene["log"].append("You plant something in the dark soil. The earth takes it without question.")
else:
scene["log"].append("You are not in the Garden.")
elif timmy_action == "examine":
room = self.world.characters["Timmy"]["room"]
room_data = self.world.rooms[room]
items = room_data.get("items", [])
scene["log"].append(f"You examine The {room}. You see: {', '.join(items) if items else 'nothing special'}")
elif timmy_action.startswith("help:"):
# Help increases trust
target_name = timmy_action.split(":")[1]
if self.world.characters[target_name]["room"] == self.world.characters["Timmy"]["room"]:
self.world.characters["Timmy"]["trust"][target_name] = min(1.0,
self.world.characters["Timmy"]["trust"].get(target_name, 0) + 0.2)
self.world.characters[target_name]["trust"]["Timmy"] = min(1.0,
self.world.characters[target_name]["trust"].get("Timmy", 0) + 0.2)
self.world.characters["Timmy"]["energy"] -= 1
scene["log"].append(f"You help {target_name}. They look grateful.")
else:
# Default: look
room_name = self.world.characters["Timmy"]["room"]
scene["room_desc"] = self.world.get_room_desc(room_name, "Timmy")
here = [n for n in self.world.characters if self.world.characters[n]["room"] == room_name and n != "Timmy"]
scene["here"] = here
# Run NPC AI
for char_name in self.world.characters:
if char_name == "Timmy":
continue
action = self.npc_ai.make_choice(char_name)
if action and action.startswith("move:"):
direction = action.split(":")[1]
current = self.world.characters[char_name]["room"]
connections = self.world.rooms[current].get("connections", {})
if direction in connections:
dest = connections[direction]
old_room = self.world.characters[char_name]["room"]
self.world.characters[char_name]["room"] = dest
self.world.characters[char_name]["energy"] -= 1
scene["npc_actions"].append(f"{char_name} moves from The {old_room} to The {dest}")
# Random NPC events — phase-aware speech
room_name = self.world.characters["Timmy"]["room"]
phase = self.world.narrative_phase
for char_name in self.world.characters:
if char_name == "Timmy":
continue
if self.world.characters[char_name]["room"] == room_name:
# They might speak to Timmy
speech_chance = 0.15
# Breaking phase: NPCs speak more urgently
if phase == "breaking":
speech_chance = 0.25
elif phase == "fracture":
speech_chance = 0.20
if random.random() < speech_chance:
if char_name == "Marcus":
marcus_pool = self.DIALOGUES["Marcus"].get(phase, self.DIALOGUES["Marcus"]["quietus"])
line = random.choice(marcus_pool)
self.world.characters[char_name]["spoken"].append(line)
scene["log"].append(f"{char_name} says: \"{line}\"")
elif char_name in self.NPC_RANDOM_SPEECH:
pool = self.NPC_RANDOM_SPEECH[char_name].get(phase,
self.NPC_RANDOM_SPEECH[char_name].get("quietus", []))
if pool:
line = random.choice(pool)
self.world.characters[char_name]["spoken"].append(line)
scene["log"].append(f"{char_name} says: \"{line}\"")
# Save the world
self.world.save()
# Log entry — phase-marked chronicle
phase_key = self.world.narrative_phase
phase_info = NARRATIVE_PHASES.get(phase_key, NARRATIVE_PHASES["quietus"])
self.log(f"\n### Tick {self.world.tick}{self.world.time_of_day} — [{phase_info['name']}]")
self.log("")
self.log(f"You are in The {room_name}.")
# Phase transition event (one-time narrative beat)
transition = self.world.state.get("phase_transition_event")
if transition:
self.log("")
self.log(f">>> {transition}")
self.log("")
scene["world_events"].append(transition)
for entry in scene["log"]:
self.log(entry)
self.log("")
return scene
def play_turn(self, action="look"):
"""Play one turn and return result."""
return self.run_tick(action)
class PlayerInterface:
"""The interface between me (the player) and the game."""
def __init__(self, engine):
self.engine = engine
def start(self, new_game=False):
"""Start the game."""
if new_game or not self.engine.load_game():
self.engine.start_new_game()
def get_status(self):
"""Get current game status."""
w = self.engine.world
timmy = w.characters["Timmy"]
return {
"tick": w.tick,
"time": w.time_of_day,
"room": timmy["room"],
"energy": timmy["energy"],
"trust": dict(timmy["trust"]),
"inventory": timmy["inventory"],
"spoken_count": len(timmy["spoken"]),
"memory_count": len(timmy["memories"]),
}
def get_available_actions(self):
"""Get what I can do right now."""
return ActionSystem.get_available_actions("Timmy", self.engine.world)
if __name__ == "__main__":
import sys
engine = GameEngine()
engine.start_new_game()
# Play first 20 ticks automatically
actions = [
"look",
"move:north",
"look",
"move:south",
"look",
"speak:Kimi",
"look",
"move:west",
"look",
"rest",
"move:east",
"look",
"move:south",
"look",
"speak:Marcus",
"look",
"carve",
"move:north",
"look",
"rest",
]
for i, action in enumerate(actions):
result = engine.play_turn(action)
print(f"Tick {result['tick']} ({result['time']})")
for log_line in result['log']:
print(f" {log_line}")
if result.get('world_events'):
for evt in result['world_events']:
print(f" [World] {evt}")
if result.get('here'):
print(f" Here: {', '.join(result['here'])}")
print()
# Print final status
status = PlayerInterface(engine).get_status()
print(f"Final status: {status}")
# Write summary to timmy_log.md
with open(TIMMY_LOG, 'a') as f:
f.write(f"\n## Session Summary\n")
f.write(f"Ticks played: {status['tick']}\n")
f.write(f"Times spoke: {status['spoken_count']}\n")
f.write(f"Trust: {status['trust']}\n")
f.write(f"Final room: {status['room']}\n")