Skip to content

Cartridge Developer Guide

This guide describes how to build and validate a MnesOS cartridge, alongside implementing complex mechanics like countering and phase-based intention.

Cartridge Contract & Structure

Create a cartridge under cartridges/<your-game-name>/.

Required files: 1. yare.yaml 2. bot_lore.md

Optional file: 3. prompt_directives.yaml

yare.yaml stays procedural. Narrative steering belongs in prompt_directives.yaml, not in the rules file.

bot_lore.md

Use this file for world facts, NPC descriptions, locations, factions, and items. - structure content with markdown headers because the lore store chunks on #, ##, and ### - keep mechanics out of lore text - use descriptive section names so retrieval has strong anchors

prompt_directives.yaml

Use this file only for short per-role LLM steering.

Important Note on State Access: When instructing the LLMs to check the game state, you MUST refer to it as bot_memory (e.g., If bot_memory['player']['hp'] < 10...). The state. prefix is only used inside yare.yaml.

Allowed keys: director, npc, narrator. If prompt_directives is placed inside yare.yaml, the loader rejects the cartridge.

yare.yaml Integration

Use this file for deterministic state and event logic. - state_schema defines tracked state and defaults - macros define reusable @ expressions - events define executable steps

For full details on supported expressions, operators, step types (like set, mutate, branch, call, table_roll, etc.), please refer to the complete YARE Specification.

State Visibility

Each schema field may define visibility (public or private).

state_schema:
  player:
    hp: { type: int, default: 100, min: 0, visibility: public }
    hidden_flag: { type: bool, default: false, visibility: private }
  • public fields are eligible for narrator context.
  • omitted visibility defaults to private.
  • private fields are excluded from the narrator context to keep hidden mechanics deterministic without leaking them into player-facing narration, but they remain fully accessible to the YARE interpreter for rules and logic.

This keeps hidden mechanics deterministic without leaking them into player-facing narration.


Advanced Mechanics & Counter-Play

This section outlines the operational domain of the MnesOS 2-node graph, including how system_notes are safely passed to the Narrator and how complex combat systems can be implemented.

1. Semantic Grounding (Ensuring Narrator Understanding)

The Narrator LLM needs to understand the numbers generated by YARE without the user seeing the raw math. Cartridge developers must provide semantic grounding within note event actions.

Use variable interpolation ({variable}) inside note strings and format them as instructions or stage directions for the Narrator. Enclosing them in tags (like [SYSTEM LOG: ...]) ensures the Narrator recognizes it as backstage instruction.

Good Practice (Semantic Grounding):

- action: mutate
  var: "state.goblin.hp"
  op: "sub"
  value: 10
- action: note
  message: "[SYSTEM LOG: Player successfully landed an attack on the Goblin. Base damage was 10. The Goblin's remaining HP is {state.goblin.hp}. Describe the strike connecting and the Goblin reeling.]"

2. The Operational Domain & Turn Iterations

The execution graph flows iteratively between the Director and the ToolNode before finally reaching the Narrator: Director (Intent Analysis) 🔁 ToolNode (Mechanics & NPC Intent)Narrator (Render)

If an action is "immediate", reactive "counters" within the same turn require the Director to query the NPC before finalizing damage. Complex rules require designing YARE events using Phase-Based Intention and Telegraphing.

3. Scenario A: Player attacks, NPC Counters (Shield Block)

If a player attacks, the Director should query the NPC intent before applying final damage.

How it works: 1. Player says "I attack the Goblin". 2. Director queries NPC intent using query_npc_intent. 3. NPC Intent tool responds: "I intend to raise my shield!" 4. Director resolves the attack mechanics (YARE), taking the block into account, and summarizes.

For complex logic: Trigger plan_player_attack to save values to a buffer, query NPC, and in the next loop, run mechanics resolution to clear the buffer and apply damage.

4. Scenario B: NPC Telegraphs, Player Counters

The Player cannot physically interrupt an active Turn resolution. To allow a Player to counter an NPC, the NPC must telegraph an attack on Turn 1, so the Player can respond on Turn 2.

How it works: 1. Turn N (Mechanics Phase): Director triggers npc_telegraph_attack. 2. Turn N (Narrator): Narrator writes "The Goblin raises his heavy club, preparing to smash it down!" 3. Turn N+1 (Player Phase): Player says "I raise my shield to brace." Director triggers player_resolve_incoming.

(Refer to earlier documentation or YARE design patterns for specific implementation details on buffering incoming_attack_val and clearing it upon resolution).


Conversion Checklist & Things Not To Rely On

Conversion Checklist

  1. Extract all stats, resources, and flags into state_schema.
  2. Move every deterministic rule into events.
  3. Add bounds with min and max where needed.
  4. Mark player-visible fields with visibility: public.
  5. Move flavor text into bot_lore.md.
  6. Move tone instructions into prompt_directives.yaml.

Anti-Patterns (Do Not Do These)

  • do not rely on hardcoded event-name matching in engine code
  • do not rely on cartridge-specific NPC behavior embedded in Python nodes
  • do not put prompt directives inside yare.yaml
  • do not assume the engine persists state for you; the client must store and re-supply the returned game state each turn