[{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/series/chronicler/","section":"Series","summary":"","title":"Chronicler","type":"series"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/chronicler/","section":"Tags","summary":"","title":"Chronicler","type":"tags"},{"content":"Counterclock is a personal notebook for side projects, half-finished ideas, and the occasional thing worth writing down properly.\nMost of what\u0026rsquo;s here right now concerns Chronicler — a solo tabletop role-playing engine built around Claude Code. Recent posts below.\n","date":"25 May 2026","externalUrl":null,"permalink":"/docs/","section":"Counterclock","summary":"","title":"Counterclock","type":"page"},{"content":" Tools # To keep things fair and interesting, Claude calls the following Python scripts over MCP. They\u0026rsquo;re grouped by what they touch.\nCampaign / session # Lifecycle commands — spinning up campaigns, archiving them, and feeding the dashboard.\ncampaign_mgmt.py — create, list, switch, inspect campaigns; add characters campaign_archive.py — export/import a campaign as a single tarball dm_session.py — headless claude -p driver for the dashboard\u0026rsquo;s /play surface export_session.py — export a Claude Code session JSONL transcript as markdown audit.py — scan events.json for procedural lapses (missed reactions/morale/surprise) log.py — chronicle notes, calendar/time advancement Combat \u0026amp; dice # Mechanics — initiative, dice, conditions, and the DungML-driven tactical grid.\ncombat.py — initiative, attacks, HP, spell declaration, conditions, effects combat_map.py — DungML renderer, grid state, combatant positioning dice.py — roll, reaction, morale, encounter, saves, skill checks Characters / party / NPCs # Character and party state — vitals, loyalty, factions, and player holdings.\nparty.py — HP, XP, spells, rest, coin hirelings.py — CHA-driven loyalty checks for hirelings/henchmen homebrew_classes.py — loader for global/homebrew/classes/*.json factions.py — factions and world-clock countdowns holdings.py — player-owned houses and mounts World / locations / maps # World simulation — places, weather, travel days, and overland geography.\nworld.py — NPCs, locations, areas (markdown-backed) world_map.py — Structurizr-style overland DSL → GeoJSON views lore.py — search, timelines, quest threads, session primers travel.py — overland day-loop chaining weather, encounters, clock ticks, rations weather.py — seasonal weather tables survival.py — consumable inventory, light sources, encumbrance, rations Reference lookups # Read-only lookup over rulebooks, monster/spell/class data, and setting databases.\nlookup.py — monster/spell/class lookup, treasure generation rules.py — FTS5 search over PHB/DMG/MM text greyhawk.py — wraps settings/greyhawk/greyhawk.db (pages, FTS, typed entries) build_2e_db.py — build global/2e.db (spells + classes) build_phb_ref.py — build ability_scores + proficiencies tables build_rules_db.py — build the FTS5 rules index Items / quests / rumors # Plot-adjacent data — quest fairness, gossip with truth tiers, and shared item weights.\nquests.py — quests with scope/stakes and level-fairness warnings rumors.py — gossip table tracking truth tier separately from what NPCs say item_weights.py — shared item-weight resolution (used by dashboard + survival) Media / misc # Audio, imagery, generators, and DM-only secret tracking.\nimages.py — Replicate scene + portrait generation tts.py — OpenAI TTS with per-character voice mapping wordlist.py — fantasy-themed random word generator secrets.py — DM-only hidden facts (dm_note, dm_secrets) Campaign state is stored as structured JSON on the file-system. Tools change state; the web UI reflects current state.\nInstructions # Claude tends towards a certain style of adventure — world-spanning stakes and long-term adventure hooks where everything connects. At lower levels, I prefer adventures to be more straightforward. My model instructions specify:\nFocus on simulating a world, rather than telling a story where every adventure thread is connected. Fairness, avoiding the rule of cool and fudging dice-rolls in favour of the party. Playing characters and monsters in line with their intelligence. Animals don\u0026rsquo;t devise clever tactics; dragons do. Respecting player statistics. Characters with low charisma shouldn\u0026rsquo;t have supermodel portraits; characters with low intelligence should use simple speech. Not breaking the fourth wall by sharing information player characters couldn\u0026rsquo;t know (statistics, dungeon master information\u0026hellip;). Sometimes it\u0026rsquo;s necessary to ask a question or nudge the engine in a certain direction. In that case, I use the ooc: (out of character) prefix. The prefix tells the engine to treat the line as meta-instruction rather than character speech — for example, ooc: skip the tavern small talk and jump to the road, or ooc: what\u0026rsquo;s the DC for this lock?. Without it, the model would route the input through whichever character I\u0026rsquo;m currently playing.\n","date":"25 May 2026","externalUrl":null,"permalink":"/docs/posts/engine-tools-and-instructions/","section":"Posts","summary":"","title":"Engine tools and instructions","type":"posts"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/instructions/","section":"Tags","summary":"","title":"Instructions","type":"tags"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/mcp/","section":"Tags","summary":"","title":"Mcp","type":"tags"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/tools/","section":"Tags","summary":"","title":"Tools","type":"tags"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/claude-code/","section":"Tags","summary":"","title":"Claude-Code","type":"tags"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/dsl/","section":"Tags","summary":"","title":"Dsl","type":"tags"},{"content":"Claude is good at narration, but pretty awful at spatial reasoning.\nWhile it\u0026rsquo;s fine to play encounters using the theatre of the mind without explicit tracking of character and monster locations, it can be fun to manage tactical positioning and get an overview of the party\u0026rsquo;s surroundings.\nIn order to solve this problem I defined a domain-specific language — DungML — that lets Claude generate tactical maps on the fly. Rooms, corridors, doors, windows, secret passages, and lighting are all declarative; the model only has to describe intent, not coordinates:\nmap \u0026#34;Quickstart\u0026#34; { grid { bounds 30 x 18 } renderer \u0026#34;hatched\u0026#34; background \u0026#34;#CCCCCC\u0026#34; } room \u0026#34;antechamber\u0026#34; { rect 2,4 12 x 10 label \u0026#34;Antechamber\u0026#34; description \u0026#34;Cracked flagstones. Pillars hold up a sagging ceiling.\u0026#34; feature pillar at 4,6 feature pillar at 4,12 feature pillar at 12,6 feature pillar at 12,12 feature brazier at 8,6 } room \u0026#34;sanctum\u0026#34; { rect 18,4 10 x 10 label \u0026#34;Inner Sanctum\u0026#34; description \u0026#34;A statue of a forgotten god still keeps watch.\u0026#34; feature statue at 23,7 feature altar at 23,11 scale 1.2 } # Short corridor bridging the 4-unit gap between the rooms. corridor \u0026#34;passage\u0026#34; { width 2 segment line from 14,9 to 18,9 } # Default door — wooden, closed, width 1. Three more lines of property # would let you change those. door at 14,9 { connects room.antechamber, corridor.passage } door at 18,9 { connects room.sanctum, corridor.passage type iron state locked description \u0026#34;Old keyed lock. The wards still hum faintly.\u0026#34; } # Windows on the antechamber\u0026#39;s west wall — exterior light. window at 2,7 { in room.antechamber width 1.5 } window at 2,11 { in room.antechamber width 1.5 } # A secret door in the antechamber\u0026#39;s south wall — discoverable only # on a successful Perception check. door at 8,14 { connects room.antechamber type secret description \u0026#34;A pivot stone. Catch the seam and the wall turns.\u0026#34; } Renders to:\nUsing a structured language allows definition of more complex tactical maps:\nThe system is flexible enough to render houses, caves, outdoor maps and the like:\nRendering is performed by a small JavaScript component embedded in the Chronicler application, allowing the system to update the map as locations are discovered and players move around:\nThe DungML editor allows saving of locations as PDF in old-school module format:\n","date":"25 May 2026","externalUrl":null,"permalink":"/docs/posts/dungeons-as-code/","section":"Posts","summary":"","title":"Dungeons as Code","type":"posts"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/maps/","section":"Tags","summary":"","title":"Maps","type":"tags"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/demo/","section":"Tags","summary":"","title":"Demo","type":"tags"},{"content":"Let\u0026rsquo;s demonstrate the engine by creating a new campaign in the Campaigns tab. I will create an example based on the Curse of the Azure Bonds module, but I\u0026rsquo;m free to let the system define a campaign instead. I\u0026rsquo;ll also let the engine generate an appropriate banner image:\nWith the campaign generated, I will go to the Play area to tell the model to set up an adventuring party. I can be as hands-on or hands-off during creation as I wish:\nThis screen has a number of options allowing me to customize my experience:\nAs the system generates the members of my party, I can review them in the party tab:\nClicking on party members shows a detailed character sheet with ability scores, inventory, proficiencies, backstory and so on:\nOnce all party members have been created, the sidebar in the play area shows their portraits and summary information:\nWe start the adventure with a detailed introduction text:\nAs a matter of personal preference, I tend to pick a single character to play and let the model handle the others. By default, the system expects text-based input. Note the small question mark and play icon at the end of the response.\nPressing play narrates the latest entry using text-to-speech. I have the option of choosing custom voices per character. The question mark shows an indication of the cost of this interaction, based on API tokens. Since I\u0026rsquo;m using a subscription, I don\u0026rsquo;t pay anything beyond a negligible amount for text-to-speech and image generation. While I prefer to play campaigns on a laptop using the keyboard, the engine also supports a Light mode, where the system provides a number of possible actions to take at the end of each response. This is convenient when playing on a mobile phone or tablet where text entry is a bit more awkward:\nAs we play, the engine builds up a detailed map of areas such as cities visited, with locations in each area for points of interest:\nActive and completed quests are tracked in a separate tab:\nAnd an index of characters along with detailed notes is also accumulated:\nThis information is stored on the file-system, allowing campaigns to grow indefinitely without eating up the context window.\nThe engine can generate images at key moments which are persisted in the gallery:\nThat\u0026rsquo;s the full loop — campaign creation, party generation, play, and the long-tail of locations, quests, NPCs and imagery that accumulates as you go. Future posts will dig into specific subsystems (combat, travel days, faction clocks) in more detail.\n","date":"25 May 2026","externalUrl":null,"permalink":"/docs/posts/playing-a-chronicler-campaign/","section":"Posts","summary":"","title":"Playing a Chronicler campaign","type":"posts"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/walkthrough/","section":"Tags","summary":"","title":"Walkthrough","type":"tags"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/architecture/","section":"Tags","summary":"","title":"Architecture","type":"tags"},{"content":"Chronicler uses Claude Code as the reasoning core, with a web-based front-end and a number of Python tools handling the mechanics. Tools are exposed over MCP so the model can invoke them directly.\nPersistence # Persistence is handled via a combination of file-system storage for campaign information and a few small SQLite databases for reference data. All interactions with the model are persisted to disk, providing a permanent memory of what happened in the campaign. This memory can be consulted in the browser or via MCP, but it doesn\u0026rsquo;t take up context space unless the model needs to look something up:\nUsing the file-system for persistence is convenient because it allows easy toggling between campaigns:\nCampaigns can be fully original, or based on one or more adventure modules. I enjoy replaying the AD\u0026amp;D second edition modules from my youth, and upload them into the system for conversion to markdown.\nExternal services # I\u0026rsquo;m running the application on a low-powered laptop. I wanted the option to migrate it to a Raspberry Pi or other power-efficient always-on server, so besides Claude Code I\u0026rsquo;m using SaaS solutions for:\nNarration: OpenAI text-to-speech. Image generation: Replicate, since OpenAI is quite conservative in its filters — image descriptions often triggered NSFW warnings because they mentioned blood or violence. Data model # The engine supports flexibility along three axes:\nRuleset: For a given system I save reference materials in markdown format, augmented with database-backed structured data for convenient look-up of spells, equipment etc. Adding rulesets is a matter of importing the relevant manuals in a machine-readable format in a ruleset-specific subdirectory.\nRuleset customizations: While I\u0026rsquo;m essentially playing AD\u0026amp;D 2nd edition, I like to specify:\nCustom races not supported out of the box: Aarakocra, centaur, Doppelganger, Kobold, Pixie, Tabaxi etc. have their own rules with attribute requirements, class restrictions, special rules etc. Custom classes follow the same pattern. Vanilla AD\u0026amp;D 2nd edition doesn\u0026rsquo;t support classes such as Barbarian, Alchemist or Witch. House rules: I add rules overriding default behavior on character creation, level-up and combat. Setting: My campaigns so far take place either in the Greyhawk or Forgotten Realms settings. Setting lore is stored as a combination of markdown background material and a database. The user can consult reference data via the browser, the model can make searches via an MCP:\nGeoJSON is a convenient format for tracking markers across larger-scale maps. Since the Greyhawk setting didn\u0026rsquo;t have a published GeoJSON map yet, I wrote a small tool to trace existing maps:\nThe next post looks at how Claude handles tactical maps — where structured data really starts to pay off.\n","date":"25 May 2026","externalUrl":null,"permalink":"/docs/posts/chronicler-architecture/","section":"Posts","summary":"","title":"Chronicler overview","type":"posts"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/ai/","section":"Tags","summary":"","title":"Ai","type":"tags"},{"content":"AI models would intuitively seem ideally suited to take on the role of game master. It\u0026rsquo;s possible to feed them a module or ask them to wing a campaign and start with minimal preparation.\nEarly on in the days of AI, I tried to run the classic Tomb of Horrors campaign as narrated by ChatGPT. Although the narrative was reasonably good, I quickly noticed several glaring problems:\nAI needs an external system to handle mechanics. My party\u0026rsquo;s skill rolls kept miraculously succeeding because the model came up with its own numbers for dice rolls. Combined with a tendency of trying to please the user, this gives the feeling that choices don\u0026rsquo;t matter. The system is limited by its context window. Without persistence, the adventure quickly outgrows the session memory, making it impossible to have longer campaigns. Persistence is also a requirement for proper inventory and spell tracking, logging of adventure locations and non-player characters and so on. Even advanced models love clichés. Soon, my adventures kept bumping into characters with the same names (Aldric and Voss are popular for some reason). Campaigns turned into an incoherent fever dream where the party kept finding notes sending them on near-identical fetch quests across the world, trying to solve a massive conspiracy where everybody knew each other. While less glaring, the generic web or CLI interface of an AI model also feels quite sparse, making it difficult to manage adventure maps or flavour images. I was quite happy using Claude Code with a custom status line to track my party vitals, but this still left something to be desired:\nThese three failure modes — fairness, memory, and originality — together with the threadbare interface became the design goals for Chronicler.\n","date":"25 May 2026","externalUrl":null,"permalink":"/docs/posts/ai-and-roleplaying-a-good-match/","section":"Posts","summary":"","title":"AI and roleplaying - a good match?","type":"posts"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/design/","section":"Tags","summary":"","title":"Design","type":"tags"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/tabletop/","section":"Tags","summary":"","title":"Tabletop","type":"tags"},{"content":"","date":"25 May 2026","externalUrl":null,"permalink":"/docs/tags/personal/","section":"Tags","summary":"","title":"Personal","type":"tags"},{"content":"My first introduction to tabletop role-playing came in the late eighties. My older brother had bought a copy of the Dungeons \u0026amp; Dragons Starter Set. He only had a passing interest in the hobby, but I fell in love with the box art and the idea of building a fantasy world even though my English was pretty rudimentary.\nOver the following years, the hobby became an important part of my identity. My friends and I spent most of our free time playing D\u0026amp;D and various other systems. We had a sea of time to go to conventions, play computer adaptations and read cheesy novels set in our campaign worlds.\nFinding myself three decades later with a job and family, scheduling a tabletop session is nigh impossible. So I built Chronicler — a computer system that lets me relive the adventures of my teens without having to synchronize calendars. The posts that follow document how it\u0026rsquo;s put together, in case anyone else wants to reuse it or build on top of it.\n","date":"25 May 2026","externalUrl":null,"permalink":"/docs/posts/introduction/","section":"Posts","summary":"","title":"Why I built a TTRPG engine","type":"posts"},{"content":"","externalUrl":null,"permalink":"/docs/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/docs/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"Chronicler is a single-player tabletop role-playing engine built around Claude Code. It exists because scheduling a real D\u0026amp;D session with five adults is harder than slaying a dragon.\nThe engine pairs Claude with about thirty Python tools exposed over MCP, a web front-end for the parts a chat window does poorly, and a file-system store that lets campaigns grow indefinitely without devouring the context window.\nWhat it does # Plays AD\u0026amp;D 2nd edition (and other rulesets you can import) with fair mechanics — dice that the model can\u0026rsquo;t fudge, real encumbrance, morale, surprise, reactions. Persists everything to disk: party, NPCs, locations, quests, rumors, faction clocks, session transcripts. Renders tactical maps from DungML, a small DSL that takes the spatial reasoning out of the model\u0026rsquo;s hands. Generates scenes and portraits via Replicate, narrates via OpenAI TTS, and tracks per-character voices. Supports Greyhawk and Forgotten Realms out of the box, with GeoJSON overland maps and full setting lore search. How it\u0026rsquo;s built # Claude Code is the reasoning core. Python tools handle anything mechanical — combat resolution, dice, travel days, weather, lookups. State lives on the file-system as structured JSON and a handful of SQLite databases; the web UI is a thin reflection of that state.\nThe architecture is designed to run on a low-powered always-on machine — a laptop today, a Raspberry Pi tomorrow.\nRead more # The full story is told across the Chronicler series:\nWhy I built a TTRPG engine AI and roleplaying — a good match? Chronicler overview Dungeons as Code Engine tools and instructions Playing a Chronicler campaign ","externalUrl":null,"permalink":"/docs/chronicler/","section":"Counterclock","summary":"","title":"Chronicler","type":"page"}]