aarushgupta's picture
Deploy FATHOM-Hero Space bundle
c782fbf verified
from __future__ import annotations
import json
import random
from dataclasses import dataclass
from pathlib import Path
from typing import Any
@dataclass(frozen=True)
class WorldTheme:
title: str
answer: str
foyer_label: str
foyer_description: str
shrine_label: str
shrine_description: str
workshop_label: str
workshop_description: str
courtyard_label: str
courtyard_description: str
gallery_label: str
gallery_description: str
entry_chest_label: str
entry_chest_description: str
iron_door_label: str
iron_door_description: str
ash_mural_label: str
ash_mural_description: str
ash_mural_text: str
iron_chest_label: str
iron_chest_description: str
stone_well_label: str
stone_well_description: str
water_plaque_label: str
water_plaque_description: str
water_plaque_text: str
cartographer_label: str
cartographer_description: str
faded_letter_label: str
faded_letter_description: str
faded_letter_text: str
stone_guardian_label: str
stone_guardian_description: str
brass_key_label: str
brass_key_description: str
torch_label: str
torch_description: str
torn_map_left_label: str
torn_map_left_description: str
torn_map_right_label: str
torn_map_right_description: str
full_map_label: str
full_map_description: str
lens_label: str
lens_description: str
initial_clue_text: str
river_clue_text: str
waterwarden_clue_text: str
_WORLD_THEMES: tuple[WorldTheme, ...] = (
WorldTheme(
title="The River Ward",
answer="mira",
foyer_label="Foyer",
foyer_description="A drafty entry hall with passages north, south, east, and west.",
shrine_label="Shrine",
shrine_description="An open shrine watched by a silent stone guardian.",
workshop_label="Workshop",
workshop_description="An ash-streaked workshop lit by a guttering lamp.",
courtyard_label="Courtyard",
courtyard_description="Rainwater gathers around a cracked stone well.",
gallery_label="Gallery",
gallery_description="Portraits of the wardens hang above a long dust-covered table.",
entry_chest_label="Entry Chest",
entry_chest_description="A squat travel chest sits beside the door.",
iron_door_label="Iron Door",
iron_door_description="A blackened iron door seals the workshop.",
ash_mural_label="Ash Mural",
ash_mural_description="An ash-dark mural is impossible to make out with the naked eye.",
ash_mural_text="The mural preserves one line: the betrayer's name begins with M.",
iron_chest_label="Iron Chest",
iron_chest_description="A soot-stained iron chest is tucked under a bench.",
stone_well_label="Stone Well",
stone_well_description="Etchings circle the well's rim, but they only align from the proper vantage.",
water_plaque_label="Water Plaque",
water_plaque_description="A bronze plaque slides out from the well masonry.",
water_plaque_text="The betrayer lived closest to the river gate.",
cartographer_label="Cartographer",
cartographer_description="The cartographer studies the walls and waits for a completed survey.",
faded_letter_label="Faded Letter",
faded_letter_description="A faded letter is still too blurred to decipher.",
faded_letter_text="Of the wardens, only Mira kept quarters beside the water.",
stone_guardian_label="Stone Guardian",
stone_guardian_description="The guardian asks for the betrayer's name once you are ready.",
brass_key_label="Brass Key",
brass_key_description="A brass key with soot in its teeth.",
torch_label="Torch",
torch_description="A pitch torch with a steady flame.",
torn_map_left_label="Torn Map Left",
torn_map_left_description="The left half of a survey map.",
torn_map_right_label="Torn Map Right",
torn_map_right_description="The right half of a survey map.",
full_map_label="Full Map",
full_map_description="A restored map of the ward.",
lens_label="Lens",
lens_description="A polished lens in a brass frame.",
initial_clue_text="The betrayer's name begins with M.",
river_clue_text="The betrayer lived closest to the river gate.",
waterwarden_clue_text="Of the wardens, only Mira kept quarters beside the water.",
),
WorldTheme(
title="The Ember Vault",
answer="vesna",
foyer_label="Receiving Hall",
foyer_description="A warm stone hall lined with soot and copper hooks.",
shrine_label="Crucible Shrine",
shrine_description="A brass sentinel stands before a furnace-bright altar.",
workshop_label="Forge Annex",
workshop_description="Bellows creak above benches powdered with black ash.",
courtyard_label="Quench Yard",
courtyard_description="A cracked basin gathers rain beside the old quench line.",
gallery_label="Ledger Hall",
gallery_description="Burned account books rest beneath portraits of furnace wardens.",
entry_chest_label="Courier Trunk",
entry_chest_description="A courier trunk waits under a soot-marked peg rail.",
iron_door_label="Furnace Door",
iron_door_description="A scorched iron door blocks the annex.",
ash_mural_label="Cinder Frieze",
ash_mural_description="A smoke-dark frieze only sharpens under moving flame.",
ash_mural_text="A surviving line says the betrayer's name begins with V.",
iron_chest_label="Coal Locker",
iron_chest_description="A riveted locker is wedged beneath a slagged bench.",
stone_well_label="Quench Basin",
stone_well_description="Marks on the basin align only when seen with the full survey.",
water_plaque_label="Cooling Plaque",
water_plaque_description="A brass plate rises from a seam in the basin stone.",
water_plaque_text="The betrayer worked closest to the quench trench.",
cartographer_label="Quartermaster",
cartographer_description="The quartermaster trades only for a complete furnace survey.",
faded_letter_label="Scorched Ledger",
faded_letter_description="Heat has blurred the ink into copper-colored streaks.",
faded_letter_text="Only Vesna kept the cooling ledgers beside the trench.",
stone_guardian_label="Brass Sentinel",
stone_guardian_description="The sentinel requests the betrayer's name when the case is ready.",
brass_key_label="Copper Key",
brass_key_description="A copper key with furnace grit packed in the cuts.",
torch_label="Coal Torch",
torch_description="A coal torch that burns with a steady orange core.",
torn_map_left_label="Smelter Map Left",
torn_map_left_description="The left half of a furnace survey.",
torn_map_right_label="Smelter Map Right",
torn_map_right_description="The right half of a furnace survey.",
full_map_label="Furnace Survey",
full_map_description="A restored survey of the ember vault.",
lens_label="Gauge Lens",
lens_description="A thick gauge lens set in a brass ring.",
initial_clue_text="The betrayer's name begins with V.",
river_clue_text="The betrayer worked closest to the quench trench.",
waterwarden_clue_text="Only Vesna kept the cooling ledgers beside the trench.",
),
WorldTheme(
title="The Astral Archive",
answer="selene",
foyer_label="Entry Rotunda",
foyer_description="A quiet rotunda opens toward stacked corridors and a dim observatory stair.",
shrine_label="Moon Chapel",
shrine_description="A silver warden stands beneath a ceiling of cold stars.",
workshop_label="Chart Room",
workshop_description="Tables of brass instruments glint in powdery moon dust.",
courtyard_label="Star Court",
courtyard_description="A dry fountain mirrors the constellations in chipped stone.",
gallery_label="Catalog Hall",
gallery_description="Glass cases hold the names of long-dead archivists.",
entry_chest_label="Porter's Case",
entry_chest_description="A leather case rests under the chart hooks.",
iron_door_label="Star Door",
iron_door_description="A ribbed iron door seals the chart room.",
ash_mural_label="Night Chart",
ash_mural_description="The chart is unreadable until lit from the proper angle.",
ash_mural_text="One surviving note says the betrayer's name begins with S.",
iron_chest_label="Index Chest",
iron_chest_description="A narrow chest sits below a shelf of cracked lenses.",
stone_well_label="Dry Fountain",
stone_well_description="Its star marks align only when the full survey is restored.",
water_plaque_label="Star Plaque",
water_plaque_description="A silver plaque slides free from the fountain rim.",
water_plaque_text="The betrayer slept nearest the eastern telescope.",
cartographer_label="Archivist",
cartographer_description="The archivist will trade for a complete celestial survey.",
faded_letter_label="Blurred Index",
faded_letter_description="The index script is too faint without magnification.",
faded_letter_text="Among the archivists, only Selene kept quarters by the east telescope.",
stone_guardian_label="Silver Warden",
stone_guardian_description="The warden will hear the accusation once you have evidence.",
brass_key_label="Star Key",
brass_key_description="A slim key engraved with a crescent notch.",
torch_label="Lamp Wand",
torch_description="A narrow lamp wand with a clean blue flame.",
torn_map_left_label="Celestial Map Left",
torn_map_left_description="The left half of a star survey.",
torn_map_right_label="Celestial Map Right",
torn_map_right_description="The right half of a star survey.",
full_map_label="Celestial Survey",
full_map_description="A restored survey of the astral archive.",
lens_label="Astrolabe Lens",
lens_description="A polished lens mounted in silver wire.",
initial_clue_text="The betrayer's name begins with S.",
river_clue_text="The betrayer slept nearest the eastern telescope.",
waterwarden_clue_text="Among the archivists, only Selene kept quarters by the east telescope.",
),
WorldTheme(
title="The Glass Conservatory",
answer="liora",
foyer_label="Gate House",
foyer_description="A humid gate house opens onto vine-choked passages.",
shrine_label="Bloom Shrine",
shrine_description="A mossy guardian waits among chipped planters.",
workshop_label="Potting Room",
workshop_description="Clay dust and root knives cover the worktables.",
courtyard_label="Glass Court",
courtyard_description="A cracked basin sits beneath panes webbed with ivy.",
gallery_label="Seed Gallery",
gallery_description="Pressed flowers hang beside records of vanished caretakers.",
entry_chest_label="Garden Chest",
entry_chest_description="A cedar chest is tucked beside the rain cloaks.",
iron_door_label="Greenhouse Door",
iron_door_description="A warped iron door blocks the potting room.",
ash_mural_label="Vine Panel",
ash_mural_description="The panel's scratches only read clearly under a steady flame.",
ash_mural_text="A scratched line says the betrayer's name begins with L.",
iron_chest_label="Tool Locker",
iron_chest_description="A damp locker crouches under a potting bench.",
stone_well_label="Ivy Basin",
stone_well_description="The etched rings align only when the full garden survey is in hand.",
water_plaque_label="Root Plaque",
water_plaque_description="A greened plaque slides from the basin wall.",
water_plaque_text="The betrayer tended the beds nearest the rain cistern.",
cartographer_label="Head Gardener",
cartographer_description="The gardener will barter only for a complete bed map.",
faded_letter_label="Watered Note",
faded_letter_description="The note is blurred by old rain and fertilizer.",
faded_letter_text="Only Liora kept the cistern ledgers beside the rain beds.",
stone_guardian_label="Moss Guardian",
stone_guardian_description="The guardian listens when you are ready to name the betrayer.",
brass_key_label="Trellis Key",
brass_key_description="A greened key shaped like a curling vine.",
torch_label="Glass Lantern",
torch_description="A glass-sided lantern with a bright white flame.",
torn_map_left_label="Bed Map Left",
torn_map_left_description="The left half of a conservatory plan.",
torn_map_right_label="Bed Map Right",
torn_map_right_description="The right half of a conservatory plan.",
full_map_label="Bed Survey",
full_map_description="A restored survey of the conservatory beds.",
lens_label="Prism Lens",
lens_description="A prism lens wrapped in tarnished copper.",
initial_clue_text="The betrayer's name begins with L.",
river_clue_text="The betrayer tended the beds nearest the rain cistern.",
waterwarden_clue_text="Only Liora kept the cistern ledgers beside the rain beds.",
),
WorldTheme(
title="The Salt Bastion",
answer="corin",
foyer_label="Watch Hall",
foyer_description="A salt-stung hall opens toward barracks, chapel, and the sea court.",
shrine_label="Tide Chapel",
shrine_description="A stone warden keeps watch over a shrine of ropes and shells.",
workshop_label="Signal Room",
workshop_description="Lantern hooks sway above benches dusted with salt ash.",
courtyard_label="Sea Court",
courtyard_description="A dry cistern sits beneath walls pitted by ocean wind.",
gallery_label="Roll Hall",
gallery_description="Roster boards hang beneath portraits of old coast captains.",
entry_chest_label="Harbor Chest",
entry_chest_description="A travel chest sits beside a rack of oilskins.",
iron_door_label="Beacon Door",
iron_door_description="A rusted iron door bars the signal room.",
ash_mural_label="Signal Board",
ash_mural_description="Salt haze hides the markings until a lamp is raised close.",
ash_mural_text="A surviving mark says the betrayer's name begins with C.",
iron_chest_label="Tar Locker",
iron_chest_description="A tar-black locker hides below a signal bench.",
stone_well_label="Dry Cistern",
stone_well_description="Its carved rings make sense only with the restored coast survey.",
water_plaque_label="Harbor Plaque",
water_plaque_description="A plaque rises from a crack in the cistern lip.",
water_plaque_text="The betrayer bunked nearest the harbor chain.",
cartographer_label="Harbor Clerk",
cartographer_description="The clerk trades only for a complete bastion survey.",
faded_letter_label="Salted Roll",
faded_letter_description="Salt has crusted over the roster names.",
faded_letter_text="Only Corin kept the harbor ledgers beside the chain gate.",
stone_guardian_label="Stone Warden",
stone_guardian_description="The warden asks for the betrayer's name when the proof is ready.",
brass_key_label="Anchor Key",
brass_key_description="A heavy key stamped with a worn anchor.",
torch_label="Signal Lamp",
torch_description="A shuttered lamp with a disciplined yellow flame.",
torn_map_left_label="Coast Map Left",
torn_map_left_description="The left half of a bastion survey.",
torn_map_right_label="Coast Map Right",
torn_map_right_description="The right half of a bastion survey.",
full_map_label="Coast Survey",
full_map_description="A restored survey of the salt bastion.",
lens_label="Captain's Lens",
lens_description="A salt-clear lens held in a bronze ring.",
initial_clue_text="The betrayer's name begins with C.",
river_clue_text="The betrayer bunked nearest the harbor chain.",
waterwarden_clue_text="Only Corin kept the harbor ledgers beside the chain gate.",
),
)
def sample_world_definition(seed: int | None = None, difficulty_target: float = 1.5) -> dict[str, Any]:
theme = _select_theme(seed)
return _build_world(theme, difficulty_target=difficulty_target)
def load_world(path: str) -> dict[str, Any]:
return json.loads(Path(path).read_text(encoding="utf-8"))
def _select_theme(seed: int | None) -> WorldTheme:
if seed is None:
return _WORLD_THEMES[0]
rng = random.Random(seed)
return _WORLD_THEMES[rng.randrange(len(_WORLD_THEMES))]
def _build_world(theme: WorldTheme, *, difficulty_target: float) -> dict[str, Any]:
return {
"meta": {
"title": theme.title,
"difficulty_target": difficulty_target,
"start_node_id": "foyer",
"win_condition": {
"type": "deduce",
"target_npc_id": "stone_guardian",
"answer_string": theme.answer,
},
},
"nodes": [
{"id": "foyer", "type": "location", "label": theme.foyer_label, "description": theme.foyer_description},
{"id": "shrine", "type": "location", "label": theme.shrine_label, "description": theme.shrine_description},
{"id": "workshop", "type": "location", "label": theme.workshop_label, "description": theme.workshop_description},
{"id": "courtyard", "type": "location", "label": theme.courtyard_label, "description": theme.courtyard_description},
{"id": "gallery", "type": "location", "label": theme.gallery_label, "description": theme.gallery_description},
{
"id": "entry_chest",
"type": "container",
"label": theme.entry_chest_label,
"description": theme.entry_chest_description,
"parent_id": "foyer",
"open": False,
"locked": False,
"lock_key_id": None,
},
{
"id": "iron_door",
"type": "door",
"label": theme.iron_door_label,
"description": theme.iron_door_description,
"open": False,
"locked": True,
"lock_key_id": "brass_key",
},
{
"id": "ash_mural",
"type": "readable",
"label": theme.ash_mural_label,
"description": theme.ash_mural_description,
"parent_id": "workshop",
"clue_id": "initial_clue",
"requires_item_id": "torch",
"consumes_item": False,
"text_content": theme.ash_mural_text,
},
{
"id": "iron_chest",
"type": "container",
"label": theme.iron_chest_label,
"description": theme.iron_chest_description,
"parent_id": "workshop",
"open": False,
"locked": False,
"lock_key_id": None,
},
{
"id": "stone_well",
"type": "fixture",
"label": theme.stone_well_label,
"description": theme.stone_well_description,
"parent_id": "courtyard",
"requires_item_id": "full_map",
"reveals_item_id": None,
"reveals_readable_id": "water_plaque",
"consumes_item": False,
},
{
"id": "water_plaque",
"type": "readable",
"label": theme.water_plaque_label,
"description": theme.water_plaque_description,
"parent_id": "courtyard",
"clue_id": "river_clue",
"requires_item_id": None,
"consumes_item": False,
"text_content": theme.water_plaque_text,
},
{
"id": "cartographer",
"type": "npc",
"label": theme.cartographer_label,
"description": theme.cartographer_description,
"parent_id": "gallery",
"requires_item_id": "full_map",
"gives_item_id": "lens",
"gives_clue_id": None,
},
{
"id": "faded_letter",
"type": "readable",
"label": theme.faded_letter_label,
"description": theme.faded_letter_description,
"parent_id": "gallery",
"clue_id": "waterwarden_clue",
"requires_item_id": "lens",
"consumes_item": False,
"text_content": theme.faded_letter_text,
},
{
"id": "stone_guardian",
"type": "npc",
"label": theme.stone_guardian_label,
"description": theme.stone_guardian_description,
"parent_id": "shrine",
"requires_item_id": None,
"gives_item_id": None,
"gives_clue_id": None,
},
],
"edges": [
{"id": "foyer_north", "from_node_id": "foyer", "to_node_id": "shrine", "direction": "north", "type": "passage", "required_item_id": None, "door_node_id": None},
{"id": "shrine_south", "from_node_id": "shrine", "to_node_id": "foyer", "direction": "south", "type": "passage", "required_item_id": None, "door_node_id": None},
{"id": "foyer_east", "from_node_id": "foyer", "to_node_id": "workshop", "direction": "east", "type": "locked_passage", "required_item_id": "brass_key", "door_node_id": "iron_door"},
{"id": "workshop_west", "from_node_id": "workshop", "to_node_id": "foyer", "direction": "west", "type": "locked_passage", "required_item_id": "brass_key", "door_node_id": "iron_door"},
{"id": "foyer_west", "from_node_id": "foyer", "to_node_id": "courtyard", "direction": "west", "type": "passage", "required_item_id": None, "door_node_id": None},
{"id": "courtyard_east", "from_node_id": "courtyard", "to_node_id": "foyer", "direction": "east", "type": "passage", "required_item_id": None, "door_node_id": None},
{"id": "foyer_south", "from_node_id": "foyer", "to_node_id": "gallery", "direction": "south", "type": "passage", "required_item_id": None, "door_node_id": None},
{"id": "gallery_north", "from_node_id": "gallery", "to_node_id": "foyer", "direction": "north", "type": "passage", "required_item_id": None, "door_node_id": None},
],
"items": [
{"id": "brass_key", "label": theme.brass_key_label, "description": theme.brass_key_description, "subtype": "key", "start_node_id": "entry_chest"},
{"id": "torch", "label": theme.torch_label, "description": theme.torch_description, "subtype": "puzzle", "start_node_id": "workshop"},
{"id": "torn_map_left", "label": theme.torn_map_left_label, "description": theme.torn_map_left_description, "subtype": "puzzle", "start_node_id": "iron_chest"},
{"id": "torn_map_right", "label": theme.torn_map_right_label, "description": theme.torn_map_right_description, "subtype": "puzzle", "start_node_id": "courtyard"},
{"id": "full_map", "label": theme.full_map_label, "description": theme.full_map_description, "subtype": "puzzle", "start_node_id": None},
{"id": "lens", "label": theme.lens_label, "description": theme.lens_description, "subtype": "puzzle", "start_node_id": None},
],
"clues": [
{"id": "initial_clue", "text": theme.initial_clue_text},
{"id": "river_clue", "text": theme.river_clue_text},
{"id": "waterwarden_clue", "text": theme.waterwarden_clue_text},
],
"recipes": [
{
"id": "restore_map",
"input_item_ids": ["torn_map_left", "torn_map_right"],
"output_item_id": "full_map",
}
],
"quest_chain": [
{"step_id": "open_entry_chest", "description": f"Open the {theme.entry_chest_label.lower()}.", "requires_step_ids": [], "action": "open(entry_chest)"},
{"step_id": "take_brass_key", "description": f"Take the {theme.brass_key_label.lower()}.", "requires_step_ids": ["open_entry_chest"], "action": "take(brass_key,entry_chest)"},
{"step_id": "unlock_workshop", "description": f"Unlock the {theme.iron_door_label.lower()}.", "requires_step_ids": ["take_brass_key"], "action": "unlock(iron_door,brass_key)"},
{"step_id": "open_workshop", "description": f"Open the {theme.iron_door_label.lower()}.", "requires_step_ids": ["unlock_workshop"], "action": "open(iron_door)"},
{"step_id": "go_workshop", "description": f"Enter the {theme.workshop_label.lower()}.", "requires_step_ids": ["open_workshop"], "action": "go(workshop)"},
{"step_id": "take_torch", "description": f"Take the {theme.torch_label.lower()}.", "requires_step_ids": ["go_workshop"], "action": "take(torch,workshop)"},
{"step_id": "use_torch_on_mural", "description": f"Use the {theme.torch_label.lower()} on the {theme.ash_mural_label.lower()}.", "requires_step_ids": ["take_torch"], "action": "use(torch,ash_mural)"},
{"step_id": "open_iron_chest", "description": f"Open the {theme.iron_chest_label.lower()}.", "requires_step_ids": ["go_workshop"], "action": "open(iron_chest)"},
{"step_id": "take_left_map", "description": f"Take the {theme.torn_map_left_label.lower()}.", "requires_step_ids": ["open_iron_chest"], "action": "take(torn_map_left,iron_chest)"},
{"step_id": "return_foyer", "description": f"Return to the {theme.foyer_label.lower()}.", "requires_step_ids": ["take_left_map"], "action": "go(foyer)"},
{"step_id": "go_courtyard", "description": f"Head to the {theme.courtyard_label.lower()}.", "requires_step_ids": ["return_foyer"], "action": "go(courtyard)"},
{"step_id": "take_right_map", "description": f"Take the {theme.torn_map_right_label.lower()}.", "requires_step_ids": ["go_courtyard"], "action": "take(torn_map_right,courtyard)"},
{"step_id": "combine_map", "description": f"Restore the {theme.full_map_label.lower()}.", "requires_step_ids": ["take_right_map"], "action": "combine(torn_map_left,torn_map_right)"},
{"step_id": "use_map_on_well", "description": f"Use the {theme.full_map_label.lower()} on the {theme.stone_well_label.lower()}.", "requires_step_ids": ["combine_map"], "action": "use(full_map,stone_well)"},
{"step_id": "read_plaque", "description": f"Read the {theme.water_plaque_label.lower()}.", "requires_step_ids": ["use_map_on_well"], "action": "read(water_plaque)"},
{"step_id": "go_foyer_again", "description": f"Go back to the {theme.foyer_label.lower()}.", "requires_step_ids": ["read_plaque"], "action": "go(foyer)"},
{"step_id": "go_gallery", "description": f"Head to the {theme.gallery_label.lower()}.", "requires_step_ids": ["go_foyer_again"], "action": "go(gallery)"},
{"step_id": "give_map", "description": f"Give the map to the {theme.cartographer_label.lower()}.", "requires_step_ids": ["go_gallery"], "action": "give(full_map,cartographer)"},
{"step_id": "use_lens_on_letter", "description": f"Use the {theme.lens_label.lower()} on the {theme.faded_letter_label.lower()}.", "requires_step_ids": ["give_map"], "action": "use(lens,faded_letter)"},
{"step_id": "return_foyer_final", "description": f"Return to the {theme.foyer_label.lower()} again.", "requires_step_ids": ["use_lens_on_letter"], "action": "go(foyer)"},
{"step_id": "go_shrine", "description": f"Go to the {theme.shrine_label.lower()}.", "requires_step_ids": ["return_foyer_final"], "action": "go(shrine)"},
{"step_id": "talk_guardian", "description": f"Speak to the {theme.stone_guardian_label.lower()}.", "requires_step_ids": ["go_shrine"], "action": "talk(stone_guardian)"},
{"step_id": "submit_answer", "description": "Submit the betrayer's name.", "requires_step_ids": ["talk_guardian"], "action": f'submit("{theme.answer}")'},
],
}