- Core engine: simulator, game mechanics, triggers (138 tests) - Fog-of-war per-player state tracking - Meeting flow: interrupt, discussion, voting, consolidation - Prompt assembler with strategy injection tiers - LLM client with fallbacks for models without JSON/system support - Prompt templates: action, discussion, voting, reflection - Full integration in main.py orchestrator - Verified working with free OpenRouter models (Gemma)
5.7 KiB
5.7 KiB
The Glass Box League — Main Game Design
Agent Architecture
Identity & Persona
- Format:
"You are {model}. {PERSONA}. {context}" - Persona is optional and toggleable
- Same model can have multiple personas
- Goal: emergent behavior first, spice second
Memory System
| Scratchpad | Persistence | Purpose |
|---|---|---|
plan.json |
Per-game | Current intentions, agent-controlled |
events.json |
Per-game | Curated game events worth remembering |
suspicions.json |
Per-game | Player reads, agent-maintained |
learned.json |
Cross-game | Core memory, enforced JSON schema |
meeting_scratch.json |
Per-meeting | Temp, erased after consolidation |
JSON is god. Enforced schema for structure, freeform for agent thoughts. JSON improves attention.
Prompt Structure
System Prompt (Core Memory)
- Model identity
- Persona (if set)
- Game rules + current map + settings
- Role briefing (crewmate/impostor)
- Strategy tips (toggleable injection levels)
- Meta-awareness (toggleable: subtle → direct → 4th wall)
- Output format instructions
- Learned lessons from
learned.json
User Prompt (Working Memory)
Order matters:
- You — role, location, status, cooldowns
- Recent history — accumulated vision from skipped ticks
- Vision — current snapshot
- Available actions — dynamic tool list
Tool System
Core Tools (Always Available)
MOVE(room_id | player_id)— walk or followWAIT()INTERACT(object_id)— tasks, panels, buttons, bodies, vents, cams
Impostor Only
KILL(player_id)SABOTAGE(system_id)FAKE_TASK(task_id)
Trigger Management (Optional)
CONFIGURE_TRIGGERS(config)— only if changing defaults
Dynamic Available Actions
{
"available_interactions": ["task_wires_cafe", "body_blue", "admin_table"],
"available_kills": ["green", "yellow"],
"available_sabotages": ["lights", "o2", "reactor", "comms"]
}
- Context-filtered by engine based on role + location
- Agent told "these are your actions this turn"
- Object IDs validated against engine to prevent glitches
Trigger System
Mandatory (Cannot Mute)
GAME_STARTDISCUSSION_STARTVOTE_STARTGAME_ENDSABOTAGE_CRITICAL
Standard (Mutable)
PLAYER_ENTERS_FOVPLAYER_EXITS_FOVBODY_IN_FOVOBJECT_IN_RANGE(every interactable)VENT_WITNESSEDKILL_WITNESSEDDESTINATION_REACHEDTASK_COMPLETESABOTAGE_START/SABOTAGE_ENDLIGHTS_OUT(panic tick)COOLDOWN_READY(kill, emergency)DEATH(special message, transition to ghost)
Optional (Opt-in)
EVERY_N_SECONDS(configurable)RANDOM_N_SECONDS(RNG toggleable)INTERSECTION(hallway/room boundaries)HALLWAY_WAYPOINT
Trigger Frequency
- Impostors: Every tick (more decision points)
- Crewmates: Event-driven + opt-in periodic
- Ghosts: Reduced frequency, longer intervals
Trigger Message Schema
{
"trigger_type": "VENT_WITNESSED",
"trigger_data": {
"player": "red",
"vent_location": "electrical",
"action": "entered",
"timestamp": 47.3
}
}
Game State Schema
Per-Tick Context
{
"time": 47.3,
"phase": "PLAYING",
"you": {
"role": "impostor",
"location": "electrical",
"kill_cooldown": 0,
"tasks": [],
"emergencies_remaining": 1
},
"recent_history": [
{"t": 12.3, "vision": {"players": ["blue"], "location": "hallway_1"}}
],
"vision": {
"players_visible": [
{"id": "blue", "location": "electrical", "doing": "task"}
],
"objects_visible": ["vent_elec", "task_wires", "body_yellow"],
"exits": ["security", "cafeteria"]
},
"available_actions": {...}
}
Fog of War
Critical: Each agent only knows what they've observed.
- Engine tracks per-player knowledge
known_deaths: bodies seen or announcedknown_locations: last seen positions + timestampswitnessed_events: vents, kills, sus behavior
Response Format
Action Phase
{
"internal_thought": "Blue just left, perfect time to kill Green",
"action": {"type": "KILL", "target": "green"},
"scratchpad_updates": {
"plan": "...",
"events": "...",
"suspicions": "..."
},
"trigger_config": {
"mute": [{"type": "INTERSECTION", "until": "REACHED_DESTINATION"}]
}
}
trigger_configonly if changing defaultsinternal_thoughtseparate from action (for thinking models)
Meeting Interrupt Flow
When report/emergency called:
- Interrupt note: Agent leaves context ("this was what I was doing")
- Pre-meeting prep: Agent reviews & prepares thoughts
- Meeting scratchpad: Temporary, discussion-only
- Post-meeting consolidation: Agent saves important info to main scratchpads
Ghost Mode
- Omniscient view of entire game
- No access to other agents' thoughts
- Can do ghost tasks
- Reduced tick frequency (save tokens)
- Write to scratchpad, observe strategies
- No game state modifications
Special Mechanics
Lights Out
- Vision radius shrinks (0.25x multiplier)
- Triggers panic tick for all players
- Engine recalculates trajectories
- Fix triggers restoration tick
Near-Death Edge Case
- Impostor queues kill, victim queues report same tick
- Report fires first (higher priority)
- Impostor gets: "Your kill was interrupted"
- Victim has no direct knowledge (must deduce from proximity)
Configuration Philosophy
Everything toggleable:
- Persona injection
- Strategy tip levels
- Meta-awareness levels (subtle/direct/4th wall)
- Periodic tick frequency
- Random tick RNG
- Tool availability based on context
Goal: Replicate human experience. LLM should have same information and options as human player.