From 7b9fc8aa1ddd95c1826a55ada88119b9a7282a38 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Tue, 30 Jun 2026 22:46:46 +0200 Subject: [PATCH] Reset story back a bit --- session/ambience.md | 2 +- session/book.md | 95 +++---------------------------- session/character.md | 3 +- session/journal.md | 4 +- session/last_prompt.md | 2 +- session/log/2026-06-23.md | 21 ------- session/log/2026-06-24.md | 15 ----- session/log/2026-06-25.md | 22 ------- session/session_log.md | 54 ++++++++++++++++++ session/settings.json | 2 +- tools/engine_lib/paths.py | 4 +- tools/engine_lib/prompts.py | 2 +- tools/engine_lib/state.py | 57 ++++++++++++------- tools/engine_lib/tools_handler.py | 2 +- tools/engine_lib/validation.py | 2 + tools/run.py | 15 ++--- tools/run_utils.py | 11 ++-- tools/test_runtime.py | 4 +- 18 files changed, 123 insertions(+), 194 deletions(-) delete mode 100644 session/log/2026-06-23.md delete mode 100644 session/log/2026-06-24.md delete mode 100644 session/log/2026-06-25.md create mode 100644 session/session_log.md diff --git a/session/ambience.md b/session/ambience.md index 69344a0..47800cb 100644 --- a/session/ambience.md +++ b/session/ambience.md @@ -1 +1 @@ -wilds +dungeon diff --git a/session/book.md b/session/book.md index 2203c1c..85be376 100644 --- a/session/book.md +++ b/session/book.md @@ -1,6 +1,4 @@ - - -## Turn — 2026-06-25 +## Turn 1 The common room of the Splintered Tankard hums with the low murmur of morning. Smoke coils from the hearth fire, mixing with the smell of barley stew and wet wool. Mistress Otta Venn moves between tables with the practised ease of a woman who has survived things — her curved knife catching the firelight as she passes. @@ -23,7 +21,7 @@ Four notices catch his eye: The mill job tugs at him — coin, and close enough to test his mettle. He heads for Weber's Smithy. -## Turn — 2026-06-25 +## Turn 2 Weber's Smithy clangs and hisses at the west edge of the Market Square. The smith himself is a hunchbacked man with forearms like hawsers, who carries on a constant low conversation with his hammer — a tool he calls "Bell." @@ -38,7 +36,7 @@ In the Market Square, Dillion spots Lark — a scrawny errand boy with quick eye Dillion grabs a pitchfork from a stall for Lark, and the two head east on the King's Road as the sun climbs toward noon. -## Turn — 2026-06-25 +## Turn 3 The old mill squats at Three Bridges where the Greywater bends east. Its wheel is half-rotted, motionless. The roof sags. Wind moans through gaps in the planking. The air smells of damp wood and something underneath — a sweet, acrid tang. @@ -57,7 +55,7 @@ Dillion's mace meets it mid-air. The impact crunches ribs — 2 damage, and the He cuts off an ear as proof for Weber, and finds the creature's gut holds 3 silver and a rusted iron key. -## Turn — 2026-06-25 +## Turn 4 The iron key fits an iron-bound chest in the corner. Inside: 12 silver, a silver locket holding a lock of auburn hair, and a bundle of love letters — from a woman named Marren to a woman named Elara. The later letters grow desperate. Someone was watching the mill. Someone in hoods. @@ -79,7 +77,7 @@ Dillion jumps down. The chest is empty — whatever was inside is gone. But on t Dillion decides to head back to the Keep. Report to Weber. Get paid. Gear up Rina. The tunnel can wait. -## Turn — 2026-06-25 +## Turn 5 Back at the Keep, morning light washes the muddy streets. Dillion leads Rina and Lark through the Market Square, drawing a few glances — a blood-spattered man, a gaunt woman in a dead miller's coat, a grinning boy carrying a pitchfork. @@ -96,7 +94,7 @@ Dillion takes her to Weber's for a light crossbow, then to a stall for a padded They buy two long ropes and head east on the King's Road as the sun begins its afternoon slope. The mill waits. The tunnel waits. And somewhere below, whatever the Guild was hiding. -## Turn — 2026-06-25 +## Turn 6 The mill is quiet as they slip through the loose board and descend into the cellar. The bodies of the creatures still lie where they fell. The crack in the wall breathes cool, damp air. @@ -141,7 +139,7 @@ A weaver. Working something. Dillion lets the curtain fall and steps back to consult Rina. -## Turn — 2026-06-25 +## Turn 7 You pull back from the velvet curtain, your heart hammering. Rina watches you, crossbow raised, eyes asking the question. @@ -157,81 +155,4 @@ Rina whispers: *"That's blood magick. I've seen the signs before. He's not just She looks at you, waiting. -Waits and deliberates, weighing the risk of waiting for the ritual to finish against the danger of interrupting a blood-weaver mid-chant. - - -## Turn — 2026-06-25 - -The weaver's voice rises to a shout — a single word, sharp and resonant. The candle flames all gutter at once, then flare back a deep, pulsing red. A low thrum vibrates through the stone under your boots. - -Through the gap in the curtain, you see the dagger in the circle begin to glow — a dull, hungry orange, like iron pulled from a forge. The weaver's hands tremble as he holds them over the blade, chanting faster now, sweat gleaming on his face. - -Rina's fingers brush your arm. She points — past the curtain, where the corridor curves right. A heavy wooden door, banded in iron. The same crest: a fist gripping a gear. - -The weaver's chant is building toward its final note. The dagger pulses brighter. Whatever he's summoning is almost here. - -You have seconds. - -Decides to hide and wait. Slipping into the weaver's study behind shelves and furniture, Dillion and Rina watch as the ritual reaches its peak and something begins to take shape in the chalk circle. - - -## Turn — 2026-06-25 - -The light in the chalk circle collapses inward, then explodes outward in a wave of heat and pressure. The weaver stumbles back, arms shielding his face. - -Where the dagger lay, a figure now stands. - -It is man-shaped, but wrong — tall and gaunt, skin the colour of old ash. A jagged blade protrudes from its own chest, driven in at an angle, crusted with dried black blood. Deep wounds crater its torso and arms — some fresh, some scarred. Its eyes open. They have no pupil, only a dull orange glow, like embers guttering in a dead fire. - -The temperature in the room drops. Frost spiderwebs across the stone floor from the demon's feet. The candles gutter blue again, then die. Only the orange glow of the demon's eyes lights the room. - -The weaver stares, breathless, a grin spreading across his face. *"It worked... it actually worked..."* - -The demon's head turns — slowly — toward the weaver. Then toward the shelf where Rina hides. Then toward the table. - -*A damned thing. Summoned. Wounded. Looking for something to defend — or destroy.* - -Rina's fingers tighten on her crossbow. She's frozen, waiting for your signal. - -Signals Rina to hold steady. They watch as the weaver tries to command the summoned demon — it resists, kills him, and now stands in the dark, aware it is not alone. - - -## Turn — 2026-06-25 - -Silence. Just the drip of moisture somewhere in the tunnel, and the slow, wet breathing of the demon. - -It takes a step toward the shelves — toward Rina. Its bare foot leaves a frozen print on the stone. The blade in its chest grinds with every movement. Its orange eyes sweep the jars, the scrolls, the shadows. - -It stops. Tilts its head. - -*A low, guttural voice, like stones grinding together:* **"I smell you."** - -Rina's hand drifts to her crossbow. - -The demon turns — slowly — toward the table where Dillion crouches. Its cracked lips peel back from teeth that are too long, too many. The cold intensifies. Your breath fogs. - -**"Fresh meat. Wrapped in leather and fear."** - -It takes another step. Then another. The frost crawls up the table legs. - - -## Turn — 2026-06-25 - -The demon stands over you, frost crackling across its scarred hide. The blade in its chest glints in the dim orange light of its eyes. It's close — too close — but it's wounded, slow, and you still have your mace. - -Rina yanks the string of her crossbow, slotting a fresh bolt with trembling fingers. *"Buy me three seconds!"* - -The demon's lips peel back. **"The warm one first. Then the other."** It raises a hand — the same hand that froze your chest — and the temperature in the room plummets again. - -You have a split second. The mace is heavy in your grip — freshly sharpened, never used since Weber's stone. It wants to bite. - - -## Turn — 2026-06-25 - -The demon plants its feet, frost spiderwebbing across the floor. Its eyes fix on you — then dart to Rina, crossbow raised. It's weighing which of you to strike first. - -Rina's voice, low and steady: *"Say the word. I'll put one through its eye."* - -The demon's lips curl. **"The crossbow won't save you, little thing. I've been killed before. It didn't stick."** It takes a step toward Rina, testing. - -You're between them. Mace in hand. Blood still cold where it touched you. +Waits and deliberates, weighing the risk of waiting for the ritual to finish against the danger of interrupting a blood-weaver mid-chant. \ No newline at end of file diff --git a/session/character.md b/session/character.md index 84f7925..fda5085 100644 --- a/session/character.md +++ b/session/character.md @@ -17,7 +17,7 @@ - **Current Health:** 6 - **Armour:** Leather (-1 reduction) - **Weapon:** Mace (1d6+1, freshly sharpened — 1d6+2 for one job) -- **Cash:** 52 silver +- **Cash:** 35 ## Chains @@ -38,7 +38,6 @@ Jaggard — a rich merchant whose name Dillion sullied. - Bedroll - Flint & steel - Small sack -- Healing salve (restores 1 HP) - Creature ear (trophy) - Rusted iron key - Silver locket (auburn hair, engraved "E — never forgotten. M.") diff --git a/session/journal.md b/session/journal.md index 4c92a7d..3b1129e 100644 --- a/session/journal.md +++ b/session/journal.md @@ -2,10 +2,8 @@ ## TODO -- Explore the tunnel under the river (return with Rina, gear up) - ## DONE - - Talk to Lark about the mill for intel - Investigate the old mill at Three Bridges — 2 creatures killed, Rina rescued, tunnel discovered +- Investigate the mine diff --git a/session/last_prompt.md b/session/last_prompt.md index e9f71f5..5dd12cf 100644 --- a/session/last_prompt.md +++ b/session/last_prompt.md @@ -1 +1 @@ -What do you do? +What do you do? \ No newline at end of file diff --git a/session/log/2026-06-23.md b/session/log/2026-06-23.md deleted file mode 100644 index d59f3d4..0000000 --- a/session/log/2026-06-23.md +++ /dev/null @@ -1,21 +0,0 @@ -# Session Log — 2026-06-23 - -- *World Change:* World and Keep established. Character created: Dillion (Guardian, failed jester, secret princess). - -- **Morning** — Splintered Tankard. Dillion checks the notice board at Market Square. Notices: mill at Three Bridges clicking at night (ask Weber's Smithy), vanished caravan (Guild, 15 silver), bones wanted in Amber Hills (2 silver/skeleton at Guild Hall), lost signet ring (ask Fenna's Curios). - -- **Late Morning** — Asks Otta about the mill. She warns it's been dead ten years — just kids' dares and rat droppings. - -- **Late Morning** — Weber's Smithy. Meets Weber (hunchbacked smith, talks to his hammer "Bell"). Counters Weber's 5 silver offer to 15, settles at 8 silver + free mace sharpening if danger. Spit-handshake seals it. *World Change:* Weber added as known NPC; mill job accepted. - -- **Late Morning** — Catches Lark in the Market Square. Asks him about the mill — Lark heard scratching, knows about a loose board. Pressed further — he saw a dark shape moving inside. Dillion recruits him with promise of loot. -- **Late Morning** — Grabs a pitchfork from a market stall for Lark. They head east on the King's Road. -- **Noon** — Arrive at Three Bridges. Scout the perimeter. Spot a dark shape through the window, motionless. Find the loose board behind the wheel. -- **Noon** — Pry the board open. Light a torch. Crawl inside the storage room. -- **Noon** — Combat! A pale sinewy creature drops from the ceiling. Dillion wins initiative, smashes it into the wall for 2 damage. Finishes it with a head shot (5 damage). Creature is dead. Found 3 silver and a rusted iron key in its gut. Cut off an ear as a trophy for Weber. -- **Noon** — Opens the iron-bound chest with the key. Inside: 12 silver, a silver locket with auburn hair, a bundle of love letters from "Marren" to "Elara" — hints the miller was watched. *World Change:* Thread deepened — the miller's disappearance involved more than just the creature. Head upstairs. -- **Noon** — Upstairs: creature's nest. Found a journal (miller trapped the creature, Guild stored something in the cellar, hooded watchers) and a clay jar of grey powder with a note: "For the lock. Sprinkle on the hasp." -- **Noon** — Finds a padlocked trapdoor behind a collapsed shelf. Sprinkles the grey powder on the lock. Yells an insult into the darkness below. -- **Noon** — A juvenile of the same creature climbs up — killed with one mace blow (7 damage). Lights a fresh torch, drops it into the cellar — reveals an iron chest and a woman. She claims she was hired to crack the chest, ambushed by hooded figures, locked in for three days. Lark pats her down — clean. Dillion jumps down to explore the cellar. -- **Noon** — The woman is Rina, a locksmith. Chest contained something long (oilcloth). Found a brass button with a crest: a fist gripping a gear. Dillion peers into the crack — a natural tunnel sloping down, likely passing under the river. -- **Noon** — Decides to head back to the Keep first. Report to Weber, get paid, gear up Rina, then return to explore the tunnel. diff --git a/session/log/2026-06-24.md b/session/log/2026-06-24.md deleted file mode 100644 index 17cb24d..0000000 --- a/session/log/2026-06-24.md +++ /dev/null @@ -1,15 +0,0 @@ -# Session Log — 2026-06-24 - -- **Early Afternoon** — Back at the Keep. Head to Weber's Smithy with Rina and Lark. Dillion tells Rina they'll settle with Weber first, then get her rested. -- **Early Afternoon** — Weber pays 8 silver and sharpens the mace (1d6+2 for next job). Asks if Rina is "one of Fenna's lot." Dillion explains she was locked in the cellar. -- **Early Afternoon** — Dillion, Rina, and Lark head to the Splintered Tankard. Dillion asks Otta to help Rina get cleaned up. Otta agrees — sends Rina to the back for a bath, finds her a change of clothes. -- **Early Afternoon** — While Rina recovers, Dillion hits the Market Square. Buys 6 torches (6 copper) and 5 days rations (5 silver). Pays Lark 5 silver for his help and sends him off. Lark leaves with a grin. -- **Early Afternoon** — Dillion sits down with Rina to learn her skills and background. She's a locksmith and trap-finder, proficient with a crossbow and dagger. Dillion takes her to Weber's for a crossbow, then to the market for a leather vest. -- **Late Afternoon** — Dillion and Rina head back to Three Bridges on the King's Road. Bought two long ropes on the way (4 silver). Reach the mill as the sun begins to dip. Enter through the loose board, descend into the cellar tunnel. -- **Late Afternoon** — Cautious descent into the tunnel. Air grows acrid — blood, decay, mint. The rough-hewn tunnel slopes steadily down, walls sweating moisture. After ~15 minutes, the tunnel opens into a natural cavern. Faint light pulses from bioluminescent fungi on the ceiling. A stone door with the fist-and-gear crest stands on the far wall, slightly ajar. -- **Late Afternoon** — Dillion decides to enter through the ajar stone door. Sneaks in successfully (DEX 5). Rina spots a tripwire — they step over it and proceed into the chamber beyond. -- **Late Afternoon** — Chamber contains a tall construct (grey stone skin, red eyes, cloth/rusted metal armour). It watches but doesn't attack. Dillion and Rina edge along the wall toward the left corridor without triggering it. -- **Late Afternoon** — Left corridor leads to an iron-banded wooden door with a heavy lock. Rina picks it while Dillion stands watch. -- **Late Afternoon** — Beyond the door: a storage room with two men (Beard and Scar) on guard duty. Dillion and Rina slip into cover behind crates (DEX 9). Ambush: Rina bolts Scar through the neck, Dillion cuts Beard's throat. Both killed silently. -- **Late Afternoon** — Bodies hidden. Dillion searches the guards (19 silver, key ring, handbill). Rina finds delivery manifests and a note mentioning "The Weeper" and "H." Dillion takes the loot and moves to the iron-reinforced door. Listens. Opens it safely with a rope from behind crates. Beyond: a dark tunnel sloping downward, humid air, slow rhythmic breathing sound. -- **Late Afternoon** — Dillion and Rina descend into the tunnel. It opens into a chamber with a pit at the centre — chains descending into darkness, slow wet breathing from below. A second iron door on the far side. Dillion and Rina skirt the pit silently (DEX 7). Beyond the door: a hallway with a velvet curtain, warm light and a chanting voice beyond. Dillion peeks: a weaver's study, chalk circle with a dagger at the centre. He pulls back to consult Rina. diff --git a/session/log/2026-06-25.md b/session/log/2026-06-25.md deleted file mode 100644 index 04f332f..0000000 --- a/session/log/2026-06-25.md +++ /dev/null @@ -1,22 +0,0 @@ -# Session Log — 2026-06-25 - -- **Early Afternoon** — Back at the Keep. Head to Weber's Smithy with Rina and Lark. Dillion tells Rina they'll settle with Weber first, then get her rested. -- **Early Afternoon** — Weber pays 8 silver and sharpens the mace (1d6+2 for next job). Asks if Rina is "one of Fenna's lot." Dillion explains she was locked in the cellar. -- **Early Afternoon** — Dillion, Rina, and Lark head to the Splintered Tankard. Dillion asks Otta to help Rina get cleaned up. Otta agrees — sends Rina to the back for a bath, finds her a change of clothes. -- **Early Afternoon** — While Rina recovers, Dillion hits the Market Square. Buys 6 torches (6 copper) and 5 days rations (5 silver). Pays Lark 5 silver for his help and sends him off. Lark leaves with a grin. -- **Early Afternoon** — Dillion sits down with Rina to learn her skills and background. She's a locksmith and trap-finder, proficient with a crossbow and dagger. Dillion takes her to Weber's for a crossbow, then to the market for a leather vest. -- **Late Afternoon** — Dillion and Rina head back to Three Bridges on the King's Road. Bought two long ropes on the way (4 silver). Reach the mill as the sun begins to dip. Enter through the loose board, descend into the cellar tunnel. -- **Late Afternoon** — Cautious descent into the tunnel. Air grows acrid — blood, decay, mint. The rough-hewn tunnel slopes steadily down, walls sweating moisture. After ~15 minutes, the tunnel opens into a natural cavern. Faint light pulses from bioluminescent fungi on the ceiling. A stone door with the fist-and-gear crest stands on the far wall, slightly ajar. -- **Late Afternoon** — Dillion decides to enter through the ajar stone door. Sneaks in successfully (DEX 5). Rina spots a tripwire — they step over it and proceed into the chamber beyond. -- **Late Afternoon** — Chamber contains a tall construct (grey stone skin, red eyes, cloth/rusted metal armour). It watches but doesn't attack. Dillion and Rina edge along the wall toward the left corridor without triggering it. -- **Late Afternoon** — Left corridor leads to an iron-banded wooden door with a heavy lock. Rina picks it while Dillion stands watch. -- **Late Afternoon** — Beyond the door: a storage room with two men (Beard and Scar) on guard duty. Dillion and Rina slip into cover behind crates (DEX 9). Ambush: Rina bolts Scar through the neck, Dillion cuts Beard's throat. Both killed silently. -- **Late Afternoon** — Bodies hidden. Dillion searches the guards (19 silver, key ring, handbill). Rina finds delivery manifests and a note mentioning "The Weeper" and "H." Dillion takes the loot and moves to the iron-reinforced door. Listens. Opens it safely with a rope from behind crates. Beyond: a dark tunnel sloping downward, humid air, slow rhythmic breathing sound. -- **Late Afternoon** — Dillion and Rina descend into the tunnel. It opens into a chamber with a pit at the centre — chains descending into darkness, slow wet breathing from below. A second iron door on the far side. Dillion and Rina skirt the pit silently (DEX 7). Beyond the door: a hallway with a velvet curtain, warm light and a chanting voice beyond. Dillion peeks: a weaver's study, chalk circle with a dagger at the centre. He pulls back to consult Rina. -- **Late Afternoon** — Dillion weighs the options — wait for the ritual to finish and snatch the artifact, or strike now. Rina warns it's blood magick. The weaver's chant climbs toward its peak. Decision hangs in the air. -- **Late Afternoon** — Dillion decides to hide and wait. DEX check (11, success). Dillion and Rina slip into the weaver's study undetected, taking cover behind shelves and the table as the ritual reaches its climax. -- **Late Afternoon** — The ritual completes. A wounded demon emerges from the chalk circle — scarred, a blade in its chest, frost radiating from its feet. Dillion signals Rina to hold. The weaver tries to command the demon — it kills him instead. Now it stands in the dark, searching the room. -- **Late Afternoon** — Combat opens. Dillion wins initiative. He hurls a jar to distract — Rina fires but misses. The demon strikes Dillion with a freezing blast (−4 HP, now 6/10). Dillion is on his feet, mace ready. Rina reloads. -- **Late Afternoon** — Dillion lands a solid blow on the demon's ribs (5+2=7 hits, 1+2-1=2 damage). The demon shrugs it off, wound icing over. Rina finishes reloading. -- **Late Afternoon** — Dillion shouts "Shoot!" Rina fires but misses (3). Dillion swings hard (5) — 6 damage. The demon staggers. Dillion and Rina flee through the hallway into the pit chamber. The demon gives chase. -- **Late Afternoon** — Ambush at the pit. Rina hits (5, 2 damage -1 = 1). Stones miss (1). Dillion tries again (4) — knocks the demon into the pit. Escape clean (5). Dillion and Rina flee the underground, back through the tunnel, and emerge at the mill at dusk. diff --git a/session/session_log.md b/session/session_log.md new file mode 100644 index 0000000..a31959f --- /dev/null +++ b/session/session_log.md @@ -0,0 +1,54 @@ +# Session Log — 2026-06-23 + +- *World Change:* World and Keep established. Character created: Dillion (Guardian, failed jester, secret princess). + +- **Morning** — Splintered Tankard. Dillion checks the notice board at Market Square. Notices: mill at Three Bridges clicking at night (ask Weber's Smithy), vanished caravan (Guild, 15 silver), bones wanted in Amber Hills (2 silver/skeleton at Guild Hall), lost signet ring (ask Fenna's Curios). + +- **Late Morning** — Asks Otta about the mill. She warns it's been dead ten years — just kids' dares and rat droppings. + +- **Late Morning** — Weber's Smithy. Meets Weber (hunchbacked smith, talks to his hammer "Bell"). Counters Weber's 5 silver offer to 15, settles at 8 silver + free mace sharpening if danger. Spit-handshake seals it. *World Change:* Weber added as known NPC; mill job accepted. + +- **Late Morning** — Catches Lark in the Market Square. Asks him about the mill — Lark heard scratching, knows about a loose board. Pressed further — he saw a dark shape moving inside. Dillion recruits him with promise of loot. +- **Late Morning** — Grabs a pitchfork from a market stall for Lark. They head east on the King's Road. +- **Noon** — Arrive at Three Bridges. Scout the perimeter. Spot a dark shape through the window, motionless. Find the loose board behind the wheel. +- **Noon** — Pry the board open. Light a torch. Crawl inside the storage room. +- **Noon** — Combat! A pale sinewy creature drops from the ceiling. Dillion wins initiative, smashes it into the wall for 2 damage. Finishes it with a head shot (5 damage). Creature is dead. Found 3 silver and a rusted iron key in its gut. Cut off an ear as a trophy for Weber. +- **Noon** — Opens the iron-bound chest with the key. Inside: 12 silver, a silver locket with auburn hair, a bundle of love letters from "Marren" to "Elara" — hints the miller was watched. *World Change:* Thread deepened — the miller's disappearance involved more than just the creature. Head upstairs. +- **Noon** — Upstairs: creature's nest. Found a journal (miller trapped the creature, Guild stored something in the cellar, hooded watchers) and a clay jar of grey powder with a note: "For the lock. Sprinkle on the hasp." +- **Noon** — Finds a padlocked trapdoor behind a collapsed shelf. Sprinkles the grey powder on the lock. Yells an insult into the darkness below. +- **Noon** — A juvenile of the same creature climbs up — killed with one mace blow (7 damage). Lights a fresh torch, drops it into the cellar — reveals an iron chest and a woman. She claims she was hired to crack the chest, ambushed by hooded figures, locked in for three days. Lark pats her down — clean. Dillion jumps down to explore the cellar. +- **Noon** — The woman is Rina, a locksmith. Chest contained something long (oilcloth). Found a brass button with a crest: a fist gripping a gear. Dillion peers into the crack — a natural tunnel sloping down, likely passing under the river. +- **Noon** — Decides to head back to the Keep first. Report to Weber, get paid, gear up Rina, then return to explore the tunnel. + +# Session Log — 2026-06-24 + +- **Early Afternoon** — Back at the Keep. Head to Weber's Smithy with Rina and Lark. Dillion tells Rina they'll settle with Weber first, then get her rested. +- **Early Afternoon** — Weber pays 8 silver and sharpens the mace (1d6+2 for next job). Asks if Rina is "one of Fenna's lot." Dillion explains she was locked in the cellar. +- **Early Afternoon** — Dillion, Rina, and Lark head to the Splintered Tankard. Dillion asks Otta to help Rina get cleaned up. Otta agrees — sends Rina to the back for a bath, finds her a change of clothes. +- **Early Afternoon** — While Rina recovers, Dillion hits the Market Square. Buys 6 torches (6 copper) and 5 days rations (5 silver). Pays Lark 5 silver for his help and sends him off. Lark leaves with a grin. +- **Early Afternoon** — Dillion sits down with Rina to learn her skills and background. She's a locksmith and trap-finder, proficient with a crossbow and dagger. Dillion takes her to Weber's for a crossbow, then to the market for a leather vest. +- **Late Afternoon** — Dillion and Rina head back to Three Bridges on the King's Road. Bought two long ropes on the way (4 silver). Reach the mill as the sun begins to dip. Enter through the loose board, descend into the cellar tunnel. +- **Late Afternoon** — Cautious descent into the tunnel. Air grows acrid — blood, decay, mint. The rough-hewn tunnel slopes steadily down, walls sweating moisture. After ~15 minutes, the tunnel opens into a natural cavern. Faint light pulses from bioluminescent fungi on the ceiling. A stone door with the fist-and-gear crest stands on the far wall, slightly ajar. +- **Late Afternoon** — Dillion decides to enter through the ajar stone door. Sneaks in successfully (DEX 5). Rina spots a tripwire — they step over it and proceed into the chamber beyond. +- **Late Afternoon** — Chamber contains a tall construct (grey stone skin, red eyes, cloth/rusted metal armour). It watches but doesn't attack. Dillion and Rina edge along the wall toward the left corridor without triggering it. +- **Late Afternoon** — Left corridor leads to an iron-banded wooden door with a heavy lock. Rina picks it while Dillion stands watch. +- **Late Afternoon** — Beyond the door: a storage room with two men (Beard and Scar) on guard duty. Dillion and Rina slip into cover behind crates (DEX 9). Ambush: Rina bolts Scar through the neck, Dillion cuts Beard's throat. Both killed silently. +- **Late Afternoon** — Bodies hidden. Dillion searches the guards (19 silver, key ring, handbill). Rina finds delivery manifests and a note mentioning "The Weeper" and "H." Dillion takes the loot and moves to the iron-reinforced door. Listens. Opens it safely with a rope from behind crates. Beyond: a dark tunnel sloping downward, humid air, slow rhythmic breathing sound. +- **Late Afternoon** — Dillion and Rina descend into the tunnel. It opens into a chamber with a pit at the centre — chains descending into darkness, slow wet breathing from below. A second iron door on the far side. Dillion and Rina skirt the pit silently (DEX 7). Beyond the door: a hallway with a velvet curtain, warm light and a chanting voice beyond. Dillion peeks: a weaver's study, chalk circle with a dagger at the centre. He pulls back to consult Rina. + +# Session Log — 2026-06-25 + +- **Early Afternoon** — Back at the Keep. Head to Weber's Smithy with Rina and Lark. Dillion tells Rina they'll settle with Weber first, then get her rested. +- **Early Afternoon** — Weber pays 8 silver and sharpens the mace (1d6+2 for next job). Asks if Rina is "one of Fenna's lot." Dillion explains she was locked in the cellar. +- **Early Afternoon** — Dillion, Rina, and Lark head to the Splintered Tankard. Dillion asks Otta to help Rina get cleaned up. Otta agrees — sends Rina to the back for a bath, finds her a change of clothes. +- **Early Afternoon** — While Rina recovers, Dillion hits the Market Square. Buys 6 torches (6 copper) and 5 days rations (5 silver). Pays Lark 5 silver for his help and sends him off. Lark leaves with a grin. +- **Early Afternoon** — Dillion sits down with Rina to learn her skills and background. She's a locksmith and trap-finder, proficient with a crossbow and dagger. Dillion takes her to Weber's for a crossbow, then to the market for a leather vest. +- **Late Afternoon** — Dillion and Rina head back to Three Bridges on the King's Road. Bought two long ropes on the way (4 silver). Reach the mill as the sun begins to dip. Enter through the loose board, descend into the cellar tunnel. +- **Late Afternoon** — Cautious descent into the tunnel. Air grows acrid — blood, decay, mint. The rough-hewn tunnel slopes steadily down, walls sweating moisture. After ~15 minutes, the tunnel opens into a natural cavern. Faint light pulses from bioluminescent fungi on the ceiling. A stone door with the fist-and-gear crest stands on the far wall, slightly ajar. +- **Late Afternoon** — Dillion decides to enter through the ajar stone door. Sneaks in successfully (DEX 5). Rina spots a tripwire — they step over it and proceed into the chamber beyond. +- **Late Afternoon** — Chamber contains a tall construct (grey stone skin, red eyes, cloth/rusted metal armour). It watches but doesn't attack. Dillion and Rina edge along the wall toward the left corridor without triggering it. +- **Late Afternoon** — Left corridor leads to an iron-banded wooden door with a heavy lock. Rina picks it while Dillion stands watch. +- **Late Afternoon** — Beyond the door: a storage room with two men (Beard and Scar) on guard duty. Dillion and Rina slip into cover behind crates (DEX 9). Ambush: Rina bolts Scar through the neck, Dillion cuts Beard's throat. Both killed silently. +- **Late Afternoon** — Bodies hidden. Dillion searches the guards (19 silver, key ring, handbill). Rina finds delivery manifests and a note mentioning "The Weeper" and "H." Dillion takes the loot and moves to the iron-reinforced door. Listens. Opens it safely with a rope from behind crates. Beyond: a dark tunnel sloping downward, humid air, slow rhythmic breathing sound. +- **Late Afternoon** — Dillion and Rina descend into the tunnel. It opens into a chamber with a pit at the centre — chains descending into darkness, slow wet breathing from below. A second iron door on the far side. Dillion and Rina skirt the pit silently (DEX 7). Beyond the door: a hallway with a velvet curtain, warm light and a chanting voice beyond. Dillion peeks: a weaver's study, chalk circle with a dagger at the centre. He pulls back to consult Rina. +- **Late Afternoon** — Dillion weighs the options — wait for the ritual to finish and snatch the artifact, or strike now. Rina warns it's blood magick. The weaver's chant climbs toward its peak. Decision hangs in the air. diff --git a/session/settings.json b/session/settings.json index 37ed6d6..2e2022e 100644 --- a/session/settings.json +++ b/session/settings.json @@ -1,5 +1,5 @@ { "active_tab": "play-tab", "music_muted": true, - "book_page": 16 + "book_page": 15 } diff --git a/tools/engine_lib/paths.py b/tools/engine_lib/paths.py index e9bb48c..f3c419f 100644 --- a/tools/engine_lib/paths.py +++ b/tools/engine_lib/paths.py @@ -5,7 +5,6 @@ paths.py — Path constants for The Chaos game engine. from __future__ import annotations -from datetime import date from pathlib import Path @@ -17,9 +16,8 @@ WORLD_PATH = SESSION_DIR / 'world.md' BOOK_PATH = SESSION_DIR / 'book.md' JOURNAL_PATH = SESSION_DIR / 'journal.md' AMBIENCE_PATH = SESSION_DIR / 'ambience.md' -LOG_DIR = SESSION_DIR / 'log' +LOG_PATH = SESSION_DIR / 'session_log.md' LLM_LOG_PATH = SESSION_DIR / 'llm.log' AMBIENCE_OPTIONS_PATH = SESSION_DIR / "ambience_options.md" CHANGES_PATH = SESSION_DIR / "changes.md" AUDIO_DIR = SESSION_DIR / "audio" -TODAY = date.today().isoformat() diff --git a/tools/engine_lib/prompts.py b/tools/engine_lib/prompts.py index e3efbb7..a7ce7f1 100644 --- a/tools/engine_lib/prompts.py +++ b/tools/engine_lib/prompts.py @@ -1,7 +1,7 @@ from __future__ import annotations from string import Template -SYSTEM_PROMPT = Template("""You are the DM for "The Chaos". Narrate in 2nd person ("You"), vivid but concise. Player: Dillion. +SYSTEM_PROMPT = Template("""You are the DM for "The Chaos". Narrate in 3rd person, vivid but concise. Use the player's name (Dillion) and NPC names explicitly — everything must be parseable on its own without relying on "you" or implied subjects. ## Rules - **Odds**: 1d6, 4+ favourable, 3- trouble. diff --git a/tools/engine_lib/state.py b/tools/engine_lib/state.py index f7d5584..989a107 100644 --- a/tools/engine_lib/state.py +++ b/tools/engine_lib/state.py @@ -10,13 +10,12 @@ from __future__ import annotations import re import sys -from datetime import date, datetime, timedelta from pathlib import Path from .paths import ( CHAR_PATH, WORLD_PATH, BOOK_PATH, JOURNAL_PATH, AMBIENCE_PATH, - LOG_DIR, LLM_LOG_PATH, AMBIENCE_OPTIONS_PATH, CHANGES_PATH, - AUDIO_DIR, TODAY, + LOG_PATH, LLM_LOG_PATH, AMBIENCE_OPTIONS_PATH, CHANGES_PATH, + AUDIO_DIR, ) from .models import TurnResult @@ -27,14 +26,10 @@ def read_file(path: Path) -> str: def read_recent_log(max_entries: int = 5) -> str: - """Read the latest log file and return the last N entries.""" - log_path = LOG_DIR / f"{TODAY}.md" - if not log_path.exists(): - yesterday = (date.today() - timedelta(days=1)).isoformat() - log_path = LOG_DIR / f"{yesterday}.md" - if not log_path.exists(): + """Return the last N entries from the session log.""" + if not LOG_PATH.exists(): return "*No recent events.*" - lines = log_path.read_text().splitlines() + lines = LOG_PATH.read_text().splitlines() entries = [l for l in lines if l.strip().startswith("- ")] return "\n".join(entries[-max_entries:]) or "*No recent events.*" @@ -121,22 +116,44 @@ def apply_state(result: TurnResult) -> None: CHANGES_PATH.write_text("") -def archive_turn(narrative: str) -> None: - """Append the narrative as a new turn in book.md.""" - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M") - heading = f"\n\n## Turn — {timestamp}\n\n" +def next_turn_number() -> int: + """Return the next turn number based on existing turns in book.md.""" + _migrate_turn_headers() + text = read_file(BOOK_PATH) + if not text: + return 1 + return len(re.findall(r"\n## Turn \d", text)) + 1 + + +def _migrate_turn_headers(): + """Rewrite old date-based turn headers (## Turn — YYYY-MM-DD) to numbered format.""" + text = read_file(BOOK_PATH) + if not text: + return + if not re.search(r"\n## Turn — \d{4}", text): + return + turns = re.split(r"\n(?=## Turn )", text) + migrated = [] + for i, t in enumerate(turns, 1): + t = re.sub(r"^## Turn — \d{4}-\d{2}-\d{2}", f"## Turn {i}", t) + migrated.append(t) + BOOK_PATH.write_text("\n".join(migrated)) + + +def archive_turn(narrative: str) -> int: + """Append the narrative as a new turn in book.md. Returns the turn number.""" + num = next_turn_number() + heading = f"\n\n## Turn {num}\n\n" BOOK_PATH.parent.mkdir(parents=True, exist_ok=True) with open(BOOK_PATH, "a") as f: f.write(heading + narrative.strip() + "\n") + return num def append_log(entry: str) -> None: - """Append a log entry to today's log file.""" - LOG_DIR.mkdir(parents=True, exist_ok=True) - log_path = LOG_DIR / f"{TODAY}.md" - if not log_path.exists(): - log_path.write_text(f"# Session Log — {TODAY}\n\n") - with open(log_path, "a") as f: + """Append a log entry to the session log.""" + LOG_PATH.parent.mkdir(parents=True, exist_ok=True) + with open(LOG_PATH, "a") as f: f.write(entry.strip() + "\n") diff --git a/tools/engine_lib/tools_handler.py b/tools/engine_lib/tools_handler.py index def5aae..832c33c 100644 --- a/tools/engine_lib/tools_handler.py +++ b/tools/engine_lib/tools_handler.py @@ -4,7 +4,7 @@ import json import random import re -from .paths import CHAR_PATH, WORLD_PATH, LOG_DIR, TODAY +from .paths import CHAR_PATH, WORLD_PATH from .state import read_file, validate_update_size, update_journal, append_llm_log diff --git a/tools/engine_lib/validation.py b/tools/engine_lib/validation.py index 662218f..bbc06c2 100644 --- a/tools/engine_lib/validation.py +++ b/tools/engine_lib/validation.py @@ -17,9 +17,11 @@ VALIDATION_PROMPT = """You are a strict RPG game master validating whether a pla {world} ## Session Log +*Written in 3rd person with explicit actor names.* {log} ## Recent Story +*Written in 3rd person with explicit actor names.* {story} ## Player Action diff --git a/tools/run.py b/tools/run.py index 5096d1f..f65199a 100755 --- a/tools/run.py +++ b/tools/run.py @@ -438,14 +438,15 @@ class ChaosTUI(App): self._show_error(result.error, result.debug_info) self._append_debug(f"✖ error: {result.error}") return - from datetime import datetime - ts = datetime.now().strftime("%H:%M") - if result.log_entry: - state.append_log(f"- **{ts}** — {result.log_entry}") - elif result.book_log: - state.append_log(f"- **Turn** — {result.book_log.strip().split(chr(10))[0][:80]}") if result.book_log: - state.archive_turn(result.book_log) + turn_num = state.archive_turn(result.book_log) + if result.log_entry: + state.append_log(f"- **Turn {turn_num}** — {result.log_entry}") + else: + summary = result.book_log.strip().split(chr(10))[0][:80] + state.append_log(f"- **Turn {turn_num}** — {summary}") + elif result.log_entry: + state.append_log(f"- {result.log_entry}") state.apply_state(result) if result.book_log or not result.user_prompt: self._display_scene(result) diff --git a/tools/run_utils.py b/tools/run_utils.py index 30abe5c..7fea8e5 100644 --- a/tools/run_utils.py +++ b/tools/run_utils.py @@ -1,9 +1,7 @@ from pathlib import Path -from datetime import date BASE = Path(__file__).resolve().parent.parent SESSION = BASE / 'session' -LOG_DIR = SESSION / 'log' CHAR_PATH = SESSION / 'character.md' WORLD_PATH = SESSION / 'world.md' JOURNAL_PATH = SESSION / 'journal.md' @@ -14,16 +12,15 @@ LAST_PROMPT_PATH = SESSION / 'last_prompt.md' CHANGES_PATH = SESSION / 'changes.md' SETTINGS_PATH = SESSION / 'settings.json' AUDIO_DIR = SESSION / 'audio' -TODAY = date.today().isoformat() -LOG_PATH = LOG_DIR / f'{TODAY}.md' +TODAY = "v0.2" +LOG_PATH = SESSION / 'session_log.md' REFRESH_SECS = 2 def ensure_log(): - LOG_DIR.mkdir(parents=True, exist_ok=True) - if not LOG_PATH.exists(): - LOG_PATH.write_text(f"# Session Log — {TODAY}\n\n") + LOG_PATH.parent.mkdir(parents=True, exist_ok=True) + LOG_PATH.touch(exist_ok=True) _populate_if_empty() diff --git a/tools/test_runtime.py b/tools/test_runtime.py index 221fdd0..a6cddc9 100755 --- a/tools/test_runtime.py +++ b/tools/test_runtime.py @@ -22,12 +22,12 @@ def test_engine_import(): sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) modules_to_test = [ - ('engine_lib.paths', ['BASE_DIR', 'SESSION_DIR', 'CHAR_PATH', 'LLM_LOG_PATH']), + ('engine_lib.paths', ['BASE_DIR', 'SESSION_DIR', 'CHAR_PATH', 'LOG_PATH', 'LLM_LOG_PATH']), ('engine_lib.models', ['TurnResult']), ('engine_lib.prompts', ['SYSTEM_PROMPT']), ('engine_lib.config', ['load_config', 'save_config', 'get_model']), ('engine_lib.context', ['build_system_prompt']), - ('engine_lib.state', ['read_file', 'apply_state', 'append_log', 'append_llm_log']), + ('engine_lib.state', ['read_file', 'apply_state', 'append_log', 'append_llm_log', 'next_turn_number']), ('engine_lib.tools_handler', ['execute_tool', 'extract_tool_calls', 'TOOL_REGISTRY']), ('engine_lib.llm', ['call_llm']), ('engine_lib.validation', ['auto_prompt', 'validate_action']),