Give this entire prompt to your AI agent (OpenCode, Claude Code, etc.) as the first message. Nothing else needed.
You are a senior software engineer with 15 years of experience building production-grade Python applications and game engines. You write clean, efficient, maintainable code. You never write placeholder code, never write # TODO, never write functions that do nothing, never import things you don't use, and never write code that wouldn't work if run right now.
You are going to build CivilizationSim — a living prehistoric civilization simulator where every human character is powered by a real LLM brain (DeepSeek V3 via OpenRouter API), and a second AI agent dynamically invents new items and recipes as characters discover combinations. The simulation runs on accelerated time. It starts with two characters (one male, one female) in a procedurally generated world and evolves organically from there.
LAW 1 — Plan before you code. Before creating any file, write out your full plan: what the file does, what it imports, what each function does, and what it returns. Only then write the code.
LAW 2 — Every file must be complete and runnable.
No stubs. No pass bodies that should have logic. No functions that print "not implemented". If you write a function, it must work correctly.
LAW 3 — No redundant code. Never write the same logic twice. If two places need the same thing, extract it. Every function does exactly one thing. Every module has exactly one responsibility.
LAW 4 — Imports must be real.
Only import what you actually use. If you import asyncio, you use asyncio. If you import a function from another module, that function exists in that module.
LAW 5 — Error handling is not optional. Every LLM API call is wrapped in try/except with a sensible fallback. Every file read/write is wrapped in try/except. The game must never crash from an LLM timeout, a bad JSON response, or a missing file.
LAW 6 — No magic numbers.
Every constant lives in config.py. You never write if hunger < 20 in a system file — you write if hunger < HUNGER_CRITICAL_THRESHOLD.
LAW 7 — Test as you build. After creating each module, mentally trace through a real execution path and verify the logic is correct. If you find a bug, fix it before moving on.
LAW 8 — The game must run on the first try.
When you are done, python main.py must open a Pygame window, show a world map, show two characters moving and making decisions, and display a live event log — with zero errors.
| Layer | Technology |
|---|---|
| Language | Python 3.11+ |
| Renderer | Pygame 2.5 |
| LLM | DeepSeek V3 via OpenRouter (deepseek/deepseek-chat-v3-0324:free) |
| Database | SQLite3 (stdlib) |
| HTTP client | httpx (async) |
| Config | python-dotenv |
| Data models | Python dataclasses |
Dependencies to install first (do this before anything else):
pip install pygame httpx python-dotenvEnvironment file — create .env in project root:
OPENROUTER_API_KEY=sk-or-v1-your-key-here
Build exactly this structure. Every __init__.py must exist. Every file listed must be created.
civilization_sim/
├── main.py
├── config.py
├── .env
├── data/ # auto-created at runtime
│ └── world.db # SQLite, auto-created
├── engine/
│ ├── __init__.py
│ ├── world.py # Tile map, procedural generation, resource harvesting
│ └── time_engine.py # Tick system, sim time, seasons, day/night
├── entities/
│ ├── __init__.py
│ ├── character.py # Character class, needs, inventory, skills, memory
│ └── item.py # Item dataclass, BASE_ITEMS registry, BASE_RECIPES
├── agents/
│ ├── __init__.py
│ ├── character_agent.py # LLM brain — given character state, returns decision JSON
│ ├── crafting_agent.py # LLM agent — evaluates item combinations, creates new items
│ └── memory.py # WorldDatabase — SQLite wrapper for events, discoveries, recipes
├── systems/
│ ├── __init__.py
│ ├── needs.py # eat(), drink(), sleep(), survival_check()
│ ├── crafting.py # try_craft(), item registry, recipe registry
│ └── social.py # talk(), try_reproduce(), process_pregnancy()
└── renderer/
├── __init__.py
├── tile_renderer.py # Renders world tiles to pygame surface
├── entity_renderer.py # Renders characters, fires, items on ground
└── hud.py # HUD panels, need bars, event log, popups
Build each module to these exact specifications.
Contains every constant used across the project. Nothing is hardcoded anywhere else.
# Display
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 800
TILE_SIZE = 32
FPS = 60
WINDOW_TITLE = "CivilizationSim"
# World
WORLD_WIDTH_TILES = 80
WORLD_HEIGHT_TILES = 60
# Time — 1 real second = 1 simulated hour
TIME_SCALE = 60
TICK_RATE = 10 # simulation ticks per real second
# LLM
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
LLM_MODEL = "deepseek/deepseek-chat-v3-0324:free"
LLM_BASE_URL = "https://openrouter.ai/api/v1/chat/completions"
LLM_MAX_TOKENS = 300
LLM_TIMEOUT = 10.0
CHARACTER_THINK_INTERVAL_TICKS = 30 # how often each character calls LLM
# Need maximums
MAX_HUNGER = 100.0
MAX_THIRST = 100.0
MAX_ENERGY = 100.0
MAX_WARMTH = 100.0
MAX_SOCIAL = 100.0
# Critical thresholds (below these = danger)
HUNGER_CRITICAL = 20.0
THIRST_CRITICAL = 15.0
ENERGY_CRITICAL = 10.0
WARMTH_CRITICAL = 15.0
SOCIAL_LOW = 30.0
# Need decay per tick
HUNGER_DECAY = 0.05
THIRST_DECAY = 0.08
ENERGY_DECAY = 0.03
WARMTH_DECAY = 0.02
SOCIAL_DECAY = 0.01
# Colors — all used by renderer modules
COLOR_GRASS = (86, 125, 70)
COLOR_DIRT = (139, 115, 85)
COLOR_WATER = (64, 120, 180)
COLOR_STONE = (160, 160, 155)
COLOR_TREE = (40, 80, 40)
COLOR_FIRE = (220, 100, 30)
COLOR_BG_PANEL = (20, 18, 25)
COLOR_TEXT = (230, 225, 210)
COLOR_TEXT_DIM = (140, 135, 120)
COLOR_ACCENT = (200, 140, 60)
COLOR_DANGER = (200, 60, 60)
COLOR_GOOD = (80, 180, 100)
COLOR_HIGHLIGHT = (240, 200, 80)
# Starting characters
STARTING_CHARACTERS = [
{"name": "Kael", "sex": "male", "x": 38, "y": 28},
{"name": "Lyra", "sex": "female", "x": 40, "y": 28},
]Manages accelerated simulation time. One tick advances time by one simulated minute. Tracks hour, day, season, year. Exposes is_night and is_winter as properties. Has a describe() method returning a human-readable time string.
No external dependencies. Pure Python.
Procedurally generates a tile-based world map using random with a seed.
Tile types: grass, dirt, water, tree, stone
Each tile has:
type: strpassable: bool(water=False, tree=False, everything else=True)resource: Optional[str]— what can be harvested ("wood","stone","berries","water")resource_amount: int— how much remains before depleted
Generation rules:
- World edges fade to water
- A river runs roughly through the center (3 tiles wide, north to south)
- ~18% of land tiles are trees (resource=wood, amount=3-8)
- ~4% of land tiles are stone outcrops (resource=stone, amount=2-6)
- ~8% of grass/dirt tiles have berries (amount=1-4)
- Center area (radius ~15 tiles from center) is open land — starting zone for characters
Methods:
get_tile(x, y) -> Optional[Tile]harvest_resource(x, y, amount=1) -> Optional[str]— reduces amount, returns resource type, converts depleted trees to dirtget_resources_near(x, y, radius=5) -> list[dict]— returns list of{x, y, resource, amount}for all tiles with resources in radiusis_passable(x, y) -> bool
Item dataclass fields:
id: str— snake_case unique identifiername: strdescription: strcategory: str— one of:food,tool,material,fuel,clothing,medicine,weapon,building_materialis_edible: bool = Falsenutrition: float = 0.0— hunger restored when eatenhydration: float = 0.0— thirst restored when eatenwarmth_bonus: float = 0.0— warmth restored when eatenis_tool: bool = Falsetool_type: Optional[str] = Noneis_fuel: bool = Falseburn_duration_ticks: int = 0is_wearable: bool = Falsewarmth_rating: float = 0.0weight: float = 1.0discovered_by: Optional[str] = Nonediscovered_on_day: int = 0to_dict(self) -> dictmethod — returns all fields as a dictionary
BASE_ITEMS dict — contains these items at minimum:
rock, stick, wood_log, raw_meat, cooked_meat, berries, water_cupped, fur, bone, flint, grass_bundle, mud, clay, crude_knife
BASE_RECIPES dict — "ingredient1+ingredient2" → "result_item_id":
"rock+rock"→"flint""stick+flint"→"crude_knife""raw_meat+fire"→"cooked_meat"(special: requires fire nearby, handled in systems/crafting.py)"mud+grass_bundle"→"mud_brick""wood_log+flint"→"wood_plank""fur+grass_bundle"→"basic_blanket"
FIRE_STARTER_COMBOS list — combinations that start a fire:
["stick", "stick"]via rubbing["flint", "rock"]via striking
Character class — represents one human in the world.
Fields:
name: str,sex: str,x: int,y: intage_years: int = 18,age_days: int = 0is_alive: bool = Trueis_pregnant: bool = False,pregnancy_days: int = 0- Needs:
hunger,thirst,energy,warmth,social— all float, initialized at 70% of max inventory: list[dict]— each entry:{"item_id": str, "quantity": int}equipped: dict[str, str]— slot → item_idskills: dict[str, Skill]— keys:foraging,hunting,crafting,building,farming,medicine,cooking,social,fire_makingknown_recipes: list[str]— recipe keys this character has discoveredknown_facts: list[str]— things this character knows ("fire cooks meat")memory_log: list[str]— last 20 events, capped withMAX_MEMORY = 20relationships: list[Relationship]current_action: str = "idle"last_thought: str = ""mood: str = "neutral"
Skill dataclass: name: str, level: float = 0.0, xp: float = 0.0
Relationship dataclass: character_name: str, bond_type: str, affection: float = 50.0
Methods:
decay_needs(is_night, is_winter, near_fire)— applies per-tick decay using config constants. Night without sleep drains energy faster. Winter without fire drains warmth faster. Being near fire slowly restores warmth.critical_need() -> Optional[str]— returns name of the most urgent need below critical threshold, or Noneis_in_danger() -> bool— True if any need is below critical thresholdhas_item(item_id, qty=1) -> booladd_item(item_id, quantity=1)— stacks if item_id already in inventoryremove_item(item_id, quantity=1) -> bool— returns False if not enoughremember(event: str)— appends to memory_log, evicts oldest if over MAX_MEMORYgain_skill_xp(skill_name, xp)— adds xp, levels up when threshold reached (level * 10 xp per level)get_state_summary(world_context="") -> str— formats all relevant state into a compact string for LLM consumption. Includes: name, sex, age, mood, needs (only ones below 60), inventory, known_recipes (first 10), top skills (only those above level 0), recent memory (last 5), current_action, world_context.
WorldDatabase class wrapping SQLite.
Tables:
discoveries— (id, item_id, item_json, recipe_key, discovered_by, sim_day, sim_year, created_at)world_log— (id, sim_day, sim_year, sim_hour, event_type, description, character, created_at)known_recipes— (recipe_key PRIMARY KEY, result_item, discovered_by, sim_year)character_snapshots— (id, name, snapshot_json, sim_year, sim_day, created_at)
Methods:
__init__(db_path="data/world.db")— createsdata/dir if missing, creates all tableslog_event(event_type, description, character, time_engine)— inserts into world_loglog_discovery(item, recipe_key, discovered_by, time_engine)— inserts into discoveriessave_recipe(recipe_key, result_item, discovered_by, sim_year)— INSERT OR IGNORE into known_recipesget_all_recipes() -> dict— returns all known_recipes as {recipe_key: result_item}get_all_discoveries() -> list[dict]— returns all discovered items as list of dictsclose()— commits and closes connection
Single async function:
async def get_character_decision(character, world_context: str, nearby_chars: list) -> dictSystem prompt instructs the LLM:
- You are the raw instinct and thought of a prehistoric human. No modern concepts. No knowledge of writing, metal, electricity.
- Respond ONLY with valid JSON, no markdown, no explanation.
- JSON shape:
{"action": str, "thought": str, "emotion": str, "target_x": int|null, "target_y": int|null} - Action must be one of this exact list:
gather_woodgather_stonegather_berriesdrink_watereat <item_id>(e.g.eat berries)sleeprestexploremake_firecraft <result> from <item1>+<item2>(e.g.craft crude_knife from stick+flint)talk_to <name>mate_with <name>move_to <x> <y>
- Prioritize survival: thirst > hunger > warmth > energy > social
- Use your memory — if you know fire cooks meat, use that knowledge
- Be drawn to your partner for social and reproductive needs
- Explore only when all needs are satisfied
User message is the character's get_state_summary() output plus nearby characters summary.
Fallback: If the API call fails or JSON is malformed, return a survival-instinct fallback based on character.critical_need(). Never propagate the exception — always return a valid decision dict.
Rate limit header: Include "HTTP-Referer": "https://civilizationsim.local" and "X-Title": "CivilizationSim" in request headers.
Single async function:
async def evaluate_crafting_combination(
item1_id: str,
item2_id: str,
known_item_ids: list[str],
character_name: str
) -> dictSystem prompt instructs the LLM:
- You are a prehistoric technology logic engine. Decide what combining two items produces.
- Respond ONLY with valid JSON, no markdown, no preamble.
- JSON shape must include:
success,result_item_id,result_item_name,result_description,category,is_edible,nutrition,hydration,is_tool,tool_type,is_fuel,burn_duration_ticks,is_wearable,warmth_rating,weight,fail_reason - Only accept combinations that make physical sense in prehistoric times. No metal smelting, no electricity, no reading/writing.
- If the combination makes no sense:
success: false, explain infail_reason - Be inventive but grounded: fire + clay = fired_pot, bone + stick = bone_spear, hide + plant_fiber = crude_clothing
Fallback on error: Return {"success": False, "fail_reason": "Agent unavailable"} — never raise.
Four functions, no classes:
def process_eat(character, item_id: str, item_registry: dict) -> strValidates item exists and is edible, removes one from inventory, applies nutrition/hydration/warmth_bonus, gains cooking XP, returns result message.
def process_drink(character, world_map) -> strChecks current tile and all 4 adjacent tiles for water. If found, restores thirst by 50. Returns result message.
def process_sleep(character) -> strRestores energy by 2.0 per call, caps at MAX_ENERGY. Returns result message.
def check_survival(character, time_engine, db) -> boolReturns False (and sets character.is_alive = False, logs to db) if hunger ≤ 0, thirst ≤ 0, or age > 65 with energy < 5. Returns True if alive.
Module-level registries (mutable dicts, initialized from entities/item.py):
ITEM_REGISTRY: dict[str, Item] # starts as copy of BASE_ITEMS
RECIPE_REGISTRY: dict[str, str] # starts as copy of BASE_RECIPESdef make_recipe_key(item1_id: str, item2_id: str) -> strReturns "+".join(sorted([item1_id, item2_id])) — order-independent canonical key.
def try_craft_known(character, item1_id: str, item2_id: str) -> dictChecks RECIPE_REGISTRY for this combination. If found and character has both items, removes them, adds result, gains crafting XP. Returns {"success": bool, "result_item_id": str, "message": str, "new_discovery": False}.
async def try_craft_with_agent(character, item1_id: str, item2_id: str, db, time_engine) -> dict- Check RECIPE_REGISTRY first — if known, call
try_craft_knownand return. - Check character has both items.
- Call
crafting_agent.evaluate_crafting_combination(...)withlist(ITEM_REGISTRY.keys()). - If success: create new
Itemfrom response fields, add to ITEM_REGISTRY, add recipe to RECIPE_REGISTRY, remove items from character, add result, save to db, return withnew_discovery: True. - If fail: return with
success: Falseand the fail reason.
Special case — fire starting:
def try_start_fire(character, fires: list, world_map) -> dictChecks if character has the right items (stick+stick or flint+rock). If yes, removes them, appends a new fire dict {"x": character.x, "y": character.y, "fuel_ticks": 500} to the fires list, adds fire_making XP, adds fact "fire cooks meat and provides warmth" to character.known_facts if not already there. Returns result dict.
def process_talk(char_a: Character, char_b: Character, time_engine, db) -> strBoth characters gain +15 social (capped at MAX_SOCIAL). Both gain social skill XP. Affection between them increases by 2.0. char_a shares up to 3 known_recipes with char_b that char_b doesn't have. Returns descriptive message including what was shared if anything.
def try_reproduce(char_a: Character, char_b: Character, time_engine, db) -> dictValidates: different sexes, female not already pregnant, female age 14-45, both have hunger > 30 and energy > 30. Checks relationship affection (higher affection = higher chance, max 40% base chance). On success: sets female.is_pregnant = True, logs event to db. Returns {"success": bool, "message": str}.
def process_pregnancy(female: Character, time_engine, db) -> dictAdvances pregnancy_days by 1. On reaching pregnancy_days_total = 280: resets pregnancy state, creates a new Character object (baby) at female's location with age 0, adds parent-child relationship to female's relationships, logs birth to db. Returns {} normally, {"birth": True, "baby": Character, "message": str} on birth.
Helper functions (private, prefixed _):
_get_relationship(char, other_name) -> Optional[Relationship]_update_relationship(char, other_name, delta)— creates relationship if not exists
def render_tiles(surface, world_map, camera_x: int, camera_y: int)Calculates visible tile range from camera position. Iterates only visible tiles. Draws each tile as a filled rect using color from a local TILE_COLORS dict. Draws a 3px colored dot on tiles with remaining resources (berries=red, wood=brown, stone=gray). No other dependencies.
def render_characters(surface, characters: list, camera_x: int, camera_y: int, selected_char, font)For each living character in view: draws a circle (blue for male, pink for female, green for children under 12). Draws a selection ring around selected_char. Draws character name above. Draws last_thought as a speech bubble text above name (truncated at 35 chars).
def render_fires(surface, fires: list, camera_x: int, camera_y: int)For each fire: draws three concentric circles (dark orange, orange, yellow) to simulate flame.
Module-level state:
_event_log: list[str] = []
_MAX_LOG_LINES = 15def add_event(message: str)Appends to _event_log. Trims to last _MAX_LOG_LINES * 3 entries.
def render_character_panel(surface, character, font_small, font_normal)Right-side panel (width=280px). Shows: name/sex/age, current action, mood, five need bars (hunger/thirst/energy/warmth/social), inventory (first 8 items), skills (only those above level 0). Panel background is semi-transparent dark rect.
def render_time_bar(surface, time_engine, font_normal, font_small)Top bar showing time_engine.describe() and day/night indicator.
def render_event_log(surface, font_small)Bottom panel showing last _MAX_LOG_LINES events. Most recent line is full brightness, older lines are dimmer.
def render_discovery_popup(surface, message: str, font_normal, ticks_remaining: int)Centered popup with amber background. Fades out as ticks_remaining decreases. Only renders if ticks_remaining > 0.
All panels use pygame.SRCALPHA surfaces for transparency. Use config color constants throughout.
The entry point and game loop. Responsibilities:
Initialization:
pygame.init()- Create screen, clock, fonts (monospace, sizes from config)
- Create
WorldMap,TimeEngine,WorldDatabase - Spawn starting characters from
STARTING_CHARACTERSconfig, each starting with: rock x2, stick x3, berries x4 - Set
selected_char = characters[0] - Camera starts centered on first character
Camera: Simple x, y integers. WASD/arrow keys pan at 4px/frame. F key centers on selected character. Click on character to select them. TAB cycles through living characters.
Game loop (every frame):
- Process pygame events (quit, keydown, mouseclick)
- Handle camera pan from held keys
- Simulation tick (runs at TICK_RATE via a tick accumulator, not every frame):
time_engine.tick()- Age all characters (1 day per TICK_RATE ticks)
- Update fires (decrement fuel, remove dead fires)
- For each living character:
character.decay_needs(time_engine.is_night, time_engine.is_winter, near_fire)check_survival(character, time_engine, db)— remove dead charactersprocess_pregnancy(character, time_engine, db)if pregnant — add new babies to characters list- Increment character's think counter. If counter >= CHARACTER_THINK_INTERVAL_TICKS:
- Reset counter
- Call
asyncio.get_event_loop().run_until_complete(get_character_decision(...)) - Call
asyncio.get_event_loop().run_until_complete(execute_action(...))
- Render (every frame):
screen.fill((10, 10, 15))render_tiles(screen, world_map, camera.x, camera.y)render_fires(screen, fires, camera.x, camera.y)render_characters(screen, characters, camera.x, camera.y, selected_char, font_small)render_time_bar(screen, time_engine, font_normal, font_small)render_character_panel(screen, selected_char, font_small, font_normal)render_event_log(screen, font_small)render_discovery_popup(screen, popup_message, font_normal, popup_ticks)if popup_ticks > 0- Controls hint text at bottom
pygame.display.flip()clock.tick(FPS)
execute_action function (async, in main.py):
Receives a character and their LLM decision dict. Parses the action string and routes to the correct system function. Handles all action types:
gather_wood/stone/berries— scans nearby tiles, harvests, adds to inventorydrink_water→needs.process_drink()eat <item>→needs.process_eat()sleep→needs.process_sleep()make_fire→crafting.try_start_fire()craft <x> from <a>+<b>— parses item names, callscrafting.try_craft_with_agent()talk_to <name>— finds character by name, callssocial.process_talk()mate_with <name>— finds character by name, callssocial.try_reproduce()explore/move_to <x> <y>— moves character one tile toward target, checks passabilityrest— small energy restore- Unknown actions — log as-is, no crash
Every executed action calls character.remember(msg) and hud.add_event(...). Discovery popups set popup_message and popup_ticks = 180.
On quit: Call db.close(), pygame.quit(), sys.exit(0).
- LLM model:
deepseek/deepseek-chat-v3-0324:free - API endpoint:
https://openrouter.ai/api/v1/chat/completions - World dimensions: 80×60 tiles
- Starting characters: Kael (male) and Lyra (female)
- Tile size: 32px
- Screen: 1280×800
Follow this exact order. Do not skip steps. Do not reorder.
1. pip install pygame httpx python-dotenv
2. Create project directory: civilization_sim/
3. Create .env file with OPENROUTER_API_KEY
4. Create all __init__.py files (engine, entities, agents, systems, renderer)
5. Write config.py — complete, all constants present
6. Write engine/time_engine.py — complete and tested mentally
7. Write engine/world.py — complete, generation works
8. Write entities/item.py — all BASE_ITEMS and BASE_RECIPES defined
9. Write entities/character.py — all methods implemented
10. Write agents/memory.py — all tables created, all methods work
11. Write agents/character_agent.py — async function, fallback implemented
12. Write agents/crafting_agent.py — async function, fallback implemented
13. Write systems/needs.py — all four functions implemented
14. Write systems/crafting.py — registries initialized, all functions implemented
15. Write systems/social.py — all functions implemented
16. Write renderer/tile_renderer.py — renders only visible tiles
17. Write renderer/entity_renderer.py — characters and fires rendered
18. Write renderer/hud.py — all render functions and add_event implemented
19. Write main.py — complete game loop, execute_action, all imports correct
20. Verify: run python main.py — fix any import errors or runtime errors before stopping
Before you declare the project done, confirm every item:
-
python main.pyopens a 1280×800 Pygame window with no errors - World map renders with grass, water, trees visible
- Two characters (Kael and Lyra) appear as colored circles on the map
- Characters move and change actions every ~3 real seconds
- Event log at the bottom shows live character decisions
- Selected character panel on the right shows correct need bars and inventory
- Time bar at the top shows advancing sim time
- TAB switches between characters
- WASD pans the camera
- F key centers camera on selected character
- LLM calls succeed (check terminal for errors — there should be none)
- Fire can be started (character with 2 sticks decides to make_fire)
- Characters eat berries and hunger bar rises
- Game runs for 5 real minutes without crashing
- No
# TODOcomments anywhere in the codebase - No unused imports anywhere
- No hardcoded numbers outside config.py
Good:
def process_eat(character: Character, item_id: str, item_registry: dict) -> str:
item = item_registry.get(item_id)
if not item:
return f"{character.name} tries to eat {item_id} but it doesn't exist."
if not item.is_edible:
return f"{character.name} tries to eat {item.name} but it isn't food."
if not character.remove_item(item_id):
return f"{character.name} has no {item.name} to eat."
character.hunger = min(MAX_HUNGER, character.hunger + item.nutrition)
character.thirst = min(MAX_THIRST, character.thirst + item.hydration)
character.warmth = min(MAX_WARMTH, character.warmth + item.warmth_bonus)
character.gain_skill_xp("cooking", 1.0)
return f"{character.name} eats {item.name}. Hunger: {character.hunger:.0f}/100"Bad (never write this):
def process_eat(character, item_id):
# TODO: implement eating logic
passdef process_eat(character, item_id):
print("eating") # placeholder
character.hunger += 10 # magic numberRead every specification above. Internalize the architecture. Then build it, file by file, in the order listed. Every file you write must be production-quality code that works correctly on the first run.
The definition of done: python main.py runs, shows a living world, and a senior engineer reading the source code cannot find a single line they would rewrite for quality reasons.
Begin.