Skip to content

Bardic v0.7.1: Lots of Critical Fixes and Story Tracking

Choose a tag to compare

@katelouie katelouie released this 12 Mar 01:17
· 34 commits to main since this release

New Features

Passage Visit Counting: _visits

The _visits dict is now a first-class engine built-in. Every time a passage is entered, its count increments automatically. No setup, no boilerplate.

:: Tavern
@if _visits.get("Tavern", 0) == 0:
    The smell of woodsmoke and something sour hits you first.
@else:
    The regulars barely glance up. You're starting to become a fixture.
@endif

+ [Sit at the bar] -> TavernBar
+ [Head upstairs] -> TavernRoom

Or inline:

You've {_visits.get("Tavern", 0) > 1 ? been here before, and it shows | never set foot in a place like this}.

_visits is automatically included in undo/redo snapshots and save/load. Zero footprint, full persistence.


Turn Counter: _turns

_turns counts player choices (incremented by choose(), not goto()). This is the number of decisions made, which turns out to be exactly what you want for pacing, urgency, and scoring.

@if _turns >= 20:
    You've been at this a long time. The candles are burning low.
@endif
+ {_turns < 5} [Take your time exploring] -> FreeExplore
+ {_turns >= 10} [The situation is getting urgent] -> UrgentPath

Use it for:

  • Pacing gates: unlock content after N choices
  • Urgency mechanics: tick-tock tension without a timer
  • Scoring: shorter paths can reward efficiency
  • "You've been here a while" flavor text

Both _visits and _turns are covered by 17 new tests.


Quest Tracking: bardic.stdlib.quest

A full quest system lands in stdlib. QuestJournal manages a collection of Quest objects, each with custom stages, completion/failure states, and narrative journal entries you can surface directly in prose.

from bardic.stdlib.quest import QuestJournal, Quest

journal = QuestJournal()

# Create a quest with named stages
q = Quest("find_the_key", "The Missing Key")
q.add_stage("search_room")
q.add_stage("check_drawer")
q.add_stage("find_key", is_final=True)

journal.add_quest(q)
@py:
journal.advance("find_the_key", "check_drawer")
journal.add_journal_entry("find_the_key", "The drawer was empty, but the dust was disturbed.")
@endpy

@if journal.get_quest("find_the_key").current_stage == "check_drawer":
    You crouch by the desk. Something's been moved recently.
@endif

Filtered views are clean and idiomatic:

for quest in journal.active_quests:
    print(f"→ {quest.title}: {quest.current_stage}")

Full to_dict()/from_dict() serialization. If it can't save, it doesn't exist.

32 new tests.


Full Stdlib Test Suite — 114 Tests

All five stdlib modules now have proper test coverage: dice, inventory, economy, relationship, and quest. Previously: zero stdlib coverage. That era is over.


Unknown Directive Detection with "Did You Mean?"

Lines starting with @ that don't match any known directive now raise a SyntaxError — loudly and helpfully — instead of silently becoming content text that confuses everyone for twenty minutes.

SyntaxError: Unknown directive '@elseif' on line 42
  Did you mean: @elif

SyntaxError: Unknown directive '@python' on line 7
  Did you mean: @py:

Common typos (@elseif, @iff, @python) get specific suggestions. Completely unknown directives list all valid options. 5 new error handling tests.


Breaking Change

Python 3.10+ Required

The codebase uses X | Y union syntax (3.10+) and lowercase generic types (3.9+). pyproject.toml, CLAUDE.md, and all tutorials now reflect the real minimum. If you were running on 3.9, this is the nudge.


Bug Fixes

Callables Corrupted on Save/Load (Critical)

Functions and other callables in engine state (create_session, get_artifact, random, anything you passed in) were falling through to __dict__-based serialization. They'd get saved as dicts. On load, calling them would throw TypeError: 'dict' object is not callable.

This would have fired mid-Arcanum-session at the worst moment. Fixed: callables are now skipped during serialization alongside classes and types.

One-Time Choice IDs Were Unstable

One-time choices (*) were tracking seen choices by rendered string while filtering by raw content arrays, so they'd reappear on revisit. Now tracking and filtering use the same representation. Your * choices are once-and-done, as they should be.

Circular Include Detection Was Broken

resolve_includes passed seen.copy() to recursive calls, giving each branch its own independent set. A→B→A cycles weren't detected. Fixed: all branches now share the same seen set.

Relationship.name Never Actually Assigned

The __init__ had self.name: str (a type annotation, not an assignment). AttributeError on any name access. Fixed: self.name = name.

Loop Exception Handler Returned Wrong Tuple

_render_loop error path returned (error_msg, None) instead of (error_msg, None, []). Caused unpacking errors on any loop exception. Fixed.

Tutorial & Docs Fixes

  • Tutorial 03_5: 6 wrong bardic.modules.* imports → bardic.stdlib.*
  • Tutorial 04: GameUI rewritten to use actual BardCompiler/BardEngine API (compile_string, goto, submit_inputs, choose)
  • Tutorial 00b: Test example rewritten to use BardCompiler.compile_string() + BardEngine(story_data) — no more nonexistent no-arg constructor
  • Tutorial 04: Removed standalone @start that was being parsed as content text
  • Cookbook custom-classes.md: <<if>>/<<for>>@if:/@for:
  • Stdlib docstrings: inventory.py and economy.py referenced bardic.modules.* instead of bardic.stdlib.*
  • Coffee shop example: alex.is_ready_for_deep_workalex.is_ready_for_deep_conversation

By the Numbers

Count
New tests (total) 168
Stdlib test coverage 114 tests across 5 modules
_visits + _turns tests 17
Quest system tests 32
Error handling tests 5
Tutorial/doc files fixed 6
Critical runtime bugs fixed 4

Upgrade Notes

pip install --upgrade bardic

If you're on Python 3.9, upgrade to 3.10+ first.

The callable serialization fix is transparent. No story changes needed, but your save files from 0.7.0 should be considered suspect if they were saved after any callable-heavy engine state. Fresh saves from 0.7.1 onward are clean.