thousand-token-theater / test_engine.py
HIMANSHUKUMARJHA's picture
Thousand-Token Theater: MiniCPM improv troupe with a hard 1,000-token memory
2b0a293 verified
Raw
History Blame Contribute Delete
3.16 kB
"""
Local logic test for the Theater engine.
This does NOT use MiniCPM. It injects a deterministic stub at the model boundary
and a heuristic token counter, purely to verify the engine's behaviour:
- the running script stays within the token budget,
- eviction moves the oldest beats into `forgotten`,
- turn order rotates through the cast,
- Director notes are recorded and themselves become forgettable.
The shipped app uses the REAL model (model.py). This stub is test-only.
"""
import re
from theater import TheaterEngine, DEFAULT_CAST
# --- stubs ----------------------------------------------------------------- #
_CANNED = [
"Hark! The lantern gutters, and with it my courage. *throws a paw to the sky*",
"Um, actually, I think the bridge is on fire. Just saying. Quickly.",
"Silence! The true protagonist of this tale has, at last, arrived. *bows*",
"*peers into the dark* Did anyone else hear that, or is it just my heart?",
"I shall deliver a monologue so vast the stars themselves will weep.",
"There's no time! The river's rising and I left the door unlatched!",
]
_i = {"n": 0}
def fake_generate(messages):
"""Return a deterministic in-character-ish line, ignoring real content."""
line = _CANNED[_i["n"] % len(_CANNED)]
_i["n"] += 1
return line
def heuristic_tokens(text):
# ~1.4 'tokens' per whitespace word — a stand-in for a real tokenizer.
words = re.findall(r"\S+", text)
return max(0, round(len(words) * 1.4))
# --- test ------------------------------------------------------------------ #
def main():
eng = TheaterEngine(
generate_fn=fake_generate,
count_tokens_fn=heuristic_tokens,
budget_tokens=120, # small cap so eviction triggers fast in the test
)
eng.start_play("woodland", premise="someone in the troupe is secretly a king")
notes = ["", "", "A storm rolls in.", "", "", "Reveal Pip is the lost prince.",
"", "", "", "The lantern dies.", "", "", "", "", ""]
seen_speakers = set()
for note in notes:
beat = eng.advance(note)
seen_speakers.add(beat.speaker)
st = eng.state()
assert st["tokens"] <= eng.budget, (
f"BUDGET BROKEN: {st['tokens']} > {eng.budget}")
st = eng.state()
print("== final state ==")
print("remembered beats:", st["remembered_count"], "| tokens:", st["tokens"], "/", st["budget"])
print("forgotten beats :", st["forgotten_count"])
print("turn rotated through:", sorted(seen_speakers))
print("next up :", st["next_speaker"].name)
print()
print("-- currently remembered --")
print(eng.transcript())
print()
print("-- recently forgotten --")
for b in st["last_forgotten"]:
print(" forgot:", b.script_line()[:70])
# assertions
assert st["forgotten_count"] > 0, "nothing was ever forgotten — eviction not working"
assert st["remembered_count"] >= 1
assert len({c.name for c in DEFAULT_CAST} & seen_speakers) == len(DEFAULT_CAST), \
"not all cast members got a turn"
print("\nALL CHECKS PASSED ✅")
if __name__ == "__main__":
main()